# Tasks: Add Clean > **Execution shape:** T1 and T2 are already DONE (patch_applier.py + 263 tests > pass). T3 (`workflows/distill.md`) has no upstream dependency among the remaining > tasks and can start immediately. T4 (`hygiene-clean` SKILL.md, git-safety inline) > is **sequential after T3**. T5 (integration tests) is **sequential after T4**. T6 > (`commands/hygiene.md` update) and T7 (CONTEXT.md + CLAUDE.md) are independent of > T3–T5 and can run in parallel with any group. Model routing is annotated per task. ## 1. patch_applier.py — DONE (model: Sonnet) - [x] 1.1 Implement `scripts/patch_applier.py` as a standalone, model-free applier. `PatchApplier(project_root, fs, git)` with `apply_indexed([(orig_index, entry)]) → {applied, skipped, failed, staged_paths}`. CLI: `python patch_applier.py --report --apply-indices 0,2,3`. Exit 0 all / 1 partial / 2 usage. - [x] 1.2 Implement per-file transaction: read bytes once, verify sha256 for every anchored entry, skip whole file on mismatch (`content-changed-since-check`), apply anchored edits descending by `anchor.start_line`, apply `insert-frontmatter` last, write once, stage once. - [x] 1.3 Implement `insert-frontmatter` re-derivation at apply time: re-read file, parse frontmatter; key+target value → idempotent no-op; key+conflict → `frontmatter-key-conflict` skip; key absent → insert (create `---` block if needed) as last in-memory step. - [x] 1.4 Implement incompatible-ops detection: per file, skip whole file (`incompatible-ops-on-file`) if `move-to-archive` co-occurs with any other op, or if anchor ranges overlap. Never compose in v1. - [x] 1.5 Implement all KIND_TABLE kinds: `delete-range`, `move-to-archive` (git mv, mkdir dest, `auto`), `insert-frontmatter` (`auto`), `replace-text` (first occurrence in span, `auto`), `dedupe` (`auto`). `(is_destructive, is_reversible)` from KIND_TABLE, not entry. - [x] 1.6 Handle edge cases: `move-to-archive` source-gone+dest-present → idempotent success; `replace-text`/`insert-frontmatter` already applied → no-op success; non-UTF-8 file → `non-utf8-content` skip; generative/unknown kind → rejected/skip. ## 2. patch_applier.py tests — DONE (model: Sonnet) - [x] 2.1 Write unit tests for per-file transaction: sha256 mismatch skips entire batch; clean match applies in descending-anchor order; `insert-frontmatter` applied last; write-once / stage-once assertions. - [x] 2.2 Write unit tests for `insert-frontmatter` re-derivation: idempotent no-op, key-conflict skip, key-absent insert (with and without existing `---` block). - [x] 2.3 Write unit tests for incompatible-ops detection: `move-to-archive`+other → skip; overlapping anchors → skip; non-conflicting entries → proceed. - [x] 2.4 Write unit tests for every KIND_TABLE kind: delete-range, move-to-archive (git mv both sides, mkdir dest), replace-text (first occ in span), dedupe, insert-frontmatter. Assert exit codes (0/1/2). - [x] 2.5 Confirm 263 tests pass: `pytest tests/` green. ## 3. workflows/distill.md — generative Sonnet workflow (model: Sonnet) - [ ] 3.1 Author `workflows/distill.md` as the Sonnet generative workflow (LOOP-GUARD: the skill points the subagent at this workflow doc, not SKILL.md, to avoid recursion). Document the LOOP-GUARD pattern in the workflow header. - [ ] 3.2 Specify the subagent inputs: live file contents (read at dispatch time, not from report cache), the report entry (`op`, `category`, `signals`, `detail`). - [ ] 3.3 Specify the output contract: `distill` → condensed prose replacing the span; `split` → new-primary prose + archived-section to append or move; skill writes and stages the result. - [ ] 3.4 Specify failure handling: if Sonnet returns malformed output, log and skip the entry (do not abort the run). ## 4. skills/hygiene-clean/SKILL.md (model: Sonnet) - [ ] 4.1 Author `skills/hygiene-clean/SKILL.md` with frontmatter mirroring the `hygiene-check` skill style. Precondition: `CLAUDE_PLUGIN_ROOT` is set; if not, halt with a clear error. - [ ] 4.2 Implement step 1: load report via `StateStore.read_report`; if none, tell user to run `/hygiene check` and stop. - [ ] 4.3 Implement step 2: re-validate loaded report via `validate_report.py`; if invalid, stop. - [ ] 4.4 Implement step 3: apply scope/category filter to entries (entry-stage, same as check). - [ ] 4.5 Implement step 4: partition entries into auto+deterministic, confirm+deterministic (`delete-range`), and generative (always `confirm`). - [ ] 4.6 Implement step 5 (GATE FIRST): present batch-confirm list for confirm-tier entries — path · category · op · tokens · why; per-entry opt-out; visually distinguish irreversible `delete-range` from reversible auto ops. Approved set = all auto + approved confirm. - [ ] 4.7 Implement step 6: git preflight — resolve project root; `git status --porcelain`; clean → baseline = HEAD; dirty → auto-create WIP checkpoint commit (user work only), baseline = that checkpoint. - [ ] 4.8 Implement step 7: apply deterministic approved entries via `python patch_applier.py --report --apply-indices `; capture JSON result (`{applied, skipped, failed, staged_paths}`). - [ ] 4.9 Implement step 8: for each approved generative entry — confirm file exists; read live file contents; dispatch Sonnet subagent → `workflows/distill.md` (LOOP-GUARD); receive new content; write + stage. - [ ] 4.10 Implement step 9: staging — `git add ` for non-move ops; DO NOT `git add` `move-to-archive` dest (applier already staged via `git mv`). NEVER `git add -A` or `git add .`. - [ ] 4.11 Implement step 10: single commit via `git-context commit-apply --message-stdin` with generated message: `docs: hygiene cleanup (N edits across M files)\n\nAuto: ...\nConfirmed: ...\nSkipped (re-analysis recommended): ...` No Co-Authored-By, no AI attribution, no emoji, no trailers. - [ ] 4.12 Implement step 11: stamp `last_clean` to commit instant via `StateStore.set_last_clean(commit_instant)`. - [ ] 4.13 Implement step 12: surface summary — applied / skipped (with re-analysis note) / confirmed counts, commit SHA, note about checking for new orphans after `move-to-archive`. - [ ] 4.14 Implement failure handling: guard mismatch → skip-file-report-continue; applier exit 2 / `git mv` fail / write error → `git restore`/`reset` to baseline, abort with structured error; partial success (applier exit 1) → NOT rollback, commit what applied; zero in-scope → no-op report, no commit. ## 5. Integration tests (model: Sonnet) - [ ] 5.1 Implement `tests/test_clean_integration.py` using a fixture git repo. Test invariant #5 GAPs: (a) dirty-tree → WIP checkpoint + exactly one cleanup commit (two total); (b) clean-tree → exactly one cleanup commit; (c) all-guard-skipped → zero commits, no rollback noise. - [ ] 5.2 Test invariant #7 sweep-gating GAP: when entries include `confirm`-tier ops and user is running under `/hygiene sweep`, the confirm gate still fires identically (not bypassed). - [ ] 5.3 Test `move-to-archive` precise staging: applier stages both sides via `git mv`; skill does not call `git add` on the dest; `git diff --cached` shows the rename cleanly. - [ ] 5.4 Test rollback path: simulate applier exit 2 mid-run; assert tree is restored to baseline and no partial commit exists. - [ ] 5.5 Confirm all new integration tests pass alongside the 263 existing unit tests. ## 6. commands/hygiene.md — clean/sweep routing (model: Haiku) - [ ] 6.1 Replace the "not yet implemented (Phase 4)" stubs for `clean` and `sweep` in `commands/hygiene.md` with live routing to the `hygiene-clean` skill, passing `--scope` and `--category` flags through. - [ ] 6.2 Wire `/hygiene sweep` as command-level check-then-clean sequencing: invoke `hygiene-check` skill, then `hygiene-clean` skill, passing shared flags to both. No third skill; no double-commit. ## 7. CONTEXT.md updates and CLAUDE.md fix (model: Haiku) - [ ] 7.1 Update `scripts/CONTEXT.md` to add `patch_applier.py` — its role as the deterministic in-memory applier, the transaction model, and its exit codes. - [ ] 7.2 Author `skills/hygiene-clean/CONTEXT.md` (progressive disclosure index for the clean skill directory). - [ ] 7.3 Update `CLAUDE.md` build-order step 4 note: mark the deferred `insert-frontmatter` hole as RESOLVED — the applier re-derives frontmatter freshness at apply time (idempotent key-absence check) instead of trusting a cached hash.