147 lines
8.0 KiB
Markdown
147 lines
8.0 KiB
Markdown
|
|
# Spec: doc-clean (delta)
|
||
|
|
|
||
|
|
## MODIFIED Requirements
|
||
|
|
|
||
|
|
### Requirement: Safety-Tier Gating Before Any Mutation
|
||
|
|
|
||
|
|
The clean skill SHALL partition report entries by safety tier (read from the
|
||
|
|
report — never recomputed) into `auto` entries (applied without prompt) and
|
||
|
|
`confirm` entries (escalated before any mutation). It SHALL present all
|
||
|
|
`confirm`-tier entries as a single batch-confirm list showing path, category,
|
||
|
|
op, token count, and rationale with per-entry opt-out, visually
|
||
|
|
distinguishing irreversible `delete-range`, `delete`, and
|
||
|
|
`extract-then-delete` entries from reversible entries. The approved set
|
||
|
|
SHALL be all `auto` entries plus any user-approved `confirm` entries. The
|
||
|
|
gate SHALL run identically under `sweep` — the `/os-doc-hygiene:sweep`
|
||
|
|
convenience path SHALL NOT bypass invariant #7. Regardless of the tier
|
||
|
|
recorded in the report, any `delete` or `extract-then-delete` entry SHALL be
|
||
|
|
re-verified at apply time per the `lifecycle-deletion` tier matrix (tracked
|
||
|
|
+ clean required for auto) before being applied without a prompt.
|
||
|
|
|
||
|
|
#### Scenario: auto entries apply without prompt
|
||
|
|
|
||
|
|
- **WHEN** the report contains only auto-tier entries (move-to-archive, insert-frontmatter, replace-text, dedupe)
|
||
|
|
- **THEN** the clean skill applies them without presenting a confirm prompt
|
||
|
|
|
||
|
|
#### Scenario: confirm entries escalate before any mutation
|
||
|
|
|
||
|
|
- **WHEN** the report contains confirm-tier entries (delete-range, delete, extract-then-delete, or any generative op)
|
||
|
|
- **THEN** the clean skill presents a batch-confirm list before any file is modified, and applies only user-approved entries
|
||
|
|
|
||
|
|
#### Scenario: per-entry opt-out is respected
|
||
|
|
|
||
|
|
- **WHEN** the user approves some confirm entries and opts out of others
|
||
|
|
- **THEN** the skill applies the approved entries and skips the opted-out entries
|
||
|
|
|
||
|
|
#### Scenario: sweep does not bypass the gate
|
||
|
|
|
||
|
|
- **WHEN** the user runs /os-doc-hygiene:sweep and the report contains confirm-tier entries
|
||
|
|
- **THEN** the confirm gate runs identically to a standalone /os-doc-hygiene:clean
|
||
|
|
|
||
|
|
#### Scenario: A report-tier auto delete is downgraded if runtime state has changed
|
||
|
|
|
||
|
|
- **WHEN** a `delete` entry was tiered `auto` in the report but the applier's runtime check finds the file is now dirty or untracked
|
||
|
|
- **THEN** the applier does not apply it silently; it is skipped and reported for re-analysis, never trusted from the cached tier
|
||
|
|
|
||
|
|
### Requirement: Git-Safe Single Commit
|
||
|
|
|
||
|
|
The clean skill SHALL produce exactly one git commit per run. Before any
|
||
|
|
mutation it SHALL resolve the project root via `StateStore`, run
|
||
|
|
`git status --porcelain`, and if the tree is dirty, SHALL automatically
|
||
|
|
create a WIP checkpoint commit of the user's work before proceeding, so the
|
||
|
|
cleanup commit remains exactly one. The cleanup commit SHALL be created via
|
||
|
|
`git-context commit-apply --message-stdin` with a generated message
|
||
|
|
summarizing auto/confirmed/skipped counts and op breakdown, including
|
||
|
|
lifecycle delete/extract-then-delete counts when present. Staging SHALL be
|
||
|
|
precise: for non-move, non-delete ops the skill calls `git add
|
||
|
|
<staged_paths>` from the applier result; for `move-to-archive` the applier
|
||
|
|
calls `git mv` (staging both sides) and the skill SHALL NOT `git add` the
|
||
|
|
destination path again; for `delete` and the delete half of
|
||
|
|
`extract-then-delete` the applier calls `git rm` (staging the removal
|
||
|
|
itself) and the skill SHALL NOT separately stage the removed path. The skill
|
||
|
|
SHALL NEVER use `git add -A` or `git add .`. If a hard failure occurs
|
||
|
|
(applier exit 2, `git mv`/`git rm` fail, or write error), the skill SHALL
|
||
|
|
roll back via `git restore`/`reset` to the pre-run baseline and abort with a
|
||
|
|
structured error. Partial success (some file batches guard-skipped) SHALL
|
||
|
|
NOT trigger rollback — the skill SHALL commit what applied and report
|
||
|
|
skipped files. Untracked candidate docs SHALL be skipped and reported
|
||
|
|
(tracked-files-only) except where a lifecycle rule explicitly escalates an
|
||
|
|
untracked delete to confirm and the user approves it. `last_clean` SHALL be
|
||
|
|
stamped to the commit instant, not the run-start instant.
|
||
|
|
|
||
|
|
#### Scenario: Clean tree produces exactly one commit
|
||
|
|
|
||
|
|
- **WHEN** the user runs /os-doc-hygiene:clean with a clean git tree
|
||
|
|
- **THEN** the run produces exactly one git commit containing all applied edits, including any lifecycle deletes
|
||
|
|
|
||
|
|
#### Scenario: Dirty tree gets a WIP checkpoint then one cleanup commit
|
||
|
|
|
||
|
|
- **WHEN** the user runs /os-doc-hygiene:clean with unstaged changes in the working tree
|
||
|
|
- **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: Hard failure triggers rollback
|
||
|
|
|
||
|
|
- **WHEN** a write error, applier exit 2, or a `git rm` failure occurs mid-run
|
||
|
|
- **THEN** the skill rolls back to the pre-run baseline and aborts with a structured error; no partial commit is created
|
||
|
|
|
||
|
|
#### Scenario: Partial success commits what applied
|
||
|
|
|
||
|
|
- **WHEN** some file batches are guard-skipped (applier exit 1) and others succeed
|
||
|
|
- **THEN** the skill commits the applied edits and reports the skipped files, without rolling back the applied changes
|
||
|
|
|
||
|
|
#### Scenario: move-to-archive is not double-staged
|
||
|
|
|
||
|
|
- **WHEN** the applier stages a move-to-archive via git mv (both source and dest)
|
||
|
|
- **THEN** the skill does not call git add on the destination path again
|
||
|
|
|
||
|
|
#### 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
|
||
|
|
|
||
|
|
## ADDED Requirements
|
||
|
|
|
||
|
|
### 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
|