110 lines
6.5 KiB
Markdown
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.
|