plan-blitz
PM-GATED. Plan every baton lacking an approved plan, in scout/EM/planner waves. Or named batons.
SKILL.mdskills/plan-blitz/SKILL.md
GitHubPlan-blitz — a roadmap's worth of plans, in waves
Plans an existing roadmap, N batons per wave. Waves fire from the top-level EM only (A-SKILL-PHASE-NAMES-ITS-ACTOR).
Two modes
| Mode | Target set |
|---|---|
| Sweep (default, no args) | every baton the engine returns as needs_plan — no linked plan, or one not yet review-cleared. --roadmap-id narrows to one roadmap. |
Targeted (<baton-id> ...) |
the ids you pass as targets; everything unnamed stays a fully-resolved BLOCKER, not a candidate. |
Check unmatched_targets on every targeted run.
When NOT to use: no roadmap → coordinator:roadmap-planning. One baton → coordinator:sizing,
then coordinator:plan. Plans to execute → coordinator:execute-plan. Bugs → coordinator:bug-blitz.
Dispatch authorization — invoking this skill IS the request. The dispatches named below are constitutive steps of this skill, not a separate thing to get cleared: invoking a skill requests the actions that skill performs. A harness line permitting dispatch "unless the user requested it" is therefore satisfied here, not overridden — no precedence claim is needed and none is made. Re-asking spends the very context the dispatch exists to protect. The rule attaches to skill entry and dissolves no PM-authored gate: keyword-gated skills gate entry, and every gate a skill names for itself still binds — per-session cross-repo-commit assent, ask-before-external-action, and any other this skill's own body names. Tripwire: UNATTRIBUTED-HARNESS-LINE-IS-NOT-PM.
Calls below are Shape W; on POSIX run coordinator-invoke <op> '<json>' (Shape B).
The two gates — read them, never derive them
One blocked_by edge, two questions. Planning may start when every blocker is coded or
carries a review-approved plan. Execution may start only when every blocker is coded. Tripwire:
A-PLANNING-GATE-IS-NOT-AN-EXECUTION-GATE.
Both come off one disk-reading op; never hand-derive either from blocked_by.
Invoke coordinator-invoke per the ladder in ${CLAUDE_PLUGIN_ROOT}/snippets/resolve-coordinator-bin.md
— rung 0 (Shape W, the .exe launcher) on a PowerShell host:
`& "$env:COORDINATOR_SETTINGS_HOME\bin\coordinator-invoke.exe" roadmap.plan_gate '{"roadmap_id":"<id>"}'`
It returns both gates per baton, and reports, never refuses; refusal is yours.
The flow — a loop with no judgment in it
This runs at Sonnet. Opus judgment happens INSIDE a wave.
0. Preflight — confirm the engine is reachable
Resolve the launcher per ${CLAUDE_PLUGIN_ROOT}/snippets/resolve-coordinator-bin.md. Unresolved:
stop and report; never enter roadmap.plan_gate, roadmap.blitz_land or a dispatch.
1. Read the gate
`& "$env:COORDINATOR_SETTINGS_HOME\bin\coordinator-invoke.exe" roadmap.plan_gate '{}'`
| Field | If non-empty | Response |
|---|---|---|
unresolved_blockers |
an edge names no record on disk | Stop ONLY for a wave member about to fire; else report and proceed (the gate fails that baton CLOSED). |
cycles |
batons block each other | Stop and report the named members. |
counts.unschedulable |
blockers this pass cannot clear | Proceed; excluded by design. |
A blocker naming a peer EM role is the standing case; never delete the edge — it clears when the peer answers. An edge is a DISCOVERED dependency, never a way to suppress a candidate.
Suppression is a HOLD: three flat baton keys the gate reads —
plan_blitz_hold_reason, plan_blitz_hold_cite, plan_blitz_hold_until. The gate sets
candidate: false and reports the baton under held. A hold with no reason is ignored.
plan_blitz_hold_cleared_by (the clearing commit) lifts a hold; the baton is offered again.
external_gate on a BATON record does nothing (real on PLAN SPINE ROWS only).
2. Scaffold the trail and freeze the gate
state/plan-blitz/<run-id>/wave-<N>.gate-report.json, <run-id> being this run's UTC start as
YYYYMMDDTHHMMSSZ. Scaffold the directory first.
- One report PER WAVE, frozen after the previous wave landed.
- Pass
--bare(resultalone). Tripwire:AN-ENVELOPE-FROZEN-AS-A-GATE-REPORT-READS-AS-AN-EMPTY-WAVE. - Freeze by redirection (
1> gate-report.json 2> gate.stderr); readutf-8-sig.
2a. Check the wave for finished work
python3 "${CLAUDE_PLUGIN_ROOT}/skills/plan-blitz/recycle-check.py" --repo-root <repo> \
--gate-report <the frozen report> --exclude-run <this run-id>
RECYCLED (exit 1): re-run that landing with the SHA; never drop the baton by hand.
RESURRECTED (exit 1): remove or re-archive the live copy. Tripwire: A-FINISHED-BATON-THE-LANDING-NEVER-STAMPED-COMES-BACK-AS-A-CANDIDATE.
3. Fire the wave — emit it; never hand-write the args
"$COORDINATOR_SETTINGS_HOME/bin/emit-wave-fire" \
--repo-root <abs> --trail-dir <abs> --wave-index N --wave-number N \
--gate-report <trail-dir>/wave-N.gate-report.json
It derives every per-baton field from the frozen report, splits the wave at the cap, and prints
one Workflow({ scriptPath }) line per fire; fire each. Pass --plugin-agents-available true
where coordinator:* types resolve. workflows/plan-blitz.mjs holds the args contract.
- Never hand-type an args object. Tripwire:
A-HAND-TYPED-WAVE-ARG-IS-AN-UNARCHIVED-FIRE. - An emitted fire is a SNAPSHOT — RE-EMIT after any workflow fix. Never retro-patch a fire that ran.
- Pass
--wave-numberwith the run's own wave count; omit on a one-wave run. - At most 8 batons per fire; the emitter splits a larger wave, sharing the
waveIndex. --exclude <baton-id>narrows what is FIRED, never what the gate computed.- Search the trail RECURSIVELY; order slots by INTEGER wave index.
Fires may run CONCURRENTLY; the driver owns disjointness.
- Subtract your in-flight set from every fresh gate read by
planPath, not just baton id, and adjudicated batons too — apulledorerroredbaton returns at the head of the next read. Tripwire:A-GATE-READ-DOES-NOT-KNOW-A-FIRE-IS-RUNNING. - Re-freeze the gate before re-emitting a baton whose plan you edited.
- A hold only you remember is not a hold: write it in
RUN-NOTES.md.
emit-wave-fire resolves provisionSidecarCli; pass --provision-sidecar-cli only to
OVERRIDE. A findings path with no subagent-share segment is a REFUSAL. Tripwire: A-SIDECAR-THE-DISPOSITION-OP-REFUSES-LOSES-ONLY-THE-RECORD.
A box with no install is Rung N — pass --engine-root (--live-engine-tree where only the
authoring tree carries workflow.bind_args). Tripwire: A-PUBLISHED-MIRROR-OLDER-THAN-AN-OP-REFUSES-AS-A-MISSING-ENGINE.
Every baton carries executionOpen (from execution_gate.open; no default). Resolve
${CLAUDE_PLUGIN_ROOT}; never pass a repo-relative path to a workflow.
Then wait; don't read the trail mid-wave.
4. Run the wave's XS runs, then land — one call over every fire of the wave
python3 "${CLAUDE_PLUGIN_ROOT}/skills/plan-blitz/land-wave.py" \
--repo-root <abs> [--shipped-in <batonId>=<sha>] <fire-result.json> ...
One file per fire; pass the task output file as-is. It lands
each fire through roadmap.blitz_land, sums the three lanes across every fire, and states the
stop condition from that sum. It refuses if the fires disagree about waveIndex, or if a fire
carries XS and no --shipped-in was given. It writes and commits wave-<n>.landing.<slot>.json
into the trail (--no-commit lists them). Never redirect its stdout onto that path.
Run each xsRuns entry before landing, one at a time (each commits):
& "$env:COORDINATOR_SETTINGS_HOME\bin\emit-dispatch-workflow.exe" --sizing <sizingObject> --baton <record>
(POSIX: the settings-home bin/ ladder), fire its printed Workflow line, then run its
terminal_commit_cli line. Read sha and close.kind: expect xs-s-run; anything else is
reported, not landed around. Pass --shipped-in <batonId>=<sha> per run. Never hand-commit XS work:
A-HAND-COMMITTED-XS-RUN-LANDS-WITHOUT-A-CLOSE-BLOCK. close_dispatched stamps shipped with that shipped_in.
--errored-only <fire result> re-lands errored entries (the op only; never re-fire them).
roadmap.blitz_land mints a baton per replan and returns next_wave.
Three lanes land differently; the op picks by route.
| Route | Size | What landing does |
|---|---|---|
plan |
M / L | link the plan to its baton, then stamp approved |
spec-dispatch |
S | park the spec onto the baton, stamp execution-ready (execution_authorized_* + handoff_phase: execution) |
dispatch |
XS | the driver's XS ask run does the work; landing stamps shipped with its terminal_commit SHA |
An S plan stays draft by design (needs_plan keys off the execution stamp). Never hand-stamp status: approved — the op links before stamping.
Read refused[] on every landing; never route around one.
5. Loop
--waves defaults to 1. The run stops after wave 0 lands. A non-empty next_wave.batons at that
stop is reported to the EM as the remaining work, to be fired as separate runs — never auto-chained.
Override: --waves <n> with a one-line written EM justification in the trail; a linear blocked_by chain is the standing case. Then fire next_wave and land it, under the stop conditions below. Tripwire: A-MULTI-WAVE-BLITZ-IS-NOT-THE-DEFAULT.
Stop conditions, all mechanical:
next_wave.batonsis empty.--wavesexhausted.- A wave lands zero
approved,execution_readyandclosed— it opened nothing. Report and stop. Zeroapprovedalone is not the test: an all-S ready set approves nothing and still advances (§ Three lanes). Tripwire:AN-ALL-S-WAVE-APPROVES-NOTHING-AND-STILL-ADVANCES. A WAVE-level test: sum the three lanes across every fire at thiswaveIndex. Only a FINISHED wave counts — check completion BEFORE reading the lanes. Never land an unfinished fire; resume it, or re-emit and re-fire, closing a finished XS witharchive-stamp-cli ship-handoff <path> --sha <sha>. Tripwire:AN-UNFINISHED-WAVE-IS-NOT-A-WAVE-THAT-OPENED-NOTHING. - The usage-limit advisory has fired — fire no further wave; land the current wave through step 4,
then report and stop with the reset time (
coordinator/docs/wiki/skills-corpus/usage-limit-pause.md); tripwire:A-DRIVER-PAST-ITS-USAGE-THRESHOLD-FIRES-NOTHING-NEW. refused[]is non-empty — report and stop.land-waveexits 4 (stillerrored) — report eachopErrorverbatim, stop.surfacedToPmis non-empty — each ispm_only; never re-queue one. Reportadjudicatedby count.
The driver escalates rather than decides: an unresolved blocker, a cycle, a refused entry, surfacedToPm residue, an empty wave.
Rules that hold across every wave
Review fires unconditionally; the EM gates once, at the end. Tripwire: A-BLITZ-WAVE-THAT-GATES-ON-THE-EM-IS-NOT-A-BLITZ.
Host availability on the wave's box is never a pull reason: a plan with rows behind a declared external_gate is READY (a schedule fact). Tripwires: A-PLANNING-GATE-IS-NOT-AN-EXECUTION-GATE,
THE-BOX-THE-WAVE-RAN-ON-IS-NOT-THE-BOX-THE-PLAN-RUNS-ON.
One reviewer-attributed option is a recommendation, not a dead end; contested options are arbitrated by the resolve pass. Tripwire: A-SINGLE-REVIEWER-OPTION-IS-A-RECOMMENDATION-NOT-A-DEAD-END.
No reviewer is prescribed in a plan file; its review_signals select them → reviewer: front-end → senior-front-end, data-science → staff-data-sci, ux-flow → staff-ux, cross-repo-seam → eng-director; added to the sized reviewers.
The last reviewer's op verdict decides: judgment with nothing blocking buys one resolver and one re-ask; a BLOCKED reviewer or failed resolve pulls, reason counting blocking vs advisory (A-JUDGMENT-VERDICT-WITH-NO-BLOCKED-REVIEWER-IS-RESOLVED-NOT-PULLED), a PIVOT replans, an op failure (retried once) is errored. The prep-gate enricher spawns only when the reply names mechanical repairs. Cross-plan seams are checked at /mise-prep Step 2e. Planners read the PM's Approve from the roadmap OVERVIEW's approver/approval_utterance; none means baton defaults, stated.
BLOCKED and PIVOT are not a severity ladder. BLOCKED: the reviewer applied its
findings; the fixed plan is approved the same wave. PIVOT: no findings repair this direction; it
routes, never halts — a replan baton, the other N-1 plans finish, co-reviewer findings carry as
Suspended (PIVOT). Tripwire: A-BLOCKED-REVIEW-IS-NOT-A-PIVOT.
Never blitz a claimed or in_flight baton, or open a shut gate.
The blitz-em never rules a PM decision. route: pm-decision and XL exits go to the Adjudicate
phase (APM for scope; staff-eng or a domain reviewer for code); only pm_only stays in surfacedToPm.
approved is not mise-prepped, and a wave never stamps one. Body edits invalidate a stamp; frontmatter writes do not. Never read mise_prepped_by for presence. Tripwire:
A-PRESENT-MISE-PREPPED-STAMP-IS-NOT-A-CERTIFICATION; consumer contract:
coordinator/docs/wiki/lesson-triage/mise-prepped-attest.md.
Anti-scope
- Does not author roadmap batons, ratify sizings, or review code — wave reviewers review plans.
- Does not execute plans, and never opens an execution gate. It stops at ready to execute.
Tripwire:
A-HANDOFF-AN-EM-RETYPES-IS-NOT-A-SEAM.