Write Executable Plan
Bite-sized plans for parallel agents. No placeholders. No vague steps.
When to use
- A PRD from
/plan featureexists but you need a step-by-step implementation plan - A
/brainstormdesign is approved and you want to skip the PRD layer - Multiple agents will execute the plan in parallel and need conflict-free task boundaries
- The work is complex enough that describing it to an agent loses fidelity
When NOT to use
- Trivial 1-file changes (just do them; no plan needed)
- Pure exploration (use
/brainstorminstead) - Bug investigation (use
/debuginstead) - The source PRD is vague or unapproved (get PRD approval before planning execution)
Phase 0: Bootstrap Gate
Read skills/_shared/bootstrap-gate.md and execute the gate check. If the gate is CLOSED, invoke skills/bootstrap/SKILL.md and wait for completion before proceeding. If the gate is OPEN, continue to Phase 1.
Phase 1: Source Selection
Accept ONE of the following inputs (in order of preference):
- PRD path —
docs/prd/YYYY-MM-DD-<feature>.mdproduced by/plan feature - Design spec path —
docs/specs/YYYY-MM-DD-<slug>-design.mdproduced by/brainstorm - Inline description — user provides a free-text description; prompt for elaboration via AUQ before proceeding
If no source is provided and $ARGUMENTS is empty, ask via AUQ:
AskUserQuestion({
questions: [{
question: "Which source should this plan be based on?",
header: "Plan Source",
options: [
{ label: "Existing PRD (Recommended)", description: "Point me to docs/prd/YYYY-MM-DD-<feature>.md — most precise decomposition." },
{ label: "Design spec from /brainstorm", description: "Point me to docs/specs/YYYY-MM-DD-<slug>-design.md — skips formal PRD." },
{ label: "Describe inline", description: "Paste or describe the feature — I will ask follow-up questions before planning." }
],
multiSelect: false
}]
})
Read the source file and extract:
- Feature title (for filename slug and plan header)
- Acceptance criteria or equivalent (drives Task decomposition)
- Explicit out-of-scope items (excludes from plan)
- File inventory if present (seeds the whole-plan Files block)
Phase 2: Decomposition
Break the source into Tasks. Each Task MUST satisfy all of these constraints:
- Single owner — one agent role (e.g.,
code-implementer,test-writer), not "the team" - Bounded file set — lists every file it creates, modifies, or tests; no file appears in two Tasks
- Independently testable — after Step 4 the task stands on its own; no cross-task dependencies in the test command
- 2-5 minute wall-clock estimate — if the estimate exceeds 5 minutes, split the Task; if two Tasks share a file, merge or re-scope
Before writing Tasks, output a brief decomposition plan in plain text:
## Decomposition (draft)
- Task 1: <title> — <owner> — ~<N> min — Files: <list>
- Task 2: <title> — <owner> — ~<N> min — Files: <list>
...
If any task's file set overlaps another, surface the conflict and resolve it before writing Steps.
Phase 3: Step Authoring (per Task)
Each Task gets exactly 5 steps in this order. All 5 steps are mandatory.
Step 1: Write the failing test
EARS seam: If the source PRD/spec carries an ## Acceptance Criteria (EARS) (or ## 3.A) section, apply the EARS→vitest 1:1 mapping below to emit per-clause test stubs. If no EARS section is present, fall back to manual derivation from prose.
EARS → vitest mapping (1:1)
| EARS pattern | vitest construct | example skeleton |
|---|---|---|
| Ubiquitous ("The S shall R.") | invariant it() — no setup branching | it('S shall R', () => { /* assert invariant */ }) |
| State-driven ("While P, the S shall R.") | describe() for state context, nested it() for assertion | describe('while P', () => { it('S shall R', () => { /* enter P; expect R */ }) }) |
| Event-driven ("When T, the S shall R.") | arrange/trigger/expect inside it() | it('when T, S shall R', () => { /* arrange; trigger T; expect R */ }) |
| Optional feature ("Where F, the S shall R.") | it.skipIf(!F) (vitest conditional) | it.skipIf(!F)('where F, S shall R', () => { /* expect R */ }) |
| Unwanted behaviour ("If C, then the S shall R.") | error-path it() with negative assertion or toThrow() | it('if C, then S shall R', () => { /* induce C; expect R */ }) |
Reference test exemplifying this pattern: tests/lib/wave-executor/persona-gate-hook.test.mjs (shipped at #481).
Follow .claude/rules/testing.md § "Test Quality — False-Positive Prevention": one meaningful assertion per it, behaviour not implementation, no branching, hardcoded expected values.
Provide:
- Exact file path (absolute from project root)
- Complete, runnable test code — no
// ..., no placeholders, no// implement later - One sentence explaining why this test verifies the intended behavior
The test MUST fail before Step 3 is applied. If the behavior already exists and cannot fail, pick a harder assertion or a new edge case.
Step 2: Run the test to confirm it fails
Provide:
- Exact command (e.g.,
npm test -- tests/unit/my-module.test.mjs) - Expected terminal output — the specific assertion failure line that proves the test exercises missing code (not a generic "FAIL" — copy the actual error shape)
Step 3: Implement the minimal code
Provide:
- Exact file paths labeled
Create:orModify: - Complete code — every function, every import, every export. No
// add appropriate logic, no// similar to above, no... - DRY constraint: reuse existing utilities; do not duplicate logic that already exists in the codebase
- YAGNI constraint: implement exactly what makes Step 2's test pass; no speculative features
Step 4: Run the test to verify it passes
Provide:
- Same command as Step 2 (copy verbatim)
- Expected terminal output showing the test suite PASS line with the test name visible
Step 5: Commit
Provide:
- Exact commit message in Conventional Commits format:
type(scope): subject - Scope: the primary module or directory affected by this Task
- Subject: imperative mood, max 72 characters, no period
- Files staged: only this Task's files (list them)
Phase 4: Placeholder Linter
Before writing the plan to disk, scan every Step in every Task for forbidden strings. Any match is a rejection — fix before writing.
Forbidden strings (case-insensitive):
| Pattern | Why forbidden |
|---|---|
TBD, TODO, FIXME, XXX | Deferred decisions defeat the plan's purpose |
add appropriate error handling | Vague — specify the exact error type and handling code |
add error handling | Same — write the catch block |
similar to Task N, same as above, like Task N | Forces the executor to cross-reference; defeats file-disjoint execution |
etc. | Incomplete enumeration — list every item |
[fill in], <placeholder>, <YOUR_VALUE> | Template leftovers — fill them in |
... inside a code block | Ellipsis in implementation code means incomplete code |
Prose ellipsis (e.g., "Phase 1... Phase 2...") is acceptable. Only code-block ellipsis is forbidden.
Any hit → surface to user via AUQ:
AskUserQuestion({
questions: [{
question: "The placeholder linter found forbidden strings in the draft plan:\n\n[list each hit with Task number, Step number, and matched text]\n\nHow do you want to proceed?",
header: "Placeholder Linter",
options: [
{ label: "Fix automatically (Recommended)", description: "I will resolve each hit by filling in the missing specifics before writing the plan." },
{ label: "Show me each hit interactively", description: "Walk me through each one so I can provide the missing detail." }