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