| name | write-plan |
| description | Sequence a locked spec into dependency-ordered, wave-grouped tasks. TRIGGER when: user says 'build the plan' or 'sequence this'; a spec needs slicing into parallel-safe waves. |
Write Plan
Slice a locked spec and its Structure Outline into atomic, dependency-ordered, wave-grouped tasks — create plan.md with its ## Waves. Sequencing only: the WHAT and the design live in the spec; this skill decides order and parallelism.
When to use
YES: meta/specs/NNN-slug/spec.md is locked with a populated Structure Outline (from tech-design), and the work needs sequencing into waves.
NO: requirements or UX unclear (use product-interview); approach, data shapes, or file layout undecided (use tech-design); plan already has waves and you want to execute (use execute-plan).
Protocol
Input
- Spec folder:
meta/specs/NNN-slug/ (or a path to either file in it).
Step 1 — Gate: spec locked, outline present
Four checks, all machine-checkable:
grep -nE '^[[:space:]]*-[[:space:]]*\*\*Status:\*\*[[:space:]]*open' spec.md # any hit ⇒ blocked
grep -n '\[NEEDS CLARIFICATION:' spec.md # any hit ⇒ blocked
grep -n '^### Files touched' spec.md # no hit ⇒ outline missing
grep -nE '^[[:space:]]*-[[:space:]]*\*\*Status:\*\*[[:space:]]*Draft' spec.md # + outline present ⇒ stale outline (reopened after design)
(Lock-gate forms are load-bearing — defined under Gate anchors in skills/product-interview/SKILL.md. POSIX ERE only.)
If a lock grep hits, stop and name the open decisions/clarifications — route to product-interview (product/UX) or tech-design (technical). If the outline check misses, route to tech-design. If both ### Files touched and the Draft grep hit, the WHAT was reopened after design and the outline is stale — route to tech-design (its resume re-runs from Step 2, scoped to the change). (A fresh Draft has no outline, so Draft + an outline means reopen — capitalized, header-only; case-split rule 2.)
Trivial-skip exception: for a trivial change with one obvious implementation, offer via AskUserQuestion — "Skip tech-design — trivial" / "Route to tech-design". On Skip, put - **Outline:** skipped (trivial; user-approved) as plan.md's last header line — reviewer criterion S2 then auto-passes.
Step 2 — Read context
Read spec.md (Requirements, Acceptance Criteria, Decisions, Structure Outline, Constraints). Skim the affected files only to size and split the work — don't redo the design; it's already in the spec. On the trivial-skip path there is no Structure Outline: read Requirements + ACs and skim the affected files instead.
Step 3 — Identify work items
Break the structure outline (on trivial-skip: the spec's Requirements/ACs) into atomic tasks for the locked design. Each task:
- One commit's worth of work — completable in a single focused session.
- Self-contained — names the file(s) it touches, what to change, and why.
- Verifiable — you can tell when it's done.
- Cited — names the
AC-NNN-XX it satisfies and any D-NNN-XX it honors, by full id (satisfies AC-NNN-02; per D-NNN-09).
Coverage check both directions: an AC no task satisfies is a gap (add a task or flag it); a task citing nothing is scope creep (justify it against the spec or cut it). Human-gated ACs still get implementation tasks where code must exist for the later live check — note human-gated: verified post-ship on the citation. On the trivial-skip path, cite AC-NNN-XX only — no technical decision blocks exist; don't invent citations to satisfy M6.
Interface changes pull in their call-sites. When a task changes a shared interface — a new or changed prop, parameter, exported signature, or return shape — grep for its direct consumers (importers, callers, renderers) and add them to that task's file-set, or to a Must land together with: / Depends on: task if another wave owns them. Map direct consumers only — a runtime scope-expansion round-trip covers the rare deeper chain. Name the interface change in the task's description (not just the feature it serves) — the S3 review can only check a delta the task states, so one hidden behind feature-level prose ("update the settings panel") slips past.
Step 4 — Order by dependency + wave grouping
For each task, determine: what must exist first, which files it touches, whether it can run in parallel.
Wave assignment rules:
- No dependencies + no file overlap → same wave; mark each such task
[P] (parallelizable).
- Depends on another task → later wave.
- Same file modified by multiple tasks → different waves (serialize), or
Must land together with: in one subagent's hands.
- Maximum 5 tasks per wave — it matches execute-plan's parallel-dispatch budget.
- Prefer fewer, fatter waves over many single-task waves.
Step 5 — Create plan.md
If plan.md already exists:
- Shipped or running:
Status: FROZEN → stop (new work = new spec). Base SHA: set or any - [x] task → route to execute-plan; never re-sequence a running plan.
- Establish the spec delta:
- Baseline commit: set
LAST_PLAN=$(git log -1 --format=%H -- meta/specs/NNN-slug/plan.md). An empty LAST_PLAN takes Recreate below.
- Spec changes: run
git diff "$LAST_PLAN" -- meta/specs/NNN-slug/spec.md; working-tree edits are included.
- Reviewed baseline: grep plan.md for
^- Plan review:. No hit means Step 6 uses Full.
- Classify and sync:
- Plan-neutral — formatting, spelling, or explanatory prose that changes no requirement, UX behavior, AC, decision, constraint, outline claim, path, or cited id: keep plan.md unchanged; Step 6 uses No review.
- Targeted sync — the affected tasks and their required edits are clear: update only those task bodies, files, citations, dependencies, or wave placements; preserve stable task ids. Step 6 chooses Delta or Full from the resulting plan diff.
- Recreate — the affected tasks cannot be identified confidently, or most of the design changed:
AskUserQuestion: "Recreate from the current spec (Recommended)" / "Stop". Replace plan.md from the current Steps 3–4 output using the canonical template below; Step 6 uses Full.
Keep targeted-sync and recreated-plan edits uncommitted until Step 6 finishes, so an interrupted session still compares against the last reviewed plan commit.
After handling an existing plan, point the user at its diff and jump to Step 6; never run the fresh-plan write below.
For a fresh plan, print a terse digest in chat, shaped exactly:
**Plan drafted — NNN-slug:** meta/specs/NNN-slug/plan.md · <T> tasks, <W> waves
- Wave 1 — <N> tasks — <what lands>
- Wave 2 — <N> tasks — <what lands>
- … one line per wave
The digest is a signpost, not the artifact — review or redirect on plan.md itself. Then create meta/specs/NNN-slug/plan.md from the canonical template below, and commit:
git add meta/specs/NNN-slug/plan.md && git commit -m "plan(NNN-slug): waves created"
(Use the slug from Input — execute-plan's resumability and promotion commits key on the same plan(<slug>): prefix; an unsubstituted placeholder breaks that chain.)
CANONICAL plan.md TEMPLATE (the only copy — execute-plan fills its own sections and cites the Plan anchors here):
# PLAN: [Feature name]
- **Created:** [YYYY-MM-DD]
- **Base SHA:** — <!-- set by execute-plan before Wave 1; final-review diff range -->
- **Status:** Building <!-- Building → FROZEN [date]. FROZEN marks the plan shipped; new work starts a new spec. -->
<!-- No Spec:/path back-links — the folder pairs the files. Conditional: the trivial-skip path (Step 1) appends ONE more header line here marking the outline as skipped — exact form in Step 1; reviewer criterion S2 keys on it. -->
## Waves
<!-- WRITTEN BY write-plan. ≤5 tasks/wave. [P] = parallelizable within its wave. Every task cites AC-NNN-XX / D-NNN-XX by full id. Task line format is load-bearing: checkbox + bold ID/title on one line (execute-plan flips the `- [ ] ` checkbox to `- [x] ` on each task line); details in indented sub-bullets. -->
### Wave 1: [short description]
- [ ] **T1 [P]: [short title]** — satisfies AC-NNN-01; per D-NNN-07
- [what to change, which files, why]
- [ ] **T2 [P]: [short title]** — satisfies AC-NNN-03
- [what to change, which files, why]
### Wave 2: [short description]
- [ ] **T3: [short title]** — satisfies AC-NNN-02; per D-NNN-09
- [what to change, which files, why]
- Depends on: T1
## Execution Log
<!-- APPENDED BY execute-plan; append-only. Discoveries logged at the moment found, one per line, with a type tag STARTING the line — the tags are line-anchored grep targets for the ship gate (see Plan anchors in skills/write-plan/SKILL.md). Types: Implementation = detail delta, stays here; AC-affecting = contradicts an AC or locked decision, STOP, user-gated promotion, entry must carry the promotion marker when resolved; Future = opportunity/limitation, triaged once at the ship gate (takes the next `F-NNN-XX` — see Plan anchors). Plus one parent-written tag: auto-resolved = a grounded decision execute-plan's Autonomy gate took without asking — not a discovery, not count-compared, surfaced only in execute-plan's Step 7 report. Guidance and prose here must NEVER start a line with a bracketed tag. -->
<!-- APPENDED BY execute-plan.
Review unit: one block per one-wave or eligible two-wave unit — range, seats, per-AC evidence, findings, Drift, fix coverage, deferred entries.
Pending unit: starts with the load-bearing marker; execute-plan replaces it with the completed block.
Final review: records Integration/Full mode, per-AC PASS/FAIL evidence, and verification outcome for the ship gate.
Deferred anchor: .
-->
<!-- RUN BY execute-plan after the docs sync, before freezing. -->
[ ] Final review complete: every seat merged, verified, confirmed P0/P1 resolved
[ ] Comments swept and durable docs synced
[ ] Every AC-affecting entry carries the promotion marker (count-compare check — see Plan anchors)
[ ] Every Future entry triaged: hole-in-shipped-thing → spec "Deferred"; new feature → surfaced to user to place (must not die silently); noise → dies here
[ ] Every deferred finding triaged: real limitation → spec "Deferred"; P3 noise → dies here
[ ] Completion record written to spec (criteria results + post-ship verification + deferred + review filter stats), spec Status → Shipped
[ ] Plan Status → FROZEN [date]
Use stable task IDs (T1, T2, ...) — they survive edits and reordering; reference dependencies by ID, never position.
Plan anchors (load-bearing — exact forms matter)
Defined here beside the canonical template; written and grepped by execute-plan — the Base SHA form is grepped by the spec Revising rule's consumers (product-interview, tech-design). POSIX ERE only.
^- \[AC-affecting\] # execution-log entry, tag starts the line
^- \[Implementation\] # "
^- \[Future\] # "
^- \[auto-resolved\]: # execution-log entry, tag starts the line; Autonomy-gate record, not count-compared
^- P[0-9]+ \[deferred\]: # wave-review deferred finding
^- Review pending: Waves # unpaid review-unit marker; resolve before new implementation or final review
promoted-to-spec # promotion marker, ALWAYS lowercase + hyphenated; case-insensitive grep
^- Plan review: # reviewed-baseline marker; no hit ⇒ Step 6 Full
^- \*\*Base SHA:\*\* — # hit, or no plan.md at all ⇒ planning stage: spec decisions/ACs edit in place (spec template's Revising rule); no hit with plan.md present ⇒ build started: supersede, never edit
Rules: tags start the line — narrative prose and template guidance must never start a line with a bracketed tag, and must never contain the hyphenated token promoted-to-spec outside a real marker (the hyphen exists so natural prose — "promoted to spec" — can never collide with the anchor; write the unhyphenated phrase freely). Ship-gate promotion check is a count-compare: number of ^- \[AC-affecting\] lines must equal the number of (case-insensitive) promotion-marker hits in the Execution Log — consumers scope BOTH counts to that section via sed -n '/^## Execution Log/,/^## Wave Reviews/p'.
F ids — every [deferred] and [Future] entry takes one at append time, whoever writes it:
- Format:
F-NNN-XX — NNN = this spec's folder number; XX = a zero-padded two-digit counter, ONE counter per plan across both entry types.
- Next
XX: highest F-NNN- id anywhere in plan.md + 1 — one scan of the file, so a resumed session recomputes it correctly.
- Placement: the id follows the anchor tag —
- P2 [deferred]: F-NNN-04 — ... / - [Future] F-NNN-09 — ... — never before it, so the anchor forms above stay exact.
- Cite from: spec, plan, and triage files only — never code. The id is the stable handle for dispatches, triage passes, and promotion records.
Step 6 — Plan review
Choose the review mode before dispatch. A fresh plan always uses Full.
| Mode | Entry condition | Review |
|---|
| No review | A reviewed baseline exists; Step 5 classified the spec delta as plan-neutral; and plan.md did not change | Skip Step 6 |
| Delta | A reviewed baseline exists; the sync changed 1–2 existing tasks; added or removed no task; changed no file-set, wave, dependency, schema, signature, return shape, component boundary, or shared interface; and the affected scope is clear | Run M1–M6 once over the full plan, plus one semantic reviewer on the spec and plan deltas |
| Full | No reviewed baseline; fresh or recreated plan; more than 2 tasks changed; any Delta condition fails; or scope/risk is unclear | Run the existing full review below |
Delta review:
- Inputs: both diffs, the changed tasks and ACs/decisions, and their affected Structure Outline excerpts.
- Checks: apply S1–S2 to that surface, then answer: "Does every changed task reflect the changed spec, with no missed task, consumer, or ordering effect?"
- Escalation: any shared-interface, cross-wave, or wider-scope effect switches the review to Full.
- Findings: route them through the same lanes below.
Full review: run three layers — a deterministic mechanical pass, a size-scaled semantic panel, then clean-room corroboration. Reviewers are reviewer agents (agents/reviewer.md) against plan.md + spec.md; instruct each to tag every finding with the exact criterion ID — routing keys on the M*/S* prefix; untagged defaults to semantic.
Criteria
Mechanical (deterministic — provable from plan.md structure):
- M1: Every task names its file(s).
- M2: Every
Depends on: points to an existing task in an earlier wave.
- M3: No file touched by multiple tasks in the same wave (unless
Must land together with:).
- M4: No wave exceeds 5 tasks.
- M5: No task appears in multiple waves.
- M6: Every task cites ≥1
AC-NNN-XX or D-NNN-XX, and every cited id exists in the spec.
Semantic (judgment):
- S1: Every
AC-NNN-XX in the spec is satisfied by ≥1 task (or explicitly marked post-ship-only).
- S2: The spec's Structure Outline covers every schema, signature, and component a task references. EXEMPT when the plan header carries the
- **Outline:** skipped line — then S2 auto-passes.
- S3: Every task that changes a shared interface (prop, parameter, exported signature, or return shape) has that interface's direct consumers in some task's file-set. Direct consumers only — a deeper chain is an acceptable runtime scope-expansion.
Full-review panel size — count total ACs (grep -cE '^- \*\*AC-[0-9]+' spec.md):
| ACs | Semantic reviewers (S1–S3) |
|---|
| ≤6 | 1 |
| 7–12 | 2 |
| ≥13 | 3 |
- Partition — split the ACs evenly across the semantic reviewers; each gets its AC subset + the full plan + the Structure Outline, and checks S1 for its subset, S2 for the schemas/signatures its tasks reference, and S3 for interface changes in those tasks (grepping the codebase for consumers).
- Every task owned by exactly one reviewer — a task citing only a
D-NNN-XX (no AC-NNN-XX) maps to no AC subset, so assign it to a reviewer too; otherwise its S2/S3 go unchecked.
- Mechanical pass runs once — scaling adds nothing to provable checks: at ≤6 ACs the lone reviewer also carries M1–M6 (one agent total); at ≥7 ACs give M1–M6 their own reviewer so they aren't re-run across the panel.
- +1 cross-wave reviewer when the plan has ≥4 waves, chartered: "Read the waves as a sequence. Find ordering bugs the mechanical checks miss — chiefly a task needing another task's output that declares no
Depends on:, and whether each wave's prerequisites exist by the time it runs. AC coverage is the other reviewers' job. An empty result is valid."
Parent merges + dedups findings (by criterion + task/AC, keep max severity), then routes by lane.
Mechanical lane (deterministic — no confidence bar):
| Finding | Action |
|---|
| None across all layers | Proceed silently. |
Mechanical (M*) | Auto-edit the plan to fix; re-run the mechanical pass once. Still failing → AskUserQuestion: "Edit manually and re-review" / "Accept defect with risk note" / "Abort". |
Semantic lane — corroborate, then apply the autonomy gate. Only if the selected review produced ≥1 semantic finding, run the triage skill on the semantic findings (mechanical findings skip triage — nothing to corroborate); it returns per finding a consider/skip verdict + adjusted_confidence. The parent then applies its own verdict:
- PROCEED —
consider, adjusted_confidence ≥ 0.80, and the fix is grounded + reversible (the spec, outline, or codebase says what the missing task or file must be — e.g. an S1 task for an uncovered AC, or an S3 consumer file the call-site grep proves): close the gap by looping Steps 3–5, then re-run the selected review mode once; a Delta fix that crosses a Delta boundary escalates to Full. Log - Plan review: auto-closed coverage gap (S1/S3) — <what> (conf 0.NN) under ## Waves.
- ASK — any other
consider (triage already judged it real and material): it's below 0.80, or its fix would invent scope, reopen design (any S2 outline gap → route to tech-design), or contradict a locked D-NNN-XX. AskUserQuestion: "Add tasks to close the gap" (same Steps 3–5 loop) / "Flag the AC back to the spec owner" / "Accept and note as known gap" / "Abort". Recommended: add tasks — a coverage gap is missing work, not noise.
- DROP — triage
skip only (false positive or trivial): log - Plan review: noted (skipped by triage) — <finding> under ## Waves — visible, not raised.
Cap the mechanical auto-fix and each semantic gap-loop — the PROCEED loop and the ASK lane's "Add tasks" loop alike — at 1 retry each; a second failure of any escalates via the AskUserQuestion in its lane above (on the ASK lane's second failure, re-ask without the "Add tasks" option). The 0.80 bar matches execute-plan's Autonomy gate — one threshold across the pipeline.
Before Next step:
- Mark the baseline: append
- Plan review: <Full | Delta> — <N findings: disposition | 0 findings — clean> under ## Waves.
- Commit an existing-plan sync: include the Step-5 sync edits and use
git commit -m "plan(NNN-slug): sync to spec changes".
- Commit any other review: include its fixes and annotations with
git commit -m "plan(NNN-slug): review fixes".
No review skips the marker and commit because plan.md did not change.
Step 7 — Offer to fold the spec/plan commits into one
This phase left several commits (spec, tech design, waves created, review fixes). Fold them now — once execute-plan sets Base SHA, rewriting history breaks it.
- Run = contiguous commits back from HEAD whose subject carries
NNN-slug/spec-NNN, or that touch only meta/; BASE = the commit before the run. Show the list. Skip the offer if any run commit is a merge or already on @{upstream}.
git diff --name-only BASE..HEAD —
- All under
meta/specs/NNN-slug/ → AskUserQuestion: "Fold into one spec+plan(NNN-slug): commit (recommended)" / "Keep separate". Fold: git reset --soft BASE && git commit -m "spec+plan(NNN-slug): <feature> — discovery, tech design, waves".
- Anything else (list it) →
AskUserQuestion: "Fold all" / "Fold only meta/specs/, rest as its own commit" / "Keep separate". Split: git reset BASE, then git add meta/specs/NNN-slug/ && git commit, then git add -A && git commit.
Next step
Route via AskUserQuestion to execute-plan (default — waves are ready to execute) or "Stop here".
Rules
- Sequencing only. No approach selection, no structure design, no feasibility checks — if those look undone, route to
tech-design rather than improvising.
- No review/test/verification tasks. Those belong to execute-plan's review-unit, final-review, and fix-verify gates.
- Out-of-scope lives in the spec. Don't restate it in the plan; cite the spec section if a boundary matters to sequencing.
- Wave rules bend only where they say they bend. Wave rule 3's
Must land together with: is the one escape; never split a task across waves. A task that can't be parallelized gets its own wave.