swarm-plan
Decompose an approved spec into a dependency-ordered swarm plan — one task per component, each with an explicit write_set. Produces `.claude/state/swarm/<slug>.json` with tasks + waves. The wave sched
SKILL.md.claude/skills/swarm-plan/SKILL.md
GitHubswarm-plan — Phase 5.5: decompose for parallel execution
Invoked after /approve-direction and before /tdd on any spec that has ≥ project.json → swarm.min_tasks_worth_swarming independent components worth parallelizing (default 1, so swarm is the default code-generation route). A spec whose dependency graph exposes no independent component falls through to /tdd solo.
Post-§18 amendment. Beyond the canonical .claude/state/swarm/<slug>.json plan, swarm-plan ALSO emits a runtime sub-track overlay at .claude/state/swarm/<slug>.jsonl (JSONL) when the chosen workflow track contains a selector node whose swarm-implementation alternate is chosen. The overlay's single line is a transient Track record with selectable: false, track_id: "swarm-runtime-<slug>", and nodes[] enumerating the swarm-worker dispatches as can_parallel: true peers. The harness reloads runtime overlays from .claude/state/swarm/*.jsonl at the same time it reads .claude/workflows.jsonl, so the runtime Track set is the union. The overlay is deleted by /archive along with the rest of the workflow's swarm state.
Prereqs
.claude/state/spec_approvals/<slug>.approvalexists (spec is human-approved).docs/specs/<slug>.mdexists and passes/spec-lint.docs/scout/<slug>.mdexists (component → file mapping is its job).
If any prereq is missing, stop and surface what's needed.
Output contract
.claude/state/swarm/<slug>.json:
{
"slug": "<slug>",
"spec": "docs/specs/<slug>.md",
"created_at": <epoch>,
"status": "planned",
"tasks": [
{
"id": "T-001",
"title": "<what the task does — one line>",
"component": "<component id from C4 Component diagram>",
"acs": ["AC-001", "AC-002"],
"write_set": ["src/foo/bar.py", "tests/foo/test_bar.py"],
"read_set": ["src/common/http.py"],
"depends_on": [],
"execution": "worker-safe"
}
],
"waves": null
}
You produce tasks[]. The validator (validate.mjs) computes waves[] deterministically via Kahn-with-disjointness.
Steps
Read upstream: the spec, the scout report, the approval token. If an older
.claude/state/swarm/<slug>.jsonexists, confirm with the user whether to overwrite (replan) or abort.Extract inputs from the spec:
- Components: every
Component(id, …)in the C4 Component diagram (there may be multiple C4 Component diagrams, one per container). - ACs: every row in the Acceptance criteria table.
- Dependency edges: every
A --> Bin the spec's dependency-graph fence. - Behavior ↔ component mapping: for each AC, the sequence diagram it references names participants; treat each non-actor, non-external participant as a component this AC touches.
- Components: every
Get file mapping from the scout report: for each component id, the files that back it. If the spec introduces greenfield components not in the scout, propose new file paths and flag them under a "new_paths" note in your plan summary — they will be accepted by the boundary guard when declared in
write_set.Construct tasks — one per component (per
swarm.granularity: component):id: T-001, T-002, … in stable order.title: one-line imperative description.component: the C4 component id.acs: every AC whose sequence names this component.write_set: union of (component files) + (test files covering those ACs). Every file must be explicit; no globs.read_set: files this task will consult but not modify. Advisory; not enforced.depends_on: for each component B such that this component depends on B (per the dependency graph), include the task id of the task owning B.execution(REQUIRED — D5 of swarm-mode-first-run-hardening): classify whether this task is safe to hand to a worker.worker-safe= pure + fully-specified by the spec (no design decision left, does not touch live shipped code that gate-A/the running harness depends on, depends on no not-yet-shipped API).needs-main-context= design-laden, touches live shipped code, or depends on an API a sibling task in this plan introduces.validate.mjsrejects a plan task whoseexecutionis missing or outside{worker-safe, needs-main-context}. At/swarm-dispatch, onlyworker-safetasks are spawned as workers;needs-main-contexttasks are completed in main context. Classifying this UP FRONT (not mid-build) is what keeps the gate-B plan honest — the first real swarm run pulled tasks back to main context mid-wave precisely because the plan assumed every task was worker-safe.
Merge overlapping tasks where forced:
- If two tasks share any file AND have no
depends_onrelationship, either introduce adepends_onedge (making them sequential across waves) or merge them into one task. Merging is preferred when they're on the same component.
- If two tasks share any file AND have no
Validate the plan:
node .claude/skills/swarm-plan/validate.mjs docs/specs/<slug>.md .claude/state/swarm/<slug>.jsonThe validator checks: required fields, depends_on references resolve, DAG is acyclic, and assigns
waves[]. If validation fails, it prints a precise error — fix the plan and re-run.Surface the plan to the user as a table:
Swarm plan for <slug> — <N> tasks across <M> waves wave 1: T-001 webhook-worker [AC-001, AC-002] 3 files worker-safe T-003 backoff-policy [AC-004] 2 files worker-safe wave 2: T-002 webhook-retry [AC-003] 2 files needs-main-context (needs T-001)The
executioncolumn is part of the gate-B review surface — the human approving the swarm at/approve-swarmsees which tasks will run as workers vs in main context.Tell the user: "Swarm planned at
.claude/state/swarm/<slug>.json. Review it, then run/approve-swarm <slug>. After approval, run/swarm-dispatch <slug>."
Constraints
- Never dispatch from this skill. Planning and execution are separated by a human consent gate (
/approve-swarm). - Every file in a
write_setmust be a concrete path, not a glob. The boundary guard does string-level membership checks. - The
validate.mjsscript is the source of truth for wave assignment. Do not hand-writewaves[]. - Greenfield files are allowed in
write_seteven if they don't exist yet — the guard checks declared ownership, not disk presence. - If validation keeps failing, the problem is usually that two tasks share a file with no dependency. Either merge or introduce a dependency edge.