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

110 lines
6.5 KiB
Markdown

# 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.