cc-os/plugins/os-doc-hygiene/openspec/changes/lifecycle-aware-doc-hygiene/specs/doc-clean/spec.md

8.0 KiB

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