112 lines
6.8 KiB
Markdown
112 lines
6.8 KiB
Markdown
# Tasks: Add Check
|
||
|
||
> **Execution shape:** Task 1 is a **human-gate** (already discharged — the
|
||
> `report_builder.py` finalize pass is approved). Group 2 (`report_builder.py` +
|
||
> tests) and Group 3 (`commands/hygiene.md`) are **independent and run in
|
||
> parallel**. Group 4 (the `hygiene-check` skill) is **sequential after Groups 2
|
||
> and 3**. Group 5 (classifier goldens + hermetic harness) is **sequential after
|
||
> Groups 2 and 4** and is **human-gated** per the META-RULE. Group 6 (CONTEXT.md)
|
||
> runs last. Model routing is annotated per group.
|
||
|
||
## 1. Human-gate — approve the deterministic finalize pass (DONE)
|
||
|
||
- [x] 1.1 Approve `report_builder.py` as a standalone, model-free finalize pass
|
||
interposed between model classification and `StateStore.write_report`, owning
|
||
the four fields the model must not author (`expected_sha256`, `safety_tier`,
|
||
`is_destructive`/`is_reversible` for deterministic ops, `raw_tokens`).
|
||
|
||
## 2. `report_builder.py` + tests — parallel with Group 3 (model: Sonnet)
|
||
|
||
- [ ] 2.1 Implement `scripts/report_builder.py` as a standalone, model-free
|
||
assembler. Input: the scanner artifact + the model's slim per-file
|
||
classification proposals. Output: a full schema-valid machine report + a
|
||
deterministic human-report skeleton. Use an injected clock/filesystem.
|
||
- [ ] 2.2 Compute `exact_edit.expected_sha256` over each file's **current bytes**
|
||
for anchor-bearing kinds only; note that `insert-frontmatter`
|
||
(`has_anchor = False`) carries no `expected_sha256` (deferred hole for clean).
|
||
- [ ] 2.3 Set `(is_destructive, is_reversible)` from `KIND_TABLE[kind]` and compute
|
||
`safety_tier = derive_safety_tier(op_type, is_destructive, is_reversible)`,
|
||
both **imported** from `validate_report.py` (single source of truth —
|
||
invariant #10; no re-implementation).
|
||
- [ ] 2.4 Extract the span (anchor range / `reducible_range` / whole file for
|
||
`move-to-archive`) and source `token_estimate` via
|
||
`token_estimator.default_estimator().estimate_for_report(span_text)` —
|
||
v1 `raw_tokens` only, weighting `null` (invariant #6).
|
||
- [ ] 2.5 Assemble the envelope: `schema_version` `"1.0"`, `tool_version` read from
|
||
`plugin.json` (flag a mismatch with `valid_report.json`), per-entry
|
||
`generated_at` = that file's hash instant, envelope `generated_at` = the run
|
||
instant. Emit the deterministic human-report skeleton (mechanical parts only).
|
||
- [ ] 2.6 Reject malformed proposal JSON before computing any field.
|
||
- [ ] 2.7 **Tests:** assert the sha256, the derived `(is_destructive,
|
||
is_reversible, safety_tier)`, and `raw_tokens` for each kind; assert the
|
||
assembled report round-trips through `validate_report.py` (exit 0); assert the
|
||
generative path counts `raw_tokens` over the non-persisted `reducible_range`;
|
||
injected clock/fs.
|
||
|
||
## 3. `commands/hygiene.md` — parallel with Group 2 (model: Haiku)
|
||
|
||
- [ ] 3.1 Author a single `commands/hygiene.md` with minimal frontmatter
|
||
(`name: hygiene`; description naming `/hygiene check` and `/hygiene status`)
|
||
that dispatches on `$ARGUMENTS`.
|
||
- [ ] 3.2 Route `check [--scope <glob-or-path>] [--category <class|subtype>]` →
|
||
invoke the `hygiene-check` skill (pass the flags through).
|
||
- [ ] 3.3 Implement `status` **inline** (a few `python3` calls): read
|
||
`get_last_check` / `get_last_clean` / `get_last_reminded` and whether a report
|
||
exists. Read-only — no scan, no model.
|
||
- [ ] 3.4 Reserve `clean` / `sweep` with a "not yet implemented (Phase 4)" message;
|
||
print usage + current status for no/unknown args.
|
||
|
||
## 4. `skills/hygiene-check/SKILL.md` — sequential after Groups 2, 3 (model: Sonnet)
|
||
|
||
- [ ] 4.1 Author `skills/hygiene-check/SKILL.md` with frontmatter mirroring the
|
||
`commit` skill. The subagent points at a **workflow doc**, not SKILL.md
|
||
itself, to avoid recursion (per the `commit`-skill precedent).
|
||
- [ ] 4.2 Implement the deterministic steps: scan (`scanner.py`, `--scope` →
|
||
`--globs`/path filter), select signal-bearing candidates (zero-signal files
|
||
presumptively cleared, not read by the model), finalize (`report_builder.py`),
|
||
validate **on a scratch path** (validate-before-rollover, invariant #4), write
|
||
(`write_report`), stamp (`set_last_check` with the same run instant).
|
||
- [ ] 4.3 Write the **Sonnet classification subagent prompt**: per signal-bearing
|
||
file, return a slim judgment-only proposal (`category` from the closed enum,
|
||
`signals` verbatim + optional `detail` gloss, `op`, `op_type`, the
|
||
`exact_edit` **skeleton** for deterministic or a non-persisted
|
||
`reducible_range` for generative, `confidence`). The model supplies **no**
|
||
`expected_sha256` / `is_destructive` / `is_reversible` / `safety_tier`.
|
||
- [ ] 4.4 Encode the decision rules (subtype → kind → tier) and the Opus-escalation
|
||
rule (low confidence on stale-vs-bloat or destructive-vs-generative).
|
||
- [ ] 4.5 Encode failure handling: validation fail (exit 1) → do not write, map
|
||
violations to entries, re-prompt or drop and re-validate; usage error (exit 2)
|
||
→ stop; empty shortlist → write empty-`entries` report + stamp; malformed
|
||
proposal → re-prompt. Record `--scope` / `--category` in the report header.
|
||
|
||
## 5. Classifier goldens + hermetic harness — after Groups 2, 4; HUMAN-GATED (model: Sonnet)
|
||
|
||
- [ ] 5.1 Create 3–5 static fixture trees under
|
||
`examples/golden/classifier/<n>-<name>/` (`input/` with stable sha256s,
|
||
`expected.json`, optional `notes.md`), each mapping a subtype to a **distinct**
|
||
`exact_edit.kind`: `orphaned`→`delete-range`/`confirm`,
|
||
`superseded`→`move-to-archive`/`auto`,
|
||
`completed-in-place`→`insert-frontmatter`/`auto`, `duplicated`→`dedupe`/`auto`,
|
||
`distill`→generative/`confirm`.
|
||
- [ ] 5.2 Implement `tests/test_classifier_golden.py` **hermetically** (no live
|
||
model call): (1) `scanner.py` on `input/` emits the expected signals on the
|
||
right paths; (2) `validate_report.py` on each `expected.json` → exit 0; (3)
|
||
stable-field match against a captured/committed check output (`category.class`,
|
||
`category.subtype`, `op_type`, derived `safety_tier`, `exact_edit.kind`).
|
||
Op-prose and exact anchor line numbers are advisory (flag, do not hard-fail).
|
||
- [ ] 5.3 Document the **separate** live model-classification regression harness
|
||
(manually/agent-invoked, NOT part of the unit suite).
|
||
- [ ] 5.4 Update `examples/golden/CONTEXT.md` to distinguish classifier goldens from
|
||
the schema-shape fixtures.
|
||
|
||
## 6. CONTEXT.md updates — last (model: Haiku)
|
||
|
||
- [ ] 6.1 Author `CONTEXT.md` for `commands/` and `skills/` (progressive
|
||
disclosure).
|
||
- [ ] 6.2 Update `scripts/CONTEXT.md` to add `report_builder.py` (its role as the
|
||
deterministic finalize pass and the symbols it imports).
|
||
|
||
## 7. Validation
|
||
|
||
- [ ] 7.1 Run `openspec validate add-check --strict` and confirm it passes.
|