Skip to main content

wadachi_spec/
lib.rs

1//! `wadachi-spec` (轍) — the frecency-ranking core, shared fleet-wide.
2//!
3//! This crate is **zero-I/O on purpose**: it owns exactly one thing — *how a
4//! path's worn-ness is scored from when it was visited* — and nothing else
5//! (no `SQLite`, no filesystem, no clock except behind a trait). That lets every
6//! consumer depend on it without dragging in storage: `wadachi`'s directory
7//! store, skim-tab's command history, and a zoxide import all rank through
8//! [`apply`] so there is exactly one formula and they cannot drift.
9//!
10//! It is authored as the pleme-io TYPED-SPEC + INTERPRETER TRIPLET:
11//! - **Typed border** — [`FrecencyRankingSpec`] + [`DecayKind`] + [`RankPhase`]
12//!   ([`spec`]).
13//! - **Authored Lisp spec** — `specs/frecency.lisp` declares the canonical
14//!   instances as data.
15//! - **Interpreter** — [`apply`] walks the phases against a mockable
16//!   [`FrecencyEnvironment`] ([`interp`], [`env`]).
17//!
18//! ```
19//! use wadachi_spec::{apply, FrecencyRankingSpec, DirEntry, MockEnvironment};
20//! use chrono::NaiveDate;
21//!
22//! let now = NaiveDate::from_ymd_opt(2026, 6, 9).unwrap().and_hms_opt(0, 0, 0).unwrap();
23//! let env = MockEnvironment::at(now);
24//! let spec = FrecencyRankingSpec::skimtab_parity();
25//! let entries = vec![DirEntry {
26//!     path: "/code".into(),
27//!     visits: vec![now], // visited "now" → age 0 → score 1.0
28//!     discovered_only: false,
29//! }];
30//! let ranked = apply(&spec, entries, &env).unwrap();
31//! assert!((ranked[0].score - 1.0).abs() < 1e-9);
32//! ```
33
34pub mod env;
35pub mod interp;
36pub mod spec;
37
38pub use env::{FrecencyEnvironment, MockEnvironment, RealEnvironment};
39pub use interp::{apply, SpecError};
40pub use spec::{DecayKind, DirEntry, FrecencyRankingSpec, RankPhase, RankedDir};