TBD - created by archiving change add-clean. Update Purpose after archive.
## Requirements
### Requirement: Per-File Transaction with Content-Hash Guard
The patch applier SHALL apply all entries for a given file as a single in-memory
transaction. It SHALL read the file's bytes once, verify that `sha256(bytes)`
matches `expected_sha256` for every anchored entry on that file, and on any
mismatch skip the **entire file batch** with reason `content-changed-since-check`
(recommending re-analysis) while continuing to process other files. It SHALL apply
anchored edits in memory in descending order by `anchor.start_line`, and SHALL apply
`insert-frontmatter` last (after all anchor edits) because it prepends and shifts
line numbers. It SHALL write the file once and stage once after all in-memory edits
succeed.
#### Scenario: Mismatch skips the entire file batch
- **WHEN** a file's current sha256 differs from any anchored entry's `expected_sha256`
- **THEN** the applier skips the entire file batch with reason `content-changed-since-check`, leaves the file untouched, and continues processing other files
#### Scenario: Descending-anchor application preserves line offsets
- **WHEN** a file has multiple anchored entries (e.g., line 40 and line 10)
- **THEN** the applier applies the line-40 edit first, then the line-10 edit, so the second edit lands on the correct line in the still-unmodified earlier portion of the file
#### Scenario: insert-frontmatter is applied last
- **WHEN** a file has both an anchored edit and an insert-frontmatter entry
- **THEN** the anchored edit is applied first in memory, and insert-frontmatter is appended last (after all anchor edits) before the single file write
#### Scenario: Write and stage occur once per file
- **WHEN** a file has multiple entries applied in memory
- **THEN** the file is written to disk exactly once and staged exactly once after all in-memory edits succeed
### Requirement: insert-frontmatter Re-Derives Freshness at Apply Time
The patch applier SHALL re-derive frontmatter state at apply time for
`insert-frontmatter` entries (which carry no `expected_sha256` because they have no
anchor) by re-reading the file and parsing its frontmatter. If the target key is
already present with the target value, the applier SHALL treat the operation as an
idempotent no-op (success, no write). If the target key is present with a different
value, the applier SHALL skip the entry with reason `frontmatter-key-conflict` and
SHALL NOT overwrite the existing value. If the key is absent, the applier SHALL
insert `key: value` (creating a `---` block if none exists) as the last in-memory
step.
#### Scenario: Key already present with target value — idempotent no-op
- **WHEN** the applier processes an insert-frontmatter entry and the file already contains `hygiene: frozen`
- **THEN** the applier treats the entry as a success and does not modify the file
#### Scenario: Key present with conflicting value — skip
- **WHEN** the applier processes an insert-frontmatter entry and the file already contains `hygiene: review`
- **THEN** the applier skips the entry with reason `frontmatter-key-conflict` and does not overwrite the existing value
#### Scenario: Key absent — insert
- **WHEN** the applier processes an insert-frontmatter entry and the file has no `hygiene` frontmatter key
- **THEN** the applier inserts the key-value pair (creating a `---` block if needed) as the last in-memory step before writing
### Requirement: Incompatible-Ops Detection Skips the File
The patch applier SHALL detect incompatible-op combinations on a per-file basis
before performing any edits. If `move-to-archive` co-occurs with any other op on the
same file, or if any anchor ranges overlap, the applier SHALL skip the entire file
with reason `incompatible-ops-on-file` and SHALL recommend re-analysis. The applier
SHALL NOT attempt to compose or sequence incompatible ops in v1.
#### Scenario: move-to-archive with another op — skip
- **WHEN** a file has both a move-to-archive entry and a replace-text entry
- **THEN** the applier skips the entire file with reason `incompatible-ops-on-file` and records a re-analysis recommendation
#### Scenario: Overlapping anchors — skip
- **WHEN** two entries on the same file have anchor ranges that overlap (e.g., lines 5–15 and lines 10–20)
- **THEN** the applier skips the entire file with reason `incompatible-ops-on-file`
#### Scenario: Non-conflicting entries on a file proceed normally
- **WHEN** a file has two entries with non-overlapping anchors and no move-to-archive
- **THEN** the applier applies both entries via the descending-anchor transaction
### Requirement: Safety-Tier Gating Before Any Mutation
- **THEN** the skill auto-creates a WIP checkpoint commit of the user's work, then produces exactly one cleanup commit — two commits total, cleanup still exactly one
#### Scenario: All-skipped produces zero commits
- **WHEN** all entries are guard-skipped (every file changed since check)
- **THEN** the skill produces zero commits and reports all files as skipped with re-analysis recommended
#### Scenario: A lifecycle delete is not double-staged
- **WHEN** the applier stages a `delete` via `git rm`
- **THEN** the skill does not separately call `git add` or any other staging command on the removed path
### Requirement: Applier Applies Delete and Extract-Then-Delete Under the Tier Matrix
The patch applier SHALL support two new op kinds, `delete` and
`extract-then-delete`. For both, immediately before applying, it SHALL
re-run `git ls-files <path>` and a dirty check against that specific path —
never trusting the cached report tier or rule claim — and SHALL apply the
tier matrix from the `lifecycle-deletion` spec to decide whether the entry
may proceed as `auto` or must be treated as `confirm` (already gated
upstream by the clean skill). `delete` SHALL perform a `git rm` (recursive
for a directory-rule aggregate entry) staged into the run's single hygiene
commit. `extract-then-delete` SHALL first complete its generative extraction
step (repo-durable via the existing live-read Sonnet distillation path
writing into an ADR/CLAUDE.md/docs target, or cross-repo via
`/os-vault:write`) and SHALL only perform the `git rm` once extraction has
succeeded; both steps SHALL land in the same single hygiene commit, or, on
extraction failure, neither SHALL be applied for that entry (skip, not a
run-level hard failure, unless the failure matches an existing hard-failure
trigger).
#### Scenario: delete performs a true git rm at apply time
- **WHEN** the applier applies a `delete` entry
- **THEN** it re-verifies tracked/clean status via `git ls-files` and a dirty check, then performs a `git rm` staged into the single hygiene commit
#### Scenario: extract-then-delete only deletes after extraction succeeds
- **WHEN** the applier applies an `extract-then-delete` entry
- **THEN** it completes the extraction write (ADR/CLAUDE.md/docs or vault) first, and performs the `git rm` only after that write succeeds
#### Scenario: A failed extraction skips the delete for that entry
- **WHEN** the extraction step of an `extract-then-delete` entry fails
- **THEN** the delete is not applied for that entry, the entry is reported as skipped, and the run is not treated as a hard failure unless the failure matches an existing hard-failure trigger (applier exit 2, write error)
#### Scenario: Directory-rule aggregate delete removes the whole directory
- **WHEN** the applier applies a `delete` entry whose path is a directory-rule aggregate entry
- **THEN** it performs a recursive `git rm` removing the entire matched directory in one operation staged into the single hygiene commit
- **THEN** the skill stops and reports the validation error without modifying anything
#### Scenario: Zero in-scope entries — no-op report
- **WHEN** all entries are filtered out by scope/category
- **THEN** the skill reports zero in-scope entries and exits without modifying the tree or creating a commit
#### Scenario: Full pipeline succeeds
- **WHEN** the report contains valid entries and the user approves all confirm entries
- **THEN** the skill applies deterministic entries via the applier, dispatches generative entries to Sonnet, stages precisely, commits once, stamps last_clean, and surfaces the commit SHA
### Requirement: Generative Distillation via Live-Read Sonnet Subagent
For approved generative entries, the clean skill SHALL confirm the file still exists
(a preceding `move-to-archive` on the same file would have removed it). It SHALL read
the **live** file contents at dispatch time (not from the report cache) to guarantee
freshness, because generative entries carry no `expected_sha256`. It SHALL dispatch
to a Sonnet subagent pointing at `workflows/distill.md` (LOOP-GUARD — the subagent
reads that workflow, not SKILL.md, to avoid recursion). The subagent SHALL return
new prose (or new-primary + archived-section for split). The skill SHALL write and
stage the result. A file with both a generative entry and a deterministic entry SHALL
be treated as `incompatible-ops-on-file` and skipped.
#### Scenario: File missing at dispatch time — skip
- **WHEN** a generative entry targets a file that no longer exists (e.g., moved by a prior step)
- **THEN** the skill skips the generative entry and reports it as skipped
#### Scenario: Live read ensures freshness
- **WHEN** the skill dispatches a generative entry to Sonnet
- **THEN** it reads the file's current bytes immediately before dispatch, not from the cached report span
#### Scenario: Generative + deterministic on same file — incompatible
- **WHEN** a file has both a generative entry and a deterministic entry
- **THEN** the applier (or skill) skips the file with reason incompatible-ops-on-file and recommends re-analysis