6.7 KiB
Change: Add Deterministic Core
Why
The report schema (build step #1) is frozen, but no code exists yet. The plugin
cannot do anything until the deterministic substrate is built: the only thing the
user ever sees unprompted (the SessionStart staleness reminder), the thing that
produces the candidate set the AI later classifies (the scanner), and the thing
that holds lifecycle state and reports per-project (the state store). All three
are deterministic, zero-model seams (invariant #6) and must exist before the
check skill (the first AI pass) can be built on top of them.
This is build step #2 per CLAUDE.md. Because no plugin in this collection has ever
wired a Claude Code hook, the change leads with a build-spike: a trivial
SessionStart hook emitting a systemMessage banner, confirmed to render
visibly in a real session, de-risking the entire reminder mechanism before the
deterministic logic is built on it.
What Changes
- Build-spike (sequential gate, first): stand up a trivial
hooks/hooks.jsonwiring aSessionStarthook (matcher: startup|resume) that emits a fixedsystemMessagebanner, and confirm it renders visibly in a real session. This proves the one mechanism with no precedent in the collection before any logic depends on it. - Reminder hook (
session-reminder): a deterministic reminder script invoked by theSessionStarthook. It reads state, compareslast_checkto a staleness threshold, and emits thesystemMessagebanner (naming the slash command to run) only when stale. It spends ZERO AI tokens, runs no scan or classification, exits 0 within a low timeout, and mutates nothing except writinglast_reminded(invariant #1). It snoozes to at most once per calendar day while stale, gated bylast_reminded(invariant #2). It is unit-testable with an injected clock. - Scanner (
doc-scanner): a deterministic walker that produces the shortlist (candidate project-root-relative paths) and the per-path signals the report schema consumes — broken references, version skew, edit-recency vs git churn, location, append-only growth, archive-to-live ratio, frontmatter markers. It honors all exclusions (invariant #9):hygiene: frozenfrontmatter,.dochygiene-ignorepatterns, detected append-only logs, self-exclusion of.dochygiene/, and the configuredscan.excluded_dirs(build, vendor, archive, graphify-out, .dochygiene). It uses no model (invariant #6). It emits an intermediate structured artifact (shortlist + per-path signals), NOT a full machine report — it does not classify, writeentries, or populatetoken_estimate. - State store (
state-store): an in-project store rooted at the resolved project root (git root, fallback cwd) under a gitignored.dochygiene/directory. It reads/writes the lifecycle timestamps (last_check,last_clean,last_reminded), enforces report rollover (keep exactly the latest.json+.mdpair, invariant #4), uses atomic writes, confines all writes to.dochygiene/, keeps no global cross-project index, and never silently edits the user's.gitignore(invariant #3). It is unit-testable with an injected clock and filesystem.
Capabilities
New Capabilities
session-reminder: the deterministicSessionStartreminder — hook wiring, the zero-token staleness banner viasystemMessage, the once-per-day snooze gated bylast_reminded, and the invariant that the hook only reminds (no scan, no classification, no mutation beyondlast_reminded). The build-spike's hook-wiring is proven under this capability.doc-scanner: the deterministic scan — walking scoped files, computing per-path objective signals, applying the exclusion pipeline (frozen frontmatter, ignore patterns, append-only detection, self-exclusion, excluded dirs), and emitting the intermediate shortlist + signals artifact. No model.state-store: the in-project state and report store — project-root resolution, lifecycle timestamps, atomic writes, report rollover, write confinement to.dochygiene/, no global index, and no silent.gitignoreedits.
One capability per independently-testable deterministic seam: each maps to a distinct runtime component with its own test seam (injected-clock snooze; exclusion pipeline; root-resolution + rollover + atomic-write), so splitting keeps each requirement set focused and the golden/unit tests cohesive rather than one monolithic capability spanning three unrelated concerns.
Modified Capabilities
None. The report-schema capability is consumed, not modified — this change
produces the shortlist and per-path signals that the frozen schema already
specifies, and changes none of its requirements.
Impact
- Affected specs: creates three new capabilities —
session-reminder,doc-scanner,state-store. Consumes the existing frozenreport-schemacapability without modifying it. - Affected code: introduces
hooks/hooks.json(first CC hook in the collection) and deterministic Python scripts underscripts/(reminder, scanner, state store) plus their unit tests. No skill or AI pass is added here. - Out of scope (named owners, so nothing is left ownerless):
- The AI classify pass /
checkskill (producesentries, reads the scanner's intermediate artifact, folds shortlist + signals into the machine report). Owner: the upcomingadd-checkchange. raw_tokens/token_estimatepopulation.token_estimateis a field onentries, andraw_tokenscounts "the removed or reduced span" — a span that only exists once the AI selects anop. Although the count is deterministic (invariant #6), its input (the chosen op) does not exist until classification, so the scanner has nothing to count.raw_tokens(v1-required) is therefore populated by thecheckchange, which ownsentries. The token estimator's full build (injection-frequency weighting + bottom-up rollup) remains the v2 bonus per PRD phase 5. "Deterministic" does not imply "belongs in deterministic-core."- The
.gitignoreignore-entry offer (the one-line "add.dochygiene/to.gitignore?" surfaced on first check). This state store only guarantees it never silently edits.gitignore; the user-facing offer UX is acheck-change concern. Note: creating the.dochygiene/directory untracked-but-not-yet-ignored is allowed — invariant #3 forbids editing.gitignore, not creating the dir. - The
cleanskill + patch-applier, thesweeporchestration, and the mtime/content guard application — all later changes.
- The AI classify pass /
- Dependencies: depends on the frozen
report-schema(theshortlistandsignalsshapes). Unblocks thecheckchange.