cc-os/plugins/os-doc-hygiene/openspec/changes/archive/2026-06-24-add-check/proposal.md

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 and StateStore.write_report. Input: the scanner artifact plus the model's slim per-file classification proposals. For each proposal it reads the anchored span, computes expected_sha256 over the file's current bytes, looks up (is_destructive, is_reversible) from KIND_TABLE[kind], calls derive_safety_tier(...) imported from validate_report.py (single source of truth, invariant #10), sources raw_tokens by calling token_estimator.default_estimator().estimate_for_report(span_text) (invariant #6), stamps each entry's generated_at at 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 the hygiene-check skill; status reads lifecycle timestamps and report presence inline (read-only, no scan, no model); clean / sweep are 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 the commit skill: 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/, because write_report deletes 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 /hygiene command surface (check / status, with clean / sweep reserved), the hygiene-check skill orchestration (scan → Sonnet classify → deterministic finalize → validate-before-write → write + stamp), the model-free report_builder.py finalize 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; splitting report_builder out 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 frozen report-schema, doc-scanner, and state-store capabilities without modifying them.
  • Affected code: introduces a new deterministic script scripts/report_builder.py (plus its unit tests), the command file commands/hygiene.md, the skill skills/hygiene-check/SKILL.md, and classifier golden fixtures + a hermetic test under examples/golden/classifier/ and tests/. Imports derive_safety_tier/KIND_TABLE from validate_report.py and default_estimator/estimate_for_report from token_estimator.py (no re-implementation).
  • Out of scope (named owners, so nothing is left ownerless):
    • The clean skill + patch-applier, the sweep orchestration, and the mtime/content guard application — the upcoming add-clean change. Note the deferred hole this change documents: insert-frontmatter ops have has_anchor = False and therefore carry no expected_sha256, so the invariant #8 content guard does not protect them at clean time. Phase 4 (clean) MUST handle insert-frontmatter application explicitly.
    • token_estimate weighting (injection_frequency, weighted_tokens, bottom-up rollup) — the v2 bonus per PRD phase 5. This change populates only raw_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.
  • Dependencies: depends on the frozen report-schema (the report shape and the derive_safety_tier/KIND_TABLE single source of truth), the doc-scanner intermediate artifact, the state-store (write_report, set_last_check, rollover), and the token estimator. Unblocks the add-clean change.