Ship Playbook
Inputs
$request: The feature prompt — what to plan, build, review, and ship. May carry overrides like
harness=codex or golive=yes to seed intake. For a fix round, pass the prior findings as the prompt.
To RESUME from an existing, already-reviewed DAG plan, point at it (e.g. resume .ulpi/plans/<name>.md
or --plan .ulpi/plans/<name>.json): the run skips planning + plan-review and starts at build.
Goal
Turn one prompt into shipped-quality work the same way every time, by running the delivery playbook
as a single runnable Workflow: a grounded DAG plan, an optional founder plan review, an implementation
built task-by-task across the DAG layers (each task written and reviewed by the executor the user
chose), an optional plan-vs-implementation review, and an optional go-live audit — then it RETURNS the
verified findings as feedback. It runs ONE pass and does not loop on its own; if findings remain, the
user decides whether to run a fix round.
The 14 steps map to the Workflow phases: step 3 → Plan · steps 4–9 → Plan review (optional, bounded
loop) · steps 10–11 → Build (per-task review optional) · step 12 → Impl review (optional,
plan-vs-implementation) · step 14 → Verify (dedup + adversarial verify → feedback) · step 13 → Audit
(only if goLive AND build+impl verified-clean). The skill does the intake (dependency check + prompt
- gate questions) up front. Each review gate runs at the depth the user chose (skip / native / codex /
kiro); there is no automatic recursion — the workflow returns its findings and stops.
Phase 1 — Intake
Step 0 — Decide the MODE: new run vs resume (do this FIRST)
The skill runs in one of two modes — detect which before anything else:
- NEW RUN (default — the user gives a feature prompt). Run the full intake below (dependency check +
gate questions + scope grounding), create a FRESH status file, and launch with full
args.
- RESUME a previous run (the user says "resume", "continue the build", gives a
wf_… runId, or points
at an existing .ulpi/workflows/<id>.json). Do NOT re-run intake and do NOT overwrite the status file.
Instead, from the repo root run node <skill-dir>/helpers/wf-status.mjs --resume [<runId>] — it reads
the run's status file and prints the exact Workflow({ scriptPath, args }) to relaunch, already carrying
planPath (skip re-planning), checkpointResume:true (skip tasks already done), and the same
statusFile. Launch that verbatim. The durable checkpoint makes resume session-independent (no
resumeFromRunId); the build rebuilds only what's left. (If --resume warns that launchArgs is
missing — a pre-v1.7.0 run — supply the missing validate/hardRules; run --write first if there's no
status file yet.) Then skip to Phase 3 reporting when it returns.
The rest of Phase 1 is the NEW-RUN path.
Step 1 — Dependency check & setup (do this FIRST)
Before anything else, tell the user what would make this skill do its BEST work, what's already here,
and what's missing — with copy-paste install commands. ship-playbook composes other skills and routes
to specialist agents; the more of these are installed, the better the result.
How to detect — THREE states, not two. A skill is only loaded into your available-skills list (and
an agent into your subagent_type options) at Claude STARTUP, so "installed on disk" ≠ "usable this
session". Classify each item:
- Ready — in your loaded available-skills list /
subagent_type options. Usable now. Don't list it.
- Installed, needs restart — NOT in the loaded list, but present on disk. Check disk
symlink-aware (skills register as symlinks into a shared store, e.g.
.claude/skills/map-project → ../../.agents/skills/map-project): use ls -laL .claude/skills/<name>
or test -e .claude/skills/<name> (follows symlinks) — NOT find -type d (silently misses symlinks).
Report these as "installed — RESTART Claude to load", with NO install command (it's already installed).
- Missing — not in the loaded list and not on disk. Show the full install command.
The loaded list is the source of truth for what's USABLE now; the symlink-aware disk check only
distinguishes "installed but not loaded (restart)" from "truly missing", so an installed-but-not-yet-
loaded skill is never mislabeled plain "missing". Sanity check: if you'd report an obviously-present
skill (one you just used) as missing, your method is broken. Disk/PATH probing for the external
kiro-cli binary is separate (see below).
Detect the project's stack(s), then check (against those loaded lists) what's present vs missing across:
- Composed skills —
plan-to-task-list-with-dag, plan-founder-review, go-live-audit,
map-project / map-project-monorepo.
- Specialist agents for this stack — the engineer + reviewer pairs the build would assign. The
reviewer name is the FULL engineer name +
-reviewer (NOT an abbreviation): e.g.
nextjs-senior-engineer + nextjs-senior-engineer-reviewer; go-senior-engineer +
go-senior-engineer-reviewer; laravel-senior-engineer + laravel-senior-engineer-reviewer. Use the
exact registered names — never shorten to nextjs-reviewer/go-reviewer. Missing ones force
general-purpose, which is lower quality.
- Stack skills —
/nextjs, /laravel, /rust, … for the detected stack.
- Optional harness tooling (only if the user might pick codex/kiro): the codex plugin
(
codex:codex-rescue); for kiro, the CLI plus the kiro-review skill (reviewing) and
hand-over-to-kiro skill (building). Detecting the kiro CLI — two gotchas:
- The binary is named
kiro-cli, NOT kiro (bare kiro is usually the Kiro IDE symlink, not the
CLI). Check kiro-cli.
- It's commonly in
~/.local/bin, which the sandboxed Bash PATH may NOT include — so command -v kiro-cli can come back empty even when it's installed. Detect with a location-tolerant check, e.g.
command -v kiro-cli || ls ~/.local/bin/kiro-cli ~/.kiro/bin/kiro-cli /usr/local/bin/kiro-cli 2>/dev/null,
and confirm it runs (kiro-cli --version). Docs: https://kiro.dev/docs/cli.
Present TWO separate tables — one for skills, one for agents — listing only items that are NOT
ready (i.e. "missing" OR "installed, needs restart"). Do NOT list ready items at all (no "present /
ready" section). Each row shows the item, its state, and the action:
- state = installed, needs restart → action: "Restart Claude to load" (NO install command — it's
already on disk).
- state = missing → action: the full, copy-paste install command:
- skills →
npx skills add https://github.com/ulpi-io/skills --skill <name> (composed skills, stack
skills, kiro helpers kiro-review/hand-over-to-kiro; kiro-cli itself → its docs link
https://kiro.dev/docs/cli);
- agents →
npx agentshq add ulpi-io/agents@<agent-name>, using the EXACT registered name per row —
e.g. …@nextjs-senior-engineer and …@nextjs-senior-engineer-reviewer (the reviewer is the full
engineer name + -reviewer, never …@nextjs-reviewer).
If a table has no not-ready items, omit it entirely. Then offer two choices with AskUserQuestion:
- Continue now with what's installed (missing specialists fall back to
general-purpose; missing
harness options simply won't be offered).
- I'll install them — restart : the user installs the listed items, then RESTARTS Claude (newly
installed skills/agents are only loaded at startup) and re-runs
/ship-playbook. Tell them to come
back and re-invoke after restarting.
Record what's available — it feeds availableAgents and constrains which intake options you offer.
Step 2 — The workflow gate questions (ask these FIRST)
The prompt is $request. Ask the SEVEN gate questions (across two AskUserQuestion calls — up to 4
each), in EXECUTION order so they read like the run, unless $request already pins them.
RESUME mode. If $request points at an existing reviewed plan (a .ulpi/plans/<name> path, or the
user asks to resume), the run starts at BUILD: skip gate questions 1 (who writes the plan) and 2 (who
reviews it) — both are already done — and ask only the remaining five (code writer, code reviewer,
impl review, go-live, map). Read the plan's .json and confirm it is well-formed (tasks[] with id /
agent / writeScope / validate, and layers[][]) before proceeding; if it is malformed or stale, say so
and offer to (re)plan instead of resuming.
These gate questions come FIRST — immediately after the dependency check (Step 1). Do NOT precede
them with scope/feature-clarification questions ("full platform or just the API?", "which SSO
protocol?", etc.). The prompt IS the scope; if it's broad or ambiguous, you ground it in Step 3 (AFTER
these gates) or let the plan phase challenge scope during planning — never with a question round before
the gates. The user expects the workflow configuration first.
Every role independently picks its executor (each write and each review is separate — write with
codex, review with kiro is fine). Only offer codex/kiro for roles whose tooling Step 1 found
installed. Defaults are LIGHT to control token cost; the user can dial each up to full rigor or skip.
Option ordering rule: list native first (mark it Recommended/default), then codex, then
kiro, and put skip as the LAST option you provide (it then renders second-to-last, right before
the auto-added "Other"). Plan writing and code writing are NOT skippable (they must happen); the four
optional gates (plan review, code review, impl review, go-live audit, map) each include skip.
- Who WRITES the plan — the DAG decomposition:
native (plan-to-task-list-with-dag — default),
codex, or kiro. Passed as planHarness. (No skip — the plan must be written.)
- Who REVIEWS the plan — founder review (scope, decomposition, phantom paths):
native (default),
codex, kiro, or skip. Passed as planReview.
- Who WRITES the code — every task + its fixes:
native (the plan's specialist engineer agents —
default), codex, or kiro. Passed as buildHarness. (No skip.)
- Who REVIEWS the code — each built task in the build loop:
native (the matched -reviewer —
default), codex, kiro, or skip (no per-task reviewer or fix loop — biggest token save). Passed
as taskReview.
- Implementation review after all tasks — the plan-vs-implementation review:
native (default),
codex, kiro, or skip. Passed as implReview.
- Run the go-live audit —
run (the go-live audit, only fires if build+impl come back
verified-clean) or skip (default). Passed as goLive (run → true).
- Run map-project at the end —
map-project, map-project-monorepo, or skip (default). Detect
the repo layout and recommend the matching variant. Held as mapRefresh — this is NOT a Workflow arg
(it isn't in the args object); the SKILL runs the chosen map skill itself in Phase 3, after the
Workflow returns, on a real (non-aborted) run.
Defaults (light, kept safe): planHarness native, planReview native, buildHarness native,
taskReview native, implReview native, goLive skip, map skip. The user can go full swing
(every review on, codex/kiro where wanted, go-live on), delegate writing and reviewing to harnesses
(any write/review role → codex|kiro — and the writer and reviewer may be DIFFERENT harnesses), or go
fast (skip the reviews). Warn (do not block) if BOTH taskReview skip AND implReview skip:
nothing then checks the build, so a clean verdict only means the engineer validates passed.
Step 3 — Scope grounding, project facts, git preflight, agent list
Now (AFTER the gate questions) ground the scope. Read the repo to understand what exists. If
$request is broad or ambiguous (e.g. "a self-hostable SaaS platform with SSO"), narrow it HERE: prefer
inferring scope from the repo, and only ask focused scope questions if you genuinely cannot proceed —
or pass the broad prompt through and let the plan phase challenge scope. Fold the resolved scope into
the prompt you pass in args.
Gather the facts the Workflow needs (do not ask the user — read the repo): root (absolute repo path),
workingBranch (never build on a protected branch without confirmation), validate (the workspace
typecheck+lint+test command), and hardRules (the load-bearing invariants from root CLAUDE.md / the spec).
Verify root is a git work tree — git -C <root> rev-parse --is-inside-work-tree must print true,
and workingBranch must exist with at least one commit. The build creates and merges task branches, so
a non-git folder (or a branch with no commit) cannot be built. If root is not a git repo, STOP and
tell the user — offer git init + a baseline commit, or correct the path — and do not launch.
Pass availableAgents (the specialist engineer + -reviewer names that exist here, from Step 1) and
allowGeneralFallback (true only if the user chose Continue with gaps) into the Workflow args. The
plan then assigns ONLY agents from that list (it can't invent one that isn't installed); the Workflow
returns missingAgents for anything that still slips through. Never silently substitute
general-purpose for a missing specialist without the user having seen the gap in Step 1.
When RESUMING from an existing plan, also resolve the plan here: read .ulpi/plans/<name>.json
(the .md renders from it), validate the DAG shape (tasks[] with id / agent / writeScope / validate,
acyclic layers[][]), and pass planPath (the path) — or the parsed plan object — in args. Do NOT
re-plan. The Workflow loads/validates it and skips both the plan and plan-review phases. If the plan is
missing or malformed, stop and offer to (re)plan instead.
Open a master TodoWrite mirroring the phases in references/playbook-state.md.
Success criteria: dependency status was shown and the user chose continue-or-restart;
planHarness, planReview, buildHarness, taskReview, implReview, goLive, mapRefresh, root
(a confirmed git work tree), workingBranch, validate, hardRules, and availableAgents
(+ allowGeneralFallback if gaps) are all resolved.
Phase 2 — Run the playbook Workflow (steps 3–14)
If goLive == yes, FIRST author the go-live audit so step 13 composes the proven audit rather than a
thin pass: run the go-live-audit skill to generate the project-tailored audit workflow and
capture the scriptPath the Workflow tool persists for it; pass that path in as auditScriptPath.
(If you skip this, step 13 falls back to an inline finder pass.)
First, create the live status file (the Workflow sandbox has no filesystem access, so the SKILL must
create it). Pick a workflowId — ship-<UTC timestamp>-<short-slug-of-the-prompt> (e.g. via Bash
date -u +%Y%m%dT%H%M%SZ) — set statusFile = <root>/.ulpi/workflows/<workflowId>.json (ABSOLUTE), and
Write the initial document so a watcher sees the run the instant it launches.
RESUME — do NOT clobber an existing status file. The build now does CHECKPOINT RESUME: it reads the
status file and SKIPS every task already marked done (passed/integrated/reviewing/fixing/blocked),
rebuilding only the rest. So when resuming a prior run, REUSE its existing <root>/.ulpi/workflows/<id>.json
(pass that same workflowId/statusFile) — do NOT overwrite it with a fresh all-pending document, or the
checkpoint is lost and everything rebuilds. Only Write the initial document for a brand-NEW run.
{ "schemaVersion": 1, "workflowId": "<id>", "skill": "ship-playbook", "status": "initializing",
"prompt": "<prompt>", "root": "<root>", "workingBranch": "<branch>", "createdAt": "<UTC now>",
"config": { "planHarness": "...", "planReview": "...", "buildHarness": "...", "taskReview": "...",
"implReview": "...", "goLive": false },
"plan": null,
"phases": { "plan": {"status":"pending"}, "plan_review": {"status":"pending"},
"build": {"status":"pending"}, "impl_review": {"status":"pending"},
"verify": {"status":"pending"}, "audit": {"status":"pending"} },
"tasks": {}, "openRegister": [], "result": null }
Then launch references/workflow-template.js via the Workflow tool, passing intake as args:
Workflow({ scriptPath: ".../references/workflow-template.js",
args: { prompt, root, workingBranch, validate, hardRules, goLive,
planHarness, planReview, buildHarness, taskReview, implReview,
auditScriptPath, availableAgents, allowGeneralFallback, warmWorktree,
planPath, kiroModel,
workflowId, statusFile, trackStatus, checkpointResume } }) // planPath → RESUME at build;
// workflowId/statusFile → live .ulpi/workflows/<id>.json
// (trackStatus:false disables); checkpointResume:false →
// force a full rebuild (default true: skip done tasks);
// warmWorktree:false → plain per-worktree install
// (default true: CoW-seed node_modules/vendor/Pods; Rust uses kache or CoW-seeds target/)
After launch, stamp the run id AND the full launch args. The Workflow tool returns a runId (wf_…)
immediately (it runs in the background). Write runId, status:"running", and — critically — the COMPLETE
launchArgs (the exact args object you just passed: prompt, root, workingBranch, validate, hardRules,
all harnesses, goLive, …) into the status file via Bash+jq:
jq --argjson la '<the args object>' '. + {runId:"<runId>", status:"running", launchArgs:$la}'
This makes the status file a self-describing, deterministic resume source — wf-status.mjs --resume
reads launchArgs back verbatim so a resume re-fires the identical config (no hand-assembly, no drift).
Resuming a previous run? Use wf-status.mjs --resume — do NOT hand-assemble args. From the repo root:
node <skill-dir>/helpers/wf-status.mjs --resume [<runId>] reads the run's status file and prints the exact
Workflow({ scriptPath, args }) to relaunch — already carrying planPath (skip re-planning),
checkpointResume:true (skip tasks already done), and the same statusFile. Launch that verbatim. The
durable status-file checkpoint makes resume session-independent — no resumeFromRunId needed. (If the
run predates v1.7.0 / has no launchArgs, --resume warns and you supply the missing validate/hardRules;
--write [<runId>] first backfills a status file from the journal.)
mapRefresh is deliberately NOT in args — it's the only intake answer the Workflow doesn't run.
The map refresh regenerates CLAUDE.md from the FINISHED code, so the skill runs it itself in Phase 3
after the Workflow returns (see Q7). All other gate answers go in args above.
Pass args as a real JSON object, NOT a JSON-encoded string. A stringified blob reaches the
script as one string, fails its typeof args === 'object' check, and every input silently falls to a
FILL: placeholder. The script hard-THROWS on that instead of returning a fake converged:true, so a
stringified-args launch errors loudly — if you hit it, relaunch as a FRESH run (no resume) with args
as an object. (Each role arg coerces to a safe default if invalid, so a typo degrades gracefully.)
The Workflow then executes the playbook in one pass, running each gate at the level the user chose:
- Plan (step 3) — the planner (per
planHarness: native / codex / kiro) follows the
plan-to-task-list-with-dag methodology unattended (mode auto-selected), grounds every path in the
real repo, assigns a specialist engineer + -reviewer + stack skill to each task, writes
.ulpi/plans/<name>.md+.json, returns {tasks, layers}. RESUME: when planPath/plan is
supplied, this phase instead LOADS that already-reviewed plan (an agent reads + validates + normalizes
it; no re-planning) and plan review is skipped — the run resumes at build.
- Plan review (steps 4–9) — reviewer per
planReview (skip / native / codex / kiro). ONE
bounded loop → fix the plan (JSON-first, re-render MD; fix is always native) → re-review; exits on no
BLOCK/CONCERN (OBSERVATIONs never block) OR non-convergence, capped at MAX_REVIEW (2).
- Build (steps 10–11) — walk the DAG layers; per task: engineer (worktree, task branch) →
in-workflow integrate agent (
git merge onto the working branch, removing each merged worktree as it
goes) → reviewer (unless taskReview skip) → bounded fix loop until it passes; barrier between layers.
Each fresh worktree is provisioned fast (warmWorktree, default on): a one-time warm step (overlapping
planning) primes caches, then engineers CoW-clone node_modules/vendor/Pods from the primary checkout
when the lockfile is unchanged (Rust shares crate compilation via kache — a path-independent, hardlink-based
rustc-wrapper cache wired in .cargo/config.toml, so worktrees restore compiled crates instead of cloning
target/; when kache is absent it CoW-seeds the warm target/ instead),
falling back to a normal frozen install; warmWorktree:false restores a plain per-worktree install.
Engineer routes per buildHarness; reviewer per taskReview — and the two are INDEPENDENT (write
codex, review kiro is fine). The build and verify fan-outs run behind concurrency gates so a wide DAG
layer or a long findings list can't trip Claude's API rate limits: at most MAX_BUILD_PARALLEL (4)
worktree engineers and MAX_PARALLEL (6) reviewers/verifiers run at once. Total agent count is
unchanged — only how many run simultaneously is bounded. Tune those two constants in
references/workflow-template.js if you hit 429s (lower) or have a high-limit account (raise); never
set them to 1–2. If an agent is still rate-limited despite the caps (returns empty / cut off
mid-flight), it is retried with exponential backoff (RETRY_DELAYS, up to 4 attempts) before
being recorded as blocked — so a rate-limit storm no longer turns into false "blocked task" noise.
- Impl review (step 12) — reviewer per
implReview (skip / native / codex / kiro). The
plan-vs-implementation review of everything built.
- Verify (step 14) — dedup + adversarially verify the build+impl findings. These become the
returned
openRegister — the feedback. No automatic re-plan/re-build; the workflow returns and stops.
- Audit (step 13) — runs only when
goLive AND build+impl come back verified-clean: COMPOSE the
proven go-live-audit workflow inline via the workflow() hook (auditScriptPath) — gates →
finders → dedup → dual-lens verify → critic — ∥ a second-harness audit lane; its findings become
openRegister.
Watch progress three ways: the /workflows panel (live agent tree), the status file
(cat <root>/.ulpi/workflows/<id>.json — overall status, per-phase, per-task), or the bundled reader
node <skill-dir>/helpers/wf-status.mjs (reconstructs per-task status straight from the run's journal —
works even mid-flight and even if status tracking is off). See references/status-tracking.md. To iterate
on the script, edit the saved scriptPath the tool returns and re-invoke with {scriptPath} (and
resumeFromRunId to reuse cached agent results). Whenever you pass resumeFromRunId, re-pass the SAME
args object — it reuses cached agent results but the script re-executes from the top, so omitting
args empties CFG and the script hard-throws on the FILL: guard. Always include the full args object
you launched with (including workflowId/statusFile). To RESUME a prior run (not iterate on the script),
use wf-status.mjs --resume instead — see the resume verb below; it carries the full args for you.
Success criteria: The Workflow runs to completion and returns
{ converged, ranReal, plan, planSupplied, build, openRegister, missingAgents, reviewConfig, noReviewGate, planReviewRan, implReviewRan, taskReviewDowngraded, auditRan, workspaceValidatePassed, endStateUngated, blockedTaskCount, workflowId, statusFile }.
Phase 3 — Report and escalate
Read the Workflow result:
- First, confirm the run is real — not a false-clean. If the result has
ranReal: false (or an
aborted message), the inputs never reached the script (or a preflight failed). Report that failure
and relaunch (fresh run) with args as a real JSON object; do NOT treat converged as meaningful.
- If
missingAgents is non-empty, some tasks ran on general-purpose because the assigned
specialist isn't installed here. Surface the list (which agents, how to install) so the user can
decide whether to install them and re-run for higher-quality output.
- If
noReviewGate: true (the user skipped BOTH per-task review and impl review), CAVEAT any
clean verdict: it only means the engineer validates passed, nothing reviewed the build. Report
reviewConfig so the user sees which gates ran.
- Gate honesty — a configured gate that did NOT actually run is never "clean". The Workflow already
keeps
openRegister non-empty (so converged is false) when a gate died, but report the cause so the
user knows WHY: planReviewRan === false / implReviewRan === false → the configured plan/impl reviewer
couldn't run (e.g. kiro CLI absent); taskReviewDowngraded non-null → the requested per-task reviewer
(e.g. codex) couldn't run here and was downgraded to native (it shows {requested, ranAs}) — the tasks
WERE reviewed (natively), but tell the user their requested reviewer didn't run; auditRan === false → the go-live audit died (launch NOT confirmed);
workspaceValidatePassed === false → the final validate on the integrated tree is RED (it does not
typecheck/lint/test — the most load-bearing blocker). endStateUngated === true (impl review skipped) →
CAVEAT that the whole-codebase semantic end-state was never gated even if the tree compiles.
blockedTaskCount > 0 → that many tasks did not pass (engineer-failed, review-blocked, or
dep_blocked because an upstream dependency never integrated — a blocked on dependency <X> reason
points at the ROOT). These are in openRegister; surface them with their reasons.
- Pre-existing-failure attribution. A
build[] entry may carry a preexistingNote — its slice is
correct and was integrated, but its validate was red ONLY from pre-existing / out-of-scope failures it
doesn't own (the build no longer discards such correct work; the engineer self-classifies new-vs-pre-existing
against the base). And when workspaceValidatePassed === false, the workspace-validate marker now
distinguishes failures INTRODUCED by this run (the real blockers) from ones PRE-EXISTING on the base (need a
separate owning task — not caused by this run), with a per-step [steps: typecheck=pass, lint=FAIL, …]
breakdown. Surface this split so the user fixes the right thing and a green-slice build whose tree is red
from inherited breakage is read correctly (work preserved + real cause named), not as "the build broke it."
- If
planSupplied: true, the run RESUMED from a pre-reviewed plan: planning and plan-review were
skipped by design — say so, so a clean verdict isn't misread as "the plan went unreviewed."
converged: true (real run, openRegister empty) → DONE. Report the build outcome per task,
the review/audit verdicts (per reviewConfig), and where the plan landed.
openRegister non-empty → PRESENT the feedback and let the user decide. List the verified
BLOCK/CONCERN findings (file:line, issue, suggested fix, which gate found them). Then offer the next
move: run a fix round (re-invoke the workflow with the findings as the prompt — same intake),
hand-fix, or accept-with-risk. The workflow does not loop on its own; never represent open
findings as clean.
The Workflow already wrote the final state into <root>/.ulpi/workflows/<id>.json (status =
done/needs_fix/aborted, per-task outcome, openRegister). Read it to enrich the report and tell the
user where it lives — it is the durable record of this run. If a run died mid-flight (no final write),
refresh the file from the journal with node <skill-dir>/helpers/wf-status.mjs --write before reporting.
Live status, stop & resume — three verbs, all backed by the status file + run journal:
- status →
cat <root>/.ulpi/workflows/<id>.json (or node <skill-dir>/helpers/wf-status.mjs) — at a
glance: overall status, which phase, and each task's state (pending → in_progress → dev_done →
integrated → passed/blocked).
- stop →
TaskStop the run, or the /workflows panel. Nothing is lost — the journal caches every
finished agent.
- resume → from the repo root run
node <skill-dir>/helpers/wf-status.mjs --resume [<runId>] and launch
the Workflow({ scriptPath, args }) it prints verbatim. It re-fires the run's stored launchArgs with
planPath (skip re-planning), checkpointResume:true (skip work already done), and the same statusFile —
a FRESH, session-independent run that carries the full args. This is the canonical resume path.
Do NOT resume with resumeFromRunId alone: it reuses cached agent results but the script re-executes
from the top, and on a new session args is dropped → CFG empties → the script hard-throws on the FILL:
guard (in older versions it ran a fake-clean pass that did nothing). Only use resumeFromRunId while
iterating on the script to reuse cache, and then you MUST still pass the SAME full args object.
A run launched before v1.5.0 (no status file)? Backfill one from its journal:
node <skill-dir>/helpers/wf-status.mjs --write [<runId>] — recovers the plan, branch, task list + status,
and conflicts. The journal has no launch args, so add --args '{…gate config + validate…}' (which only the
launching session knows) to complete the resume recipe. See references/status-tracking.md.
Then, if the user chose a project-map refresh at intake AND the run was real (ranReal, not
aborted), run it last — invoke the chosen skill (map-project or map-project-monorepo) so the
CLAUDE.md context map reflects the code the build just landed. Skip it on an aborted/false-clean run
(there's nothing new to map). This is the final step, after reporting.
Success criteria: Either the enabled gates are genuinely clean (caveated by reviewConfig /
noReviewGate), or the user is handed an honest list of what still blocks plus next moves; the durable
status file reflects the final state; and the project map is refreshed if requested.
Guardrails
- Do not run proactively; this is explicit-user-only (it spawns many agents across rounds).
- The workflow runs ONE pass and never loops on its own; a fix round is a deliberate user choice
(re-invoke with the findings as the prompt). Review gates are user-selected per run — honor the
user's
planReview/taskReview/implReview choices, and warn (don't block) when both per-task and
impl review are skipped. The Workflow owns steps 3–14; the skill owns intake (dependency check +
questions) and the Phase-3 report.
- Do not hand-roll the plan — the Workflow's plan phase follows the plan-to-task-list-with-dag
methodology and assigns a specialist agent per task.
- The build assigns the closest AVAILABLE specialist per task; fall back to
general-purpose only with
the user's consent after notifying them a specialist is missing (never silently). When a stack skill
is installed, the engineer MUST use it (/nextjs, /laravel, /rust, …).
- Never fabricate a clean verdict — open BLOCK/CONCERN findings are returned as feedback, not hidden.
- The workflow runs ONE pass and never loops on its own; a fix round is a deliberate user choice
(re-invoke with the findings as the prompt). Do not re-introduce autonomous recursion.
- Do not pass secrets/tokens into any agent brief; reference by location, redact values.
- Do not build on a protected branch without explicit confirmation of
workingBranch.
- Do not launch the build in a non-git folder or an empty repo — the build creates and merges task
branches, so it requires a git work tree with a committed
workingBranch.
- Keep build worktrees from poisoning the gate: the workflow prunes its
.claude/worktrees/ build
checkouts (at preflight, after each integrate, and at end), and the integrate validate must never
scan .claude/worktrees/** or sibling agent dirs. If a run's gate fails on files outside the task's
scope, suspect leftover worktrees before suspecting the code.
- Follow the DAG; never build a task before its dependencies are integrated. The build integrates
layer-by-layer with a barrier, so correctness depends on the PLAN ordering tasks properly: every task's
dependsOn must be complete and layers must be a topological o
…(truncated)
1---2name: ship-playbook3description: Take one feature request from prompt to reviewed, audited delivery in a SINGLE autonomous pass — one runnable Workflow that plans, reviews the plan, builds task by task, cross-reviews the build, and optionally runs a launch audit, then RETURNS the verified findings rather than looping (the user decides any fix round; a Workflow can't ask mid-run, and an autonomous fix-loop is what caused multi-hour grinds). It chains the existing skills — plan-to-task-list-with-dag (a specialist agent per task) → plan-founder-review → a specialist engineer/reviewer build across the DAG → a full claude ∥ codex/kiro cross-review → go-live-audit — and FOLLOWS the graph strictly: every task declares its dependencies, layers must be a topological order, plan review blocks a mis-ordered graph, and a task NEVER builds before its dependencies integrate (no building on a broken base). Per-task review is SLICE-SCOPED (judges only that task's own change against its acceptance criteria, attributing whole-codebase end-state gaps to the ta4---5
6<EXTREMELY-IMPORTANT>
7This skill drives a long-running, multi-agent delivery Workflow. Non-negotiable rules:
81. The Workflow runs ONE pass — plan → plan review → build → impl review → (audit) → verify — and
9 RETURNS the verified findings as feedback. It does NOT loop on its own: a Workflow can't ask the
10 user mid-run, and an autonomous fix-loop is what caused multi-hour grinds. When findings remain, the
11 skill (Phase 3) presents them and the USER decides whether to run a fix round. The Workflow executes
12 steps 3–14 as real phases; the skill does the intake (dependency check + prompt + gate questions)
13 and feeds the choices in as `args`.
142. Intake order is FIXED: (1) dependency check + continue/restart, then (2) the SEVEN gate questions —
15 who writes the plan, who reviews the plan, who writes the code, who reviews the code, impl review,
16 go-live audit, map project — then (3) scope grounding + facts. ALWAYS ask the seven gate questions
17 FIRST, right after the dependency check. (RESUME exception: when the user points at an existing,
18 already-reviewed DAG plan, the first two — plan writer + plan reviewer — are skipped; ask the other
19 five and pass the plan in as `planPath`/`plan` so the run starts at build.) NEVER precede them with scope/feature-clarification
20 questions ("full platform or just the API?", "which protocol?") — the prompt is the scope; ground it
21 in step 3 or let the plan phase challenge it. Honor the gate choices: full rigor or skip both valid.
223. The BUILD is a Workflow phase, not a description. Per task across the DAG layers: the ENGINEER
23 implements on a task branch in an isolated worktree, an in-workflow INTEGRATE agent git-merges it
24 onto the working branch AND removes each merged worktree, the REVIEWER reviews the integrated state
25 (unless `taskReview skip`), and a bounded fix loop runs until the task passes. Engineer routes per
26 `buildHarness` and reviewer per `taskReview` — INDEPENDENTLY (each ∈ native / `codex` / `kiro`; the
27 writer and reviewer may be different harnesses).
284. The build picks the closest AVAILABLE specialist per task; it falls back to `general-purpose` ONLY
29 with the user's consent after the skill notified them a specialist is missing — never silently. When
30 a task's stack skill is installed, the engineer MUST use it (`/nextjs`, `/laravel`, `/rust`, …).
31 (This applies to the native path; when the user handed building/reviewing to `codex` or `kiro`,
32 those tasks route there instead.)
335. Gates that ARE enabled are real — never wave one through or fake a clean verdict to exit. But the
34 user controls WHICH gates run: a skipped gate (`planReview skip`, `taskReview skip`, `implReview
35 skip`, `goLive no`) is a deliberate choice, not a gate to sneak back in. Warn (don't block) when
36 both per-task and impl review are off — nothing checks the build then.
376. There is NO global harness — every role (code writing, plan review, per-task review, impl review)
38 independently picks its own executor: `native` (claude / the plan's specialist agent), `codex` (the
39 `codex:codex-rescue` plugin), or `kiro` (the kiro path; reviews via `kiro-review`, builds via
40 `hand-over-to-kiro`). Writer and reviewers are independent. (See `harness-routing.md`.)
417. There is no autonomous recursion. After one pass, surface the verified findings honestly and let
42 the user choose to run a fix round (re-invoke with the findings as the prompt) — NEVER fake a clean
43 verdict, and never silently loop.
448. Keep this body focused on launching + reporting the Workflow. Load the build contract, harness
45 routing, state machine, and the Workflow script itself from `references/`.
469. Per-task review is SLICE-SCOPED, never an end-state gate. The build lands one slice at a time, so a
47 per-task reviewer judges ONLY that task's own writeScope + diff against ITS acceptance criteria — it
48 is given the rest of the plan and must attribute an unmet whole-codebase invariant (a legacy path a
49 LATER task removes, a route/link a later task adds, an export a later task consumes) to the OWNING
50 task as an OBSERVATION, NOT a BLOCK against the current slice. The fix loop only acts on findings
51 INSIDE the task's writeScope (the engineer can't fix what it can't touch). The whole-codebase
52 end-state is the IMPL-REVIEW gate (step 12) + the FINAL `validate` gate on the integrated tree (run
53 ONCE after the build, when the whole suite can pass) — that is the real "is it working software"
54 signal, not the per-task blocked count. Per-task integrate is MERGE-ONLY: it does NOT run the
55 whole-workspace validate (a whole-suite failure in a half-migrated tree must never become a per-task
56 blocker — that loops a clean slice to death).
5710. Each run gets a LIVE status file at `<root>/.ulpi/workflows/<workflow-id>.json` — the SKILL creates
58 it before launch (this sandboxed Workflow has no FS access), the Workflow updates it at every phase
59 + DAG layer via cheap status-writer agents, and the skill reads it back in Phase 3. Status tracking
60 is OBSERVABILITY and is NON-FATAL: a failed status write is logged and ignored, NEVER blocking the
61 build. The file is the at-a-glance "where did we get to / stop / resume / retry" record.
6211. CHECKPOINT RESUME is durable, not cache-based: the build reads the status file and SKIPS every task
63 already marked done, rebuilding only the rest — independent of the runtime's agent cache (which any
64 template edit invalidates). So on resume, REUSE the prior run's status file; do NOT overwrite it with a
65 fresh all-`pending` document (that erases the checkpoint). A task builds only once its `dependsOn` are
66 actually INTEGRATED on the working branch (DAG gate) — a task whose dependency never landed is
67 `dep_blocked` (pointing at the ROOT), never built on a broken base.
6812. GATES FAIL CLOSED — a configured gate that did not actually run is NEVER counted as clean. A died/
69 absent/empty reviewer (native/codex/kiro), a died go-live audit, and a RED final workspace validate each
70 keep `openRegister` non-empty so `converged` is false. The FINAL `validate` on the integrated tree is
71 the load-bearing end-state truth (the "run the full suite on the final branch" gate): slices can each
72 pass alone yet break the merged tree, so a non-green final validate blocks regardless of per-task
73 verdicts. Report `planReviewRan`/`implReviewRan`/`auditRan`/`workspaceValidatePassed`/`endStateUngated`
74 honestly — never present a build with a dead gate or a non-green tree as shippable.
75</EXTREMELY-IMPORTANT>
76
77# Ship Playbook
78
79## Inputs
80
81- `$request`: The feature prompt — what to plan, build, review, and ship. May carry overrides like
82 `harness=codex` or `golive=yes` to seed intake. For a fix round, pass the prior findings as the prompt.
83 To **RESUME** from an existing, already-reviewed DAG plan, point at it (e.g. `resume .ulpi/plans/<name>.md`
84 or `--plan .ulpi/plans/<name>.json`): the run skips planning + plan-review and starts at build.
85
86## Goal
87
88Turn one prompt into shipped-quality work the same way every time, by running the delivery playbook
89as a single runnable Workflow: a grounded DAG plan, an optional founder plan review, an implementation
90built task-by-task across the DAG layers (each task written and reviewed by the executor the user
91chose), an optional plan-vs-implementation review, and an optional go-live audit — then it RETURNS the
92verified findings as feedback. It runs ONE pass and does not loop on its own; if findings remain, the
93user decides whether to run a fix round.
94
95The 14 steps map to the Workflow phases: **step 3 → Plan · steps 4–9 → Plan review (optional, bounded
96loop) · steps 10–11 → Build (per-task review optional) · step 12 → Impl review (optional,
97plan-vs-implementation) · step 14 → Verify (dedup + adversarial verify → feedback) · step 13 → Audit
98(only if `goLive` AND build+impl verified-clean)**. The skill does the intake (dependency check + prompt
99+ gate questions) up front. Each review gate runs at the depth the user chose (skip / native / codex /
100kiro); there is no automatic recursion — the workflow returns its findings and stops.
101
102## Phase 1 — Intake
103
104### Step 0 — Decide the MODE: new run vs resume (do this FIRST)
105
106The skill runs in one of two modes — detect which before anything else:
107
108- **NEW RUN** (default — the user gives a feature prompt). Run the full intake below (dependency check +
109 gate questions + scope grounding), create a FRESH status file, and launch with full `args`.
110- **RESUME a previous run** (the user says "resume", "continue the build", gives a `wf_…` runId, or points
111 at an existing `.ulpi/workflows/<id>.json`). Do NOT re-run intake and do NOT overwrite the status file.
112 Instead, from the repo root run **`node <skill-dir>/helpers/wf-status.mjs --resume [<runId>]`** — it reads
113 the run's status file and prints the exact `Workflow({ scriptPath, args })` to relaunch, already carrying
114 `planPath` (skip re-planning), `checkpointResume:true` (skip tasks already done), and the same
115 `statusFile`. Launch that verbatim. The durable checkpoint makes resume session-independent (no
116 `resumeFromRunId`); the build rebuilds only what's left. (If `--resume` warns that `launchArgs` is
117 missing — a pre-v1.7.0 run — supply the missing `validate`/`hardRules`; run `--write` first if there's no
118 status file yet.) Then skip to Phase 3 reporting when it returns.
119
120The rest of Phase 1 is the NEW-RUN path.
121
122### Step 1 — Dependency check & setup (do this FIRST)
123
124Before anything else, tell the user what would make this skill do its BEST work, what's already here,
125and what's missing — with copy-paste install commands. ship-playbook composes other skills and routes
126to specialist agents; the more of these are installed, the better the result.
127
128**How to detect — THREE states, not two.** A skill is only loaded into your available-skills list (and
129an agent into your `subagent_type` options) at Claude STARTUP, so "installed on disk" ≠ "usable this
130session". Classify each item:
131
132- **Ready** — in your loaded available-skills list / `subagent_type` options. Usable now. Don't list it.
133- **Installed, needs restart** — NOT in the loaded list, but present on disk. Check disk
134 **symlink-aware** (skills register as symlinks into a shared store, e.g.
135 `.claude/skills/map-project → ../../.agents/skills/map-project`): use `ls -laL .claude/skills/<name>`
136 or `test -e .claude/skills/<name>` (follows symlinks) — NOT `find -type d` (silently misses symlinks).
137 Report these as "installed — RESTART Claude to load", with NO install command (it's already installed).
138- **Missing** — not in the loaded list and not on disk. Show the full install command.
139
140The loaded list is the source of truth for what's USABLE now; the symlink-aware disk check only
141distinguishes "installed but not loaded (restart)" from "truly missing", so an installed-but-not-yet-
142loaded skill is never mislabeled plain "missing". Sanity check: if you'd report an obviously-present
143skill (one you just used) as missing, your method is broken. Disk/PATH probing for the external
144`kiro-cli` binary is separate (see below).
145
146Detect the project's stack(s), then check (against those loaded lists) what's present vs missing across:
147
148- **Composed skills** — `plan-to-task-list-with-dag`, `plan-founder-review`, `go-live-audit`,
149 `map-project` / `map-project-monorepo`.
150- **Specialist agents for this stack** — the engineer + reviewer pairs the build would assign. The
151 reviewer name is the FULL engineer name + `-reviewer` (NOT an abbreviation): e.g.
152 `nextjs-senior-engineer` + `nextjs-senior-engineer-reviewer`; `go-senior-engineer` +
153 `go-senior-engineer-reviewer`; `laravel-senior-engineer` + `laravel-senior-engineer-reviewer`. Use the
154 exact registered names — never shorten to `nextjs-reviewer`/`go-reviewer`. Missing ones force
155 `general-purpose`, which is lower quality.
156- **Stack skills** — `/nextjs`, `/laravel`, `/rust`, … for the detected stack.
157- **Optional harness tooling** (only if the user might pick codex/kiro): the **codex** plugin
158 (`codex:codex-rescue`); for **kiro**, the CLI plus the `kiro-review` skill (reviewing) and
159 `hand-over-to-kiro` skill (building). **Detecting the kiro CLI — two gotchas:**
160 - The binary is named **`kiro-cli`, NOT `kiro`** (bare `kiro` is usually the Kiro IDE symlink, not the
161 CLI). Check `kiro-cli`.
162 - It's commonly in `~/.local/bin`, which the sandboxed Bash PATH may NOT include — so `command -v
163 kiro-cli` can come back empty even when it's installed. Detect with a location-tolerant check, e.g.
164 `command -v kiro-cli || ls ~/.local/bin/kiro-cli ~/.kiro/bin/kiro-cli /usr/local/bin/kiro-cli 2>/dev/null`,
165 and confirm it runs (`kiro-cli --version`). Docs: <https://kiro.dev/docs/cli>.
166
167Present **TWO separate tables** — one for skills, one for agents — listing only items that are NOT
168ready (i.e. "missing" OR "installed, needs restart"). Do NOT list ready items at all (no "present /
169ready" section). Each row shows the item, its state, and the action:
170
171- **state = installed, needs restart** → action: "Restart Claude to load" (NO install command — it's
172 already on disk).
173- **state = missing** → action: the **full, copy-paste install command**:
174 - skills → `npx skills add https://github.com/ulpi-io/skills --skill <name>` (composed skills, stack
175 skills, kiro helpers `kiro-review`/`hand-over-to-kiro`; `kiro-cli` itself → its docs link
176 <https://kiro.dev/docs/cli>);
177 - agents → `npx agentshq add ulpi-io/agents@<agent-name>`, using the EXACT registered name per row —
178 e.g. `…@nextjs-senior-engineer` and `…@nextjs-senior-engineer-reviewer` (the reviewer is the full
179 engineer name + `-reviewer`, never `…@nextjs-reviewer`).
180
181If a table has no not-ready items, omit it entirely. Then **offer two choices** with `AskUserQuestion`:
182
183- **Continue now** with what's installed (missing specialists fall back to `general-purpose`; missing
184 harness options simply won't be offered).
185- **I'll install them — restart** : the user installs the listed items, then RESTARTS Claude (newly
186 installed skills/agents are only loaded at startup) and re-runs `/ship-playbook`. Tell them to come
187 back and re-invoke after restarting.
188
189Record what's available — it feeds `availableAgents` and constrains which intake options you offer.
190
191### Step 2 — The workflow gate questions (ask these FIRST)
192
193The prompt is `$request`. Ask the SEVEN gate questions (across two `AskUserQuestion` calls — up to 4
194each), in EXECUTION order so they read like the run, unless `$request` already pins them.
195
196**RESUME mode.** If `$request` points at an existing reviewed plan (a `.ulpi/plans/<name>` path, or the
197user asks to resume), the run starts at BUILD: **skip gate questions 1 (who writes the plan) and 2 (who
198reviews it)** — both are already done — and ask only the remaining five (code writer, code reviewer,
199impl review, go-live, map). Read the plan's `.json` and confirm it is well-formed (tasks[] with id /
200agent / writeScope / validate, and layers[][]) before proceeding; if it is malformed or stale, say so
201and offer to (re)plan instead of resuming.
202
203**These gate questions come FIRST — immediately after the dependency check (Step 1).** Do NOT precede
204them with scope/feature-clarification questions ("full platform or just the API?", "which SSO
205protocol?", etc.). The prompt IS the scope; if it's broad or ambiguous, you ground it in Step 3 (AFTER
206these gates) or let the plan phase challenge scope during planning — never with a question round before
207the gates. The user expects the workflow configuration first.
208
209**Every role independently picks its executor** (each write and each review is separate — write with
210codex, review with kiro is fine). Only offer `codex`/`kiro` for roles whose tooling Step 1 found
211installed. Defaults are LIGHT to control token cost; the user can dial each up to full rigor or skip.
212
213**Option ordering rule:** list `native` first (mark it Recommended/default), then `codex`, then
214`kiro`, and put **`skip` as the LAST option you provide** (it then renders second-to-last, right before
215the auto-added "Other"). Plan writing and code writing are NOT skippable (they must happen); the four
216optional gates (plan review, code review, impl review, go-live audit, map) each include `skip`.
217
2181. **Who WRITES the plan** — the DAG decomposition: `native` (plan-to-task-list-with-dag — default),
219 `codex`, or `kiro`. Passed as `planHarness`. (No skip — the plan must be written.)
2202. **Who REVIEWS the plan** — founder review (scope, decomposition, phantom paths): `native` (default),
221 `codex`, `kiro`, or `skip`. Passed as `planReview`.
2223. **Who WRITES the code** — every task + its fixes: `native` (the plan's specialist engineer agents —
223 default), `codex`, or `kiro`. Passed as `buildHarness`. (No skip.)
2244. **Who REVIEWS the code** — each built task in the build loop: `native` (the matched `-reviewer` —
225 default), `codex`, `kiro`, or `skip` (no per-task reviewer or fix loop — biggest token save). Passed
226 as `taskReview`.
2275. **Implementation review after all tasks** — the plan-vs-implementation review: `native` (default),
228 `codex`, `kiro`, or `skip`. Passed as `implReview`.
2296. **Run the go-live audit** — `run` (the go-live audit, only fires if build+impl come back
230 verified-clean) or `skip` (default). Passed as `goLive` (run → true).
2317. **Run map-project at the end** — `map-project`, `map-project-monorepo`, or `skip` (default). Detect
232 the repo layout and recommend the matching variant. Held as `mapRefresh` — this is NOT a Workflow arg
233 (it isn't in the `args` object); the SKILL runs the chosen map skill itself in Phase 3, after the
234 Workflow returns, on a real (non-aborted) run.
235
236**Defaults** (light, kept safe): `planHarness native`, `planReview native`, `buildHarness native`,
237`taskReview native`, `implReview native`, `goLive skip`, map `skip`. The user can go **full swing**
238(every review on, codex/kiro where wanted, go-live on), **delegate writing and reviewing to harnesses**
239(any write/review role → codex|kiro — and the writer and reviewer may be DIFFERENT harnesses), or go
240fast (skip the reviews). **Warn (do not block)** if BOTH `taskReview skip` AND `implReview skip`:
241nothing then checks the build, so a clean verdict only means the engineer validates passed.
242
243### Step 3 — Scope grounding, project facts, git preflight, agent list
244
245**Now (AFTER the gate questions) ground the scope.** Read the repo to understand what exists. If
246`$request` is broad or ambiguous (e.g. "a self-hostable SaaS platform with SSO"), narrow it HERE: prefer
247inferring scope from the repo, and only ask focused scope questions if you genuinely cannot proceed —
248or pass the broad prompt through and let the plan phase challenge scope. Fold the resolved scope into
249the `prompt` you pass in `args`.
250
251Gather the facts the Workflow needs (do not ask the user — read the repo): `root` (absolute repo path),
252`workingBranch` (never build on a protected branch without confirmation), `validate` (the workspace
253typecheck+lint+test command), and `hardRules` (the load-bearing invariants from root `CLAUDE.md` / the spec).
254
255**Verify `root` is a git work tree** — `git -C <root> rev-parse --is-inside-work-tree` must print `true`,
256and `workingBranch` must exist with at least one commit. The build creates and merges task branches, so
257a non-git folder (or a branch with no commit) cannot be built. If `root` is not a git repo, STOP and
258tell the user — offer `git init` + a baseline commit, or correct the path — and do not launch.
259
260Pass `availableAgents` (the specialist engineer + `-reviewer` names that exist here, from Step 1) and
261`allowGeneralFallback` (`true` only if the user chose Continue with gaps) into the Workflow `args`. The
262plan then assigns ONLY agents from that list (it can't invent one that isn't installed); the Workflow
263returns `missingAgents` for anything that still slips through. Never silently substitute
264`general-purpose` for a missing specialist without the user having seen the gap in Step 1.
265
266**When RESUMING from an existing plan**, also resolve the plan here: read `.ulpi/plans/<name>.json`
267(the `.md` renders from it), validate the DAG shape (tasks[] with id / agent / writeScope / validate,
268acyclic layers[][]), and pass `planPath` (the path) — or the parsed `plan` object — in `args`. Do NOT
269re-plan. The Workflow loads/validates it and skips both the plan and plan-review phases. If the plan is
270missing or malformed, stop and offer to (re)plan instead.
271
272Open a master `TodoWrite` mirroring the phases in `references/playbook-state.md`.
273
274**Success criteria**: dependency status was shown and the user chose continue-or-restart;
275`planHarness`, `planReview`, `buildHarness`, `taskReview`, `implReview`, `goLive`, `mapRefresh`, `root`
276(a confirmed git work tree), `workingBranch`, `validate`, `hardRules`, and `availableAgents`
277(+ `allowGeneralFallback` if gaps) are all resolved.
278
279## Phase 2 — Run the playbook Workflow (steps 3–14)
280
281If `goLive == yes`, FIRST author the go-live audit so step 13 composes the proven audit rather than a
282thin pass: run the **`go-live-audit`** skill to generate the project-tailored audit workflow and
283capture the `scriptPath` the Workflow tool persists for it; pass that path in as `auditScriptPath`.
284(If you skip this, step 13 falls back to an inline finder pass.)
285
286**First, create the live status file** (the Workflow sandbox has no filesystem access, so the SKILL must
287create it). Pick a `workflowId` — `ship-<UTC timestamp>-<short-slug-of-the-prompt>` (e.g. via Bash
288`date -u +%Y%m%dT%H%M%SZ`) — set `statusFile = <root>/.ulpi/workflows/<workflowId>.json` (ABSOLUTE), and
289`Write` the initial document so a watcher sees the run the instant it launches.
290
291**RESUME — do NOT clobber an existing status file.** The build now does CHECKPOINT RESUME: it reads the
292status file and SKIPS every task already marked done (`passed`/`integrated`/`reviewing`/`fixing`/`blocked`),
293rebuilding only the rest. So when resuming a prior run, REUSE its existing `<root>/.ulpi/workflows/<id>.json`
294(pass that same `workflowId`/`statusFile`) — do NOT overwrite it with a fresh all-`pending` document, or the
295checkpoint is lost and everything rebuilds. Only `Write` the initial document for a brand-NEW run.
296
297```json
298{ "schemaVersion": 1, "workflowId": "<id>", "skill": "ship-playbook", "status": "initializing",
299 "prompt": "<prompt>", "root": "<root>", "workingBranch": "<branch>", "createdAt": "<UTC now>",
300 "config": { "planHarness": "...", "planReview": "...", "buildHarness": "...", "taskReview": "...",
301 "implReview": "...", "goLive": false },
302 "plan": null,
303 "phases": { "plan": {"status":"pending"}, "plan_review": {"status":"pending"},
304 "build": {"status":"pending"}, "impl_review": {"status":"pending"},
305 "verify": {"status":"pending"}, "audit": {"status":"pending"} },
306 "tasks": {}, "openRegister": [], "result": null }
307```
308
309Then launch `references/workflow-template.js` via the **Workflow** tool, passing intake as `args`:
310
311```
312Workflow({ scriptPath: ".../references/workflow-template.js",
313 args: { prompt, root, workingBranch, validate, hardRules, goLive,
314 planHarness, planReview, buildHarness, taskReview, implReview,
315 auditScriptPath, availableAgents, allowGeneralFallback, warmWorktree,
316 planPath, kiroModel,
317 workflowId, statusFile, trackStatus, checkpointResume } }) // planPath → RESUME at build;
318 // workflowId/statusFile → live .ulpi/workflows/<id>.json
319 // (trackStatus:false disables); checkpointResume:false →
320 // force a full rebuild (default true: skip done tasks);
321 // warmWorktree:false → plain per-worktree install
322 // (default true: CoW-seed node_modules/vendor/Pods; Rust uses kache or CoW-seeds target/)
323```
324
325**After launch, stamp the run id AND the full launch args.** The Workflow tool returns a `runId` (`wf_…`)
326immediately (it runs in the background). Write `runId`, `status:"running"`, and — critically — the COMPLETE
327`launchArgs` (the exact `args` object you just passed: prompt, root, workingBranch, validate, hardRules,
328all harnesses, goLive, …) into the status file via Bash+jq:
329`jq --argjson la '<the args object>' '. + {runId:"<runId>", status:"running", launchArgs:$la}'`
330This makes the status file a **self-describing, deterministic resume source** — `wf-status.mjs --resume`
331reads `launchArgs` back verbatim so a resume re-fires the identical config (no hand-assembly, no drift).
332
333**Resuming a previous run? Use `wf-status.mjs --resume` — do NOT hand-assemble args.** From the repo root:
334`node <skill-dir>/helpers/wf-status.mjs --resume [<runId>]` reads the run's status file and prints the exact
335`Workflow({ scriptPath, args })` to relaunch — already carrying `planPath` (skip re-planning),
336`checkpointResume:true` (skip tasks already done), and the same `statusFile`. Launch that verbatim. The
337durable status-file checkpoint makes resume **session-independent** — no `resumeFromRunId` needed. (If the
338run predates v1.7.0 / has no `launchArgs`, `--resume` warns and you supply the missing `validate`/`hardRules`;
339`--write [<runId>]` first backfills a status file from the journal.)
340
341`mapRefresh` is deliberately NOT in `args` — it's the only intake answer the Workflow doesn't run.
342The map refresh regenerates `CLAUDE.md` from the FINISHED code, so the skill runs it itself in Phase 3
343after the Workflow returns (see Q7). All other gate answers go in `args` above.
344
345**Pass `args` as a real JSON object, NOT a JSON-encoded string.** A stringified blob reaches the
346script as one string, fails its `typeof args === 'object'` check, and every input silently falls to a
347`FILL:` placeholder. The script hard-THROWS on that instead of returning a fake `converged:true`, so a
348stringified-args launch errors loudly — if you hit it, relaunch as a FRESH run (no resume) with `args`
349as an object. (Each role arg coerces to a safe default if invalid, so a typo degrades gracefully.)
350
351The Workflow then executes the playbook in one pass, running each gate at the level the user chose:
352
353- **Plan (step 3)** — the planner (per `planHarness`: native / codex / kiro) follows the
354 plan-to-task-list-with-dag methodology unattended (mode auto-selected), grounds every path in the
355 real repo, assigns a specialist engineer + `-reviewer` + stack skill to each task, writes
356 `.ulpi/plans/<name>.md`+`.json`, returns `{tasks, layers}`. **RESUME:** when `planPath`/`plan` is
357 supplied, this phase instead LOADS that already-reviewed plan (an agent reads + validates + normalizes
358 it; no re-planning) and **plan review is skipped** — the run resumes at build.
359- **Plan review (steps 4–9)** — reviewer per `planReview` (`skip` / `native` / `codex` / `kiro`). ONE
360 bounded loop → fix the plan (JSON-first, re-render MD; fix is always native) → re-review; exits on no
361 BLOCK/CONCERN (OBSERVATIONs never block) OR non-convergence, capped at `MAX_REVIEW` (2).
362- **Build (steps 10–11)** — walk the DAG layers; per task: engineer (worktree, task branch) →
363 in-workflow integrate agent (`git merge` onto the working branch, removing each merged worktree as it
364 goes) → reviewer (unless `taskReview skip`) → bounded fix loop until it passes; barrier between layers.
365 Each fresh worktree is **provisioned fast** (`warmWorktree`, default on): a one-time warm step (overlapping
366 planning) primes caches, then engineers CoW-clone `node_modules`/`vendor`/`Pods` from the primary checkout
367 when the lockfile is unchanged (Rust shares crate compilation via `kache` — a path-independent, hardlink-based
368 rustc-wrapper cache wired in `.cargo/config.toml`, so worktrees restore compiled crates instead of cloning
369 `target/`; when kache is absent it CoW-seeds the warm `target/` instead),
370 falling back to a normal frozen install; `warmWorktree:false` restores a plain per-worktree install.
371 Engineer routes per `buildHarness`; reviewer per `taskReview` — and the two are INDEPENDENT (write
372 codex, review kiro is fine). The build and verify fan-outs run behind concurrency gates so a wide DAG
373 layer or a long findings list can't trip Claude's API rate limits: at most `MAX_BUILD_PARALLEL` (4)
374 worktree engineers and `MAX_PARALLEL` (6) reviewers/verifiers run at once. Total agent count is
375 unchanged — only how many run simultaneously is bounded. Tune those two constants in
376 `references/workflow-template.js` if you hit 429s (lower) or have a high-limit account (raise); never
377 set them to 1–2. If an agent is still rate-limited despite the caps (returns empty / cut off
378 mid-flight), it is **retried with exponential backoff** (`RETRY_DELAYS`, up to 4 attempts) before
379 being recorded as blocked — so a rate-limit storm no longer turns into false "blocked task" noise.
380- **Impl review (step 12)** — reviewer per `implReview` (`skip` / `native` / `codex` / `kiro`). The
381 plan-vs-implementation review of everything built.
382- **Verify (step 14)** — dedup + adversarially verify the build+impl findings. These become the
383 returned `openRegister` — the feedback. No automatic re-plan/re-build; the workflow returns and stops.
384- **Audit (step 13)** — runs only when `goLive` AND build+impl come back verified-clean: COMPOSE the
385 proven `go-live-audit` workflow inline via the `workflow()` hook (`auditScriptPath`) — gates →
386 finders → dedup → dual-lens verify → critic — ∥ a second-harness audit lane; its findings become
387 `openRegister`.
388
389Watch progress three ways: the `/workflows` panel (live agent tree), the **status file**
390(`cat <root>/.ulpi/workflows/<id>.json` — overall status, per-phase, per-task), or the bundled reader
391`node <skill-dir>/helpers/wf-status.mjs` (reconstructs per-task status straight from the run's journal —
392works even mid-flight and even if status tracking is off). See `references/status-tracking.md`. To iterate
393on the script, edit the saved `scriptPath` the tool returns and re-invoke with `{scriptPath}` (and
394`resumeFromRunId` to reuse cached agent results). **Whenever you pass `resumeFromRunId`, re-pass the SAME
395`args` object** — it reuses cached *agent* results but the script re-executes from the top, so omitting
396`args` empties `CFG` and the script hard-throws on the `FILL:` guard. Always include the full `args` object
397you launched with (including `workflowId`/`statusFile`). To RESUME a prior run (not iterate on the script),
398use `wf-status.mjs --resume` instead — see the **resume** verb below; it carries the full `args` for you.
399
400**Success criteria**: The Workflow runs to completion and returns
401`{ converged, ranReal, plan, planSupplied, build, openRegister, missingAgents, reviewConfig, noReviewGate,
402planReviewRan, implReviewRan, taskReviewDowngraded, auditRan, workspaceValidatePassed, endStateUngated,
403blockedTaskCount, workflowId, statusFile }`.
404
405## Phase 3 — Report and escalate
406
407Read the Workflow result:
408
409- **First, confirm the run is real — not a false-clean.** If the result has `ranReal: false` (or an
410 `aborted` message), the inputs never reached the script (or a preflight failed). Report that failure
411 and relaunch (fresh run) with `args` as a real JSON object; do NOT treat `converged` as meaningful.
412- **If `missingAgents` is non-empty**, some tasks ran on `general-purpose` because the assigned
413 specialist isn't installed here. Surface the list (which agents, how to install) so the user can
414 decide whether to install them and re-run for higher-quality output.
415- **If `noReviewGate: true`** (the user skipped BOTH per-task review and impl review), CAVEAT any
416 clean verdict: it only means the engineer validates passed, nothing reviewed the build. Report
417 `reviewConfig` so the user sees which gates ran.
418- **Gate honesty — a configured gate that did NOT actually run is never "clean".** The Workflow already
419 keeps `openRegister` non-empty (so `converged` is false) when a gate died, but report the cause so the
420 user knows WHY: `planReviewRan === false` / `implReviewRan === false` → the configured plan/impl reviewer
421 couldn't run (e.g. kiro CLI absent); `taskReviewDowngraded` non-null → the requested per-task reviewer
422 (e.g. codex) couldn't run here and was downgraded to native (it shows `{requested, ranAs}`) — the tasks
423 WERE reviewed (natively), but tell the user their requested reviewer didn't run; `auditRan === false` → the go-live audit died (launch NOT confirmed);
424 `workspaceValidatePassed === false` → the final `validate` on the integrated tree is RED (it does not
425 typecheck/lint/test — the most load-bearing blocker). `endStateUngated === true` (impl review skipped) →
426 CAVEAT that the whole-codebase semantic end-state was never gated even if the tree compiles.
427- **`blockedTaskCount > 0`** → that many tasks did not pass (engineer-failed, review-blocked, or
428 `dep_blocked` because an upstream dependency never integrated — a `blocked on dependency <X>` reason
429 points at the ROOT). These are in `openRegister`; surface them with their reasons.
430- **Pre-existing-failure attribution.** A `build[]` entry may carry a `preexistingNote` — its slice is
431 correct and was integrated, but its validate was red ONLY from pre-existing / out-of-scope failures it
432 doesn't own (the build no longer discards such correct work; the engineer self-classifies new-vs-pre-existing
433 against the base). And when `workspaceValidatePassed === false`, the `workspace-validate` marker now
434 distinguishes failures INTRODUCED by this run (the real blockers) from ones PRE-EXISTING on the base (need a
435 separate owning task — not caused by this run), with a per-step `[steps: typecheck=pass, lint=FAIL, …]`
436 breakdown. Surface this split so the user fixes the right thing and a green-slice build whose tree is red
437 from inherited breakage is read correctly (work preserved + real cause named), not as "the build broke it."
438- **If `planSupplied: true`**, the run RESUMED from a pre-reviewed plan: planning and plan-review were
439 skipped by design — say so, so a clean verdict isn't misread as "the plan went unreviewed."
440- **`converged: true`** (real run, `openRegister` empty) → DONE. Report the build outcome per task,
441 the review/audit verdicts (per `reviewConfig`), and where the plan landed.
442- **`openRegister` non-empty** → PRESENT the feedback and let the user decide. List the verified
443 BLOCK/CONCERN findings (file:line, issue, suggested fix, which gate found them). Then offer the next
444 move: **run a fix round** (re-invoke the workflow with the findings as the prompt — same intake),
445 **hand-fix**, or **accept-with-risk**. The workflow does not loop on its own; never represent open
446 findings as clean.
447
448The Workflow already wrote the final state into `<root>/.ulpi/workflows/<id>.json` (`status` =
449`done`/`needs_fix`/`aborted`, per-task outcome, `openRegister`). Read it to enrich the report and tell the
450user where it lives — it is the durable record of this run. If a run died mid-flight (no final write),
451refresh the file from the journal with `node <skill-dir>/helpers/wf-status.mjs --write` before reporting.
452
453**Live status, stop & resume** — three verbs, all backed by the status file + run journal:
454- **status** → `cat <root>/.ulpi/workflows/<id>.json` (or `node <skill-dir>/helpers/wf-status.mjs`) — at a
455 glance: overall status, which phase, and each task's state (pending → in_progress → dev_done →
456 integrated → passed/blocked).
457- **stop** → `TaskStop` the run, or the `/workflows` panel. Nothing is lost — the journal caches every
458 finished agent.
459- **resume** → from the repo root run `node <skill-dir>/helpers/wf-status.mjs --resume [<runId>]` and launch
460 the `Workflow({ scriptPath, args })` it prints **verbatim**. It re-fires the run's stored `launchArgs` with
461 `planPath` (skip re-planning), `checkpointResume:true` (skip work already done), and the same `statusFile` —
462 a FRESH, session-independent run that carries the **full `args`**. This is the canonical resume path.
463 **Do NOT resume with `resumeFromRunId` alone**: it reuses cached *agent* results but the script re-executes
464 from the top, and on a new session `args` is dropped → `CFG` empties → the script hard-throws on the `FILL:`
465 guard (in older versions it ran a fake-clean pass that did nothing). Only use `resumeFromRunId` while
466 iterating on the *script* to reuse cache, and then you MUST still pass the SAME full `args` object.
467
468**A run launched before v1.5.0 (no status file)?** Backfill one from its journal:
469`node <skill-dir>/helpers/wf-status.mjs --write [<runId>]` — recovers the plan, branch, task list + status,
470and conflicts. The journal has no launch args, so add `--args '{…gate config + validate…}'` (which only the
471launching session knows) to complete the `resume` recipe. See `references/status-tracking.md`.
472
473**Then, if the user chose a project-map refresh at intake AND the run was real (`ranReal`, not
474aborted), run it last** — invoke the chosen skill (`map-project` or `map-project-monorepo`) so the
475`CLAUDE.md` context map reflects the code the build just landed. Skip it on an aborted/false-clean run
476(there's nothing new to map). This is the final step, after reporting.
477
478**Success criteria**: Either the enabled gates are genuinely clean (caveated by `reviewConfig` /
479`noReviewGate`), or the user is handed an honest list of what still blocks plus next moves; the durable
480status file reflects the final state; and the project map is refreshed if requested.
481
482## Guardrails
483
484- Do not run proactively; this is explicit-user-only (it spawns many agents across rounds).
485- The workflow runs ONE pass and never loops on its own; a fix round is a deliberate user choice
486 (re-invoke with the findings as the prompt). Review gates are user-selected per run — honor the
487 user's `planReview`/`taskReview`/`implReview` choices, and warn (don't block) when both per-task and
488 impl review are skipped. The Workflow owns steps 3–14; the skill owns intake (dependency check +
489 questions) and the Phase-3 report.
490- Do not hand-roll the plan — the Workflow's plan phase follows the plan-to-task-list-with-dag
491 methodology and assigns a specialist agent per task.
492- The build assigns the closest AVAILABLE specialist per task; fall back to `general-purpose` only with
493 the user's consent after notifying them a specialist is missing (never silently). When a stack skill
494 is installed, the engineer MUST use it (`/nextjs`, `/laravel`, `/rust`, …).
495- Never fabricate a clean verdict — open BLOCK/CONCERN findings are returned as feedback, not hidden.
496- The workflow runs ONE pass and never loops on its own; a fix round is a deliberate user choice
497 (re-invoke with the findings as the prompt). Do not re-introduce autonomous recursion.
498- Do not pass secrets/tokens into any agent brief; reference by location, redact values.
499- Do not build on a protected branch without explicit confirmation of `workingBranch`.
500- Do not launch the build in a non-git folder or an empty repo — the build creates and merges task
501 branches, so it requires a git work tree with a committed `workingBranch`.
502- Keep build worktrees from poisoning the gate: the workflow prunes its `.claude/worktrees/` build
503 checkouts (at preflight, after each integrate, and at end), and the integrate validate must never
504 scan `.claude/worktrees/**` or sibling agent dirs. If a run's gate fails on files outside the task's
505 scope, suspect leftover worktrees before suspecting the code.
506- Follow the DAG; never build a task before its dependencies are integrated. The build integrates
507 layer-by-layer with a barrier, so correctness depends on the PLAN ordering tasks properly: every task's
508 `dependsOn` must be complete and `layers` must be a topological o
509
510…(truncated)