6.5 KiB
Change: Add Check
Why
The deterministic substrate is in place: the SessionStart reminder, the scanner
(shortlist + per-path signals), the state store (timestamps, atomic writes, report
rollover), and the token estimator all exist and are deterministic, zero-model
seams. The reminder banner already advertises /hygiene check as the action the
user should take when docs are stale — but no /hygiene command and no check
skill exist yet, so following the reminder's own advice does nothing.
This is build step #3 per CLAUDE.md. It is the plugin's first AI pass: it folds the
scanner's intermediate artifact and a Sonnet classification into a schema-valid
machine report and a human report, then stamps last_check. Four per-entry fields
on the frozen report schema cannot be model-authored without violating an
invariant or being physically impossible — exact_edit.expected_sha256 (sha256 of
real file bytes), safety_tier (invariant #10: derived only by
derive_safety_tier(...), never model-assigned), is_destructive/is_reversible
for deterministic ops (fixed by exact_edit.kind via KIND_TABLE), and
token_estimate.raw_tokens (invariant #6: from the local token estimator, never a
model). Therefore check MUST interpose a deterministic finalize pass between
model classification and the report write. This change introduces that pass as a
standalone, model-free assembler.
What Changes
- Deterministic finalize pass (
report_builder.py): a new standalone, model-free script that sits between the model's classification andStateStore.write_report. Input: the scanner artifact plus the model's slim per-file classification proposals. For each proposal it reads the anchored span, computesexpected_sha256over the file's current bytes, looks up(is_destructive, is_reversible)fromKIND_TABLE[kind], callsderive_safety_tier(...)imported fromvalidate_report.py(single source of truth, invariant #10), sourcesraw_tokensby callingtoken_estimator.default_estimator().estimate_for_report(span_text)(invariant #6), stamps each entry'sgenerated_atat that file's hash instant, and assembles the schema-valid machine report envelope plus a deterministic human-report skeleton. Output: the full machine report + human skeleton. - Command surface (
commands/hygiene.md): a single command file dispatching on$ARGUMENTS.check [--scope <glob-or-path>] [--category <class|subtype>]invokes thehygiene-checkskill;statusreads lifecycle timestamps and report presence inline (read-only, no scan, no model);clean/sweepare reserved with a "not yet implemented (Phase 4)" message; no/unknown args print usage plus current status. - Check skill (
skills/hygiene-check/SKILL.md): the orchestration. Modeled on thecommitskill: the SKILL.md workflow runs deterministic scripts via Bash, dispatches a Sonnet subagent for judgment-only classification, then runs deterministic scripts to finalize, validate, and write. Enforces the validate-before-rollover sequencing (validation runs on a scratch path, never in.dochygiene/, becausewrite_reportdeletes the prior pair first — invariant #4). - Classifier golden examples + hermetic harness: Layer-2 reversion-protection
fixtures under
examples/golden/classifier/(input doc tree → expected classification report), distinct from the schema-shape fixtures (valid_report.json/invalid_report.json). A hermetic pytest harness asserts only deterministic/stable parts (no live model call); the live model-classification regression is a separate, manually/agent-invoked harness.
Capabilities
New Capabilities
doc-check: the first AI pass and its deterministic guardrails — the/hygienecommand surface (check/status, withclean/sweepreserved), thehygiene-checkskill orchestration (scan → Sonnet classify → deterministic finalize → validate-before-write → write + stamp), the model-freereport_builder.pyfinalize pass that owns the four non-model-authored fields, the validate-before-rollover sequencing constraint, and the hermetic classifier golden harness. One capability: the command, the skill, the finalize pass, and the report-write sequencing are a single check pipeline whose pieces are only meaningful together; splittingreport_builderout would scatter a tightly coupled flow across capabilities.
Modified Capabilities
None. The report-schema capability is consumed, not modified — this change
populates the frozen schema's scan, shortlist, and entries (including the
four fields the schema requires be derived/computed, not model-assigned) and changes
none of its requirements.
Impact
- Affected specs: creates one new capability —
doc-check. Consumes the frozenreport-schema,doc-scanner, andstate-storecapabilities without modifying them. - Affected code: introduces a new deterministic script
scripts/report_builder.py(plus its unit tests), the command filecommands/hygiene.md, the skillskills/hygiene-check/SKILL.md, and classifier golden fixtures + a hermetic test underexamples/golden/classifier/andtests/. Importsderive_safety_tier/KIND_TABLEfromvalidate_report.pyanddefault_estimator/estimate_for_reportfromtoken_estimator.py(no re-implementation). - Out of scope (named owners, so nothing is left ownerless):
- The
cleanskill + patch-applier, thesweeporchestration, and the mtime/content guard application — the upcomingadd-cleanchange. Note the deferred hole this change documents:insert-frontmatterops havehas_anchor = Falseand therefore carry noexpected_sha256, so the invariant #8 content guard does not protect them at clean time. Phase 4 (clean) MUST handleinsert-frontmatterapplication explicitly. token_estimateweighting (injection_frequency,weighted_tokens, bottom-up rollup) — the v2 bonus per PRD phase 5. This change populates onlyraw_tokens.- The live model-classification regression harness (running an actual check against a fixture tree and diffing) — a separate, manually/agent-invoked harness, not part of the unit suite.
- The
- Dependencies: depends on the frozen
report-schema(the report shape and thederive_safety_tier/KIND_TABLEsingle source of truth), thedoc-scannerintermediate artifact, thestate-store(write_report,set_last_check, rollover), and the token estimator. Unblocks theadd-cleanchange.