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 <cr-ref>, deletes that CR's whole transient artifact set: the plan pair
(<cr-ref>.plan.md + <cr-ref>.log.jsonl) plus its transient CR-level planning briefs
(<cr-ref>.design.md, <cr-ref>.operations.md, <cr-ref>.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.
<cr-ref>.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 causes 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-<gid> → Asana, local-<slug> → 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 == <cr-ref> must exist in the project ledger (--ledger). The sweep keys on the structured
distills field, never a <cr-ref> 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 <cr-ref>.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
node "<skill>/scripts/retire-plans.mts" \
--root .agents/plans \
--ledger .agents/specs/<project>/ledger \
--retire github-34,asana-7 [--dry-run]
--ledger <dir> 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 (
<cr-ref>.plan.md, <cr-ref>.log.jsonl,
<cr-ref>.design.md, <cr-ref>.operations.md, <cr-ref>.evidence.md) only for a <cr-ref> that
is cleared (--retire) and present on disk (<cr-ref>.plan.md exists) and either
distilled (a strategy with distills == <cr-ref> in --ledger) or has no combat log to
distill (no <cr-ref>.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
<cr-ref> 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 <cr-ref>, if
its <cr-ref>.log.jsonl exists, first confirm a strategy entry with distills == <cr-ref>
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 <cr-ref>.plan.md, <cr-ref>.log.jsonl, <cr-ref>.design.md, <cr-ref>.operations.md, and
<cr-ref>.evidence.md if present, touching nothing else — and only if <cr-ref>.plan.md is
present in the first place (a brief alone never triggers deletion).
1---2name: plan-retirement3description: 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.4---56# SDD Plan Retirement78The Doctrine loop's **last retro step** (`sdd:doctrine-loop`; the provenance shape is9`sdd:combat-log-governance`). Because plans are **tracked** (committed with10the work, not gitignored), a retired plan leaves the tree by a deliberate **tracked deletion** —11never a gitignore side effect. This skill carries a self-contained `.mts` sweep that, for each12cleared `<cr-ref>`, deletes that CR's whole **transient artifact set**: the plan pair13(`<cr-ref>.plan.md` + `<cr-ref>.log.jsonl`) plus its **transient CR-level planning briefs**14(`<cr-ref>.design.md`, `<cr-ref>.operations.md`, `<cr-ref>.evidence.md`) from `.agents/plans` —15so a retired CR leaves no orphan behind.1617## The transient briefs ride along, never gate1819`design.md` / `operations.md` / `evidence.md` are optional, cr-ref-scoped planning briefs that20share the plan's lifetime. They retire alongside the plan pair but do not participate in the21retirement decision. Two boundaries:2223- **They do not widen the distilled gate.** The gate keys on the combat log's presence only — a24 cr-ref with briefs but no `log.jsonl` still retires without a distilling `strategy` entry. A25 brief owes no distillation (its content was consumed by the mission itself).26- **They do not anchor presence.** `<cr-ref>.plan.md` is the sole presence signal. A brief27 without a `plan.md` is left untouched.2829## Distill and delete are decoupled3031- **Distill (early).** At `→ implemented`, the Scanner reads the concluded combat log and distills32 recurring `cause`s into the ledger's `strategy` lines (`sdd:doctrine-loop`).33- **Delete (late).** This sweep runs as a **separate, later** step, gated on source = `done`/merged34 **and** the plan distilled. Never delete an un-distilled plan (the retro never ran).3536## The clearance boundary — split by verifiability3738The two gating signals split by what the sweep can check itself:3940- **source = `done`/merged** — the **caller's judgment**: query the source natively (`github-NN` → GH41 issue, `asana-<gid>` → Asana, `local-<slug>` → the local store); needs network/`gh`. The caller42 passes the source-cleared set via `--retire`. `sdd:doctrine-loop`'s Scanner is the standard43 caller: during its pass it cross-checks each brief's own `todos-all-done` against `source-closed`44 and only passes through a cr-ref where **both** agree terminal — a disagreement (source closed45 but the brief's own todos are not all done, or the reverse) is held back and surfaced as a46 flagged finding for a human, never passed through on source alone.47- **distilled** — **verified mechanically by the sweep**: a `strategy` entry with `distills ==48 <cr-ref>` must exist in the project ledger (`--ledger`). The sweep keys on the structured49 `distills` field, **never** a `<cr-ref>` that appears only in a strategy's `evidence`50 cross-references, and an **unratified** distilling entry still counts51 (`sdd:combat-log-governance`). Its absence is **fail-closed** — but only when a combat log52 **exists**: a cr-ref whose `<cr-ref>.log.jsonl` was never written (a non-gated mission — hand-run,53 chore-tracked, investigation — runs no gate cycle and emits no correction) has **nothing to54 distill**, so it retires on clearance + presence alone. The fail-closed leaves an existing,55 undistilled log's plan intact so its distillation can still be drafted.5657Leaving the distilled half to the caller once let a plan + combat log be deleted before any58distillation existed (the evidence the distill was meant to preserve). Because the check is local,59the sweep does it itself. Only the genuinely non-local judgment (source status) stays with the caller.6061## Run the sweep6263```bash64node "<skill>/scripts/retire-plans.mts" \65 --root .agents/plans \66 --ledger .agents/specs/<project>/ledger \67 --retire github-34,asana-7 [--dry-run]68```6970- **`--ledger <dir>`** points at the project's ledger directory (the `ledger/` sibling of the root71 `spec.md`). **Required for any deletion** — omit it (or an unreadable dir) and the sweep72 fail-closes: nothing is deleted (the no-log branch only applies once a ledger is present to consult).73- Deletes the transient artifact set (`<cr-ref>.plan.md`, `<cr-ref>.log.jsonl`,74 `<cr-ref>.design.md`, `<cr-ref>.operations.md`, `<cr-ref>.evidence.md`) only for a `<cr-ref>` that75 is cleared (`--retire`) **and** present on disk (`<cr-ref>.plan.md` exists) **and** either76 distilled (a `strategy` with `distills == <cr-ref>` in `--ledger`) **or** has no combat log to77 distill (no `<cr-ref>.log.jsonl` on disk). Each brief is deleted only if present; an absent one is78 a no-op, same as the missing log half.79- **Fail-closed** — a plan not named in `--retire`, or whose combat log **exists** but has no80 distilling ledger entry, is never touched (and neither are its briefs).81- **Idempotent** — a cleared `<cr-ref>` with no plan on disk (already retired, or an open CR the82 caller declined to clear) is a no-op, even if a brief for it exists; the sweep is safe to re-run.83- `--dry-run` prints the planned deletions without touching the tree.8485When `node` is absent, an agent performs the same decision by hand: for each cleared `<cr-ref>`, if86its `<cr-ref>.log.jsonl` **exists**, **first confirm a `strategy` entry with `distills == <cr-ref>`87exists in the project ledger** (not a mere `evidence` mention; unratified still counts) — if none,88skip it. A cr-ref with **no** `log.jsonl` has nothing to distill and needs no such entry. Only then89delete `<cr-ref>.plan.md`, `<cr-ref>.log.jsonl`, `<cr-ref>.design.md`, `<cr-ref>.operations.md`, and90`<cr-ref>.evidence.md` if present, touching nothing else — and only if `<cr-ref>.plan.md` is91present in the first place (a brief alone never triggers deletion).