--- name: plan-retirement description: "Partial Skill: invoke by name only — the SDD Doctrine loop's last retro step — the gated, idempotent tracked deletion of a retired mission plan. Invoked by the doctrine-loop Scanner, not user-triggered; the clearance contract lives in the body + README." user-invocable: false metadata: internal: true --- # SDD Plan Retirement The Doctrine loop's **last retro step** (`sdd:doctrine-loop`; the provenance shape is `sdd:combat-log-governance`). Because plans are **tracked** (committed with the work, not gitignored), a retired plan leaves the tree by a deliberate **tracked deletion** — never a gitignore side effect. This skill carries a self-contained `.mts` sweep that, for each cleared ``, deletes that CR's whole **transient artifact set**: the plan pair (`.plan.md` + `.log.jsonl`) plus its **transient CR-level planning briefs** (`.design.md`, `.operations.md`, `.evidence.md`) from `.agents/plans` — so a retired CR leaves no orphan behind. ## The transient briefs ride along, never gate `design.md` / `operations.md` / `evidence.md` are optional, cr-ref-scoped planning briefs that share the plan's lifetime. They retire alongside the plan pair but do not participate in the retirement decision. Two boundaries: - **They do not widen the distilled gate.** The gate keys on the combat log's presence only — a cr-ref with briefs but no `log.jsonl` still retires without a distilling `strategy` entry. A brief owes no distillation (its content was consumed by the mission itself). - **They do not anchor presence.** `.plan.md` is the sole presence signal. A brief without a `plan.md` is left untouched. ## Distill and delete are decoupled - **Distill (early).** At `→ implemented`, the Scanner reads the concluded combat log and distills recurring `cause`s into the ledger's `strategy` lines (`sdd:doctrine-loop`). - **Delete (late).** This sweep runs as a **separate, later** step, gated on source = `done`/merged **and** the plan distilled. Never delete an un-distilled plan (the retro never ran). ## The clearance boundary — split by verifiability The two gating signals split by what the sweep can check itself: - **source = `done`/merged** — the **caller's judgment**: query the source natively (`github-NN` → GH issue, `asana-` → Asana, `local-` → the local store); needs network/`gh`. The caller passes the source-cleared set via `--retire`. `sdd:doctrine-loop`'s Scanner is the standard caller: during its pass it cross-checks each brief's own `todos-all-done` against `source-closed` and only passes through a cr-ref where **both** agree terminal — a disagreement (source closed but the brief's own todos are not all done, or the reverse) is held back and surfaced as a flagged finding for a human, never passed through on source alone. - **distilled** — **verified mechanically by the sweep**: a `strategy` entry with `distills == ` must exist in the project ledger (`--ledger`). The sweep keys on the structured `distills` field, **never** a `` that appears only in a strategy's `evidence` cross-references, and an **unratified** distilling entry still counts (`sdd:combat-log-governance`). Its absence is **fail-closed** — but only when a combat log **exists**: a cr-ref whose `.log.jsonl` was never written (a non-gated mission — hand-run, chore-tracked, investigation — runs no gate cycle and emits no correction) has **nothing to distill**, so it retires on clearance + presence alone. The fail-closed leaves an existing, undistilled log's plan intact so its distillation can still be drafted. Leaving the distilled half to the caller once let a plan + combat log be deleted before any distillation existed (the evidence the distill was meant to preserve). Because the check is local, the sweep does it itself. Only the genuinely non-local judgment (source status) stays with the caller. ## Run the sweep ```bash node "/scripts/retire-plans.mjs" \ --root .agents/plans \ --ledger .agents/specs//ledger \ --retire github-34,asana-7 [--dry-run] ``` - **`--ledger `** points at the project's ledger directory (the `ledger/` sibling of the root `spec.md`). **Required for any deletion** — omit it (or an unreadable dir) and the sweep fail-closes: nothing is deleted (the no-log branch only applies once a ledger is present to consult). - Deletes the transient artifact set (`.plan.md`, `.log.jsonl`, `.design.md`, `.operations.md`, `.evidence.md`) only for a `` that is cleared (`--retire`) **and** present on disk (`.plan.md` exists) **and** either distilled (a `strategy` with `distills == ` in `--ledger`) **or** has no combat log to distill (no `.log.jsonl` on disk). Each brief is deleted only if present; an absent one is a no-op, same as the missing log half. - **Fail-closed** — a plan not named in `--retire`, or whose combat log **exists** but has no distilling ledger entry, is never touched (and neither are its briefs). - **Idempotent** — a cleared `` with no plan on disk (already retired, or an open CR the caller declined to clear) is a no-op, even if a brief for it exists; the sweep is safe to re-run. - `--dry-run` prints the planned deletions without touching the tree. When `node` is absent, an agent performs the same decision by hand: for each cleared ``, if its `.log.jsonl` **exists**, **first confirm a `strategy` entry with `distills == ` exists in the project ledger** (not a mere `evidence` mention; unratified still counts) — if none, skip it. A cr-ref with **no** `log.jsonl` has nothing to distill and needs no such entry. Only then delete `.plan.md`, `.log.jsonl`, `.design.md`, `.operations.md`, and `.evidence.md` if present, touching nothing else — and only if `.plan.md` is present in the first place (a brief alone never triggers deletion).