-
Confirm nothing else is in-progress — run
python3 "${CLAUDE_PLUGIN_ROOT}/scripts/cairn_next.py" for the mechanical
active/workable picture rather than eyeballing the ROADMAP. If it reports
an active milestone, get explicit user sign-off to plan ahead anyway
(planning ahead is fine; it just needs saying).
-
Investigate first. Read the relevant code and DECISIONS.md. For
scopes touching more than a couple of files, fan out Explore subagents
([S]-tagged descriptions) with specific focuses; require file:line
citations. Draft scope, tasks,
and the list of genuinely open decisions internally.
The acceptance criteria are drafted here to their final wording, not at
step 4 — step 3's criteria audit reads the bytes step 4 will write, and an
audit over a rougher draft certifies text that never ships.
Exploring a source corpus. A scope that points at a corpus of
maybe-relevant sources on the references/sources/ shelf is a supply-push
case (tracking-rules "Exploring prospective sources"):
investigation may triage them for prospective oracles or methods rather than dismissing them as uncited, emitting ROADMAP candidate rows for what it finds and a survey synthesis note only when the triage outlives this planning, never a per-source page.
Collision check (mandatory). Sweep the ROADMAP (all statuses), the
archive, and DECISIONS.md for overlap with what the user described.
Sweep DECISIONS.md per the tracking-rules bounded DECISIONS.md read:
scan the ### D- headings, read every matched entry whole before
surfacing it, and back-reference each match by its own D-0NN id so a
later entry superseding it surfaces too.
Quote a collision verbatim from the full entry, never from the heading.
Prior state is surfaced at the question gate, never silently obeyed or
silently overridden:
candidate row → the normal promotion path: absorb the row, note the
lineage.
planned milestone → no duplicates: amend it, supersede its plan, or
confirm the scopes are distinct and cross-reference.
in-progress milestone → fold in via the amendment protocol
(/milestone-implement step 6) or plan separately with Depends on:.
done (archived) → it shipped; tell the user (it may already do what
they want); otherwise plan an extension referencing the old ID.
dropped milestone or D-entry rejection → quote the prior rationale
verbatim ("D-014 rejected X because Y — does that still hold?"). To
proceed: supersede, don't ignore — append a superseding D-entry
first. Never plan against a standing rejection without superseding it;
never refuse merely because a rejection exists.
Harvest recent lessons (before the gate). Review cairn/LESSONS.md
(read at session start) and surface any lessons bearing on this scope —
build quirks, testing tricks, gotchas that should shape the tasks,
acceptance bar, or a gate question. Empty file → nothing to surface. This
is intake, not obedience: a lesson informs the plan, it doesn't dictate it.
-
Question gate (one batched AskUserQuestion round, 2–5 questions, each
with a recommendation): scope boundary, sequencing, acceptance bar, and
any collision dispositions.
Acceptance chips (tracking-rules): a question resting on a produced
conclusion — subagent findings, a collision verdict — shows that
conclusion's substance verbatim above the chip. Every proposed scope cut must state where
the remainder goes — never "M12 covers A and B" alone, but "M12 covers
A and B; C becomes M13 (planned now, depends on M12); D becomes a
candidate row; E sounds unwanted — drop entirely?".
Criteria audit (runs before the questions are composed). A plan
author's own read of its own criteria is the check measured to fail — M114
authored criteria that were unsatisfiable as written and one that mandated
an IP4 violation, costing gated amendments and review returns, and each was
discoverable here. So the step-2 criteria go to a fresh-context [O]
reader that authored none of them, which asks two mechanical questions of
each: what state of the world satisfies this exactly as written, and
does any IP or D-entry make that state unreachable. It reads the wording
step 4 will write, never a paraphrase of it. Dispose of what it returns at
this gate, never silently: a finding with one clear right answer is fixed
and the fix reported in chat, and a finding you could reasonably decide
either way becomes one of this round's questions, within the three-marker
cap. The instrument is a reader and never a check — satisfiability and
IP-conflict are judgments about prose meaning, which D-059's retirement
precedent says to route to the mechanism that works rather than mechanize.
Release-shaped tripwire. Release timing is user-declared, never agent-proposed (tracking-rules; D-050) — so a release-framed scope stops here for an explicit window declaration.
It fires when the scope in hand would ship a version: a release, a CRAN or
registry submission, a "prepare/consolidate for vX.Y.Z". On a hit, the gate
asks the user to declare the window in so many words, and
the default answer is no — absent a declaration the work lands as a candidate row, never as a planned milestone, and never at Priority: high.
A declared window is the user saying to queue this release now; the
dependency list going green is not, since it says only that the bundle is
complete.
Two things the tripwire does not touch.
Work about release tooling — a release-walk slot, release docs — is ordinary milestone work, not a release.
And a milestone the user has already declared a window for plans normally.
When a release milestone exists but its window is not open, its home is
blocked — park it there rather than planning around it.
-
Solidify autonomously (no further questions). Create one or more
milestone files from
${CLAUDE_PLUGIN_ROOT}/skills/shared/templates/milestone.md — when the
sizing tripwires fire, the answer is multiple milestones in one run, not
shrink-to-fit and discard. For each file:
-
Acceptance criteria verifiable with evidence; never vibes. Criteria
that cite a formula or reference value must name their source
(citekey (p. N) — see the primary-sources rule). Write the wording
step 3's audit read; a criterion the gate changed goes back through the
audit's two questions before it is written, and the change is reported.
-
Acceptance criteria set the test scope for the milestone (see "What
gets a test" in tracking-rules): name the behavior that must be tested.
-
Out: items name where the excluded work lives instead.
-
Tasks ≤ one working session each, ordered by dependency.
-
Coverage map (owner: plan): after the criteria and tasks are
written, author the Coverage section — one line per acceptance
criterion mapping it to the task(s) that satisfy it, by positional
number (AC1 → T1, T3; AC/Task numbers run top-to-bottom in their
sections). Every criterion maps to ≥1 task; a criterion no task
satisfies is a planning gap — add the missing task or cut the
criterion, never ship an unmapped criterion. Review reads this map to
fence evidence.
-
Principles touched (header slot): fill it with the DESIGN.md
IPn/GPn ids this milestone adds, changes, or works under — or —
if none. It is the authoritative source cairn_impact and
cairn_validate read for principle impact; an accurate slot beats an
incidental (IPn) in prose (M17).
-
Driving RR (header slot): a milestone planned from an RR that
carries Binding criteria sets the slot to RR<NN>, ingests each
criterion verbatim into the AC block (the binding criteria check
string-compares them; departures go in the shown "Deviations from
RR" table), and copies the RR's numeric projections beside the
criteria with their stated tolerances — an unstated tolerance is
strict, so review's shortfall chip fires on any gap. Otherwise —.
-
Open questions that hit an RB tripwire (see tracking-rules) are
tagged inline on the affected task or criterion with the canonical
token — (RB tripwire: no-oracle | irreversible-api | ip-touching) —
so implement inherits them.
-
Write only the plan-owned sections per the tracking-rules
section-ownership table; leave the others to their owners.
-
Draft against the budget, not against the gate. The template's
per-section budgets are the drafting target; count while writing rather
than discovering the overrun at cairn_validate time, when the only
remedy left is compression:
python3 "${CLAUDE_PLUGIN_ROOT}/scripts/cairn_budget.py" cairn/milestones/M<NN>-<slug>.md
It prints the plan-owned body against the cap plus the section
breakdown, and exits 1 if any axis is over. The budgets are guidance —
what fails a gate is still only the cap.
Deferred chunks not yet plannable get candidate ROADMAP rows, not files.
-
Remainder ledger (conservation check). Before committing, enumerate
every distinct thing the user originally asked for and its disposition:
in this milestone / planned as M / candidate row / dropped at the
user's explicit request. Nothing may be silently absent. Deferral is
NEVER recorded as a decision not to do something — D-entries are for
genuine rejections with rationale; postponement lives in the ROADMAP.
Include the ledger in the plan summary presented to the user.
-
Commit atomically. Durable-record preview first (tracking-rules):
show each drafted durable text — the milestone files' plan-owned
sections, any D-entry, new ROADMAP rows — verbatim in chat before the
commit. Then update ROADMAP rows (planned / candidate) and
commit files + rows together, directly to main, no branch, no PR
(docs-only carve-out): plan M<NN>[, M<NN>…]: <title>; push. A session
dying mid-plan must not leave a half-planned ghost.
-
Routing chip (AskUserQuestion), composed from what was just planned
(chip rules per tracking-rules) — e.g.:
- Start implementing M (the proximal one) →
/milestone-implement
(recommended)
- Plan another milestone →
/milestone-plan
- Stop here