| name | do-sdlc |
| description | Supervise a full SDLC pipeline run to merge in a local Claude Code session. Triggered by 'do-sdlc', 'run the full pipeline', 'ship this issue end to end', 'supervise the sdlc'. |
| context | fork |
do-sdlc — Local Pipeline Supervisor
This skill is the local stand-in for the bridge PM session. /sdlc (in this repo) is a single-stage router by contract: it dispatches ONE sub-skill and returns, expecting a PM session to re-invoke it. In a local Claude Code session there is no PM loop — this skill IS that loop: it re-invokes the router, dispatching each stage to a subagent on the stage-appropriate model (opus/sonnet), until merge, a blocking guard, or the iteration cap.
You are the supervisor, not the worker. You assess, dispatch, and track. The stage subagents do all the work.
Redundant-context check (issue #2026, WS-F): if a bridge PM/dev context already owns this issue — a live eng session (e.g. a bridge PM session) or a live supervised-run signal for the issue number — then /do-sdlc is redundant: that context IS the supervision loop. Do not run it; drive via /sdlc (in this repo, the single-stage router) instead. Running /do-sdlc inside an already-owned run nests a second supervision loop and wastes turns.
Repo Context Probe
If docs/sdlc/do-sdlc.md exists, read it and honor its declarations; otherwise use the generic defaults described below. The defaults drive the pipeline through sdlc-tool (synced to every machine via ~/.local/bin), gh, and git.
Hard Rules
- NEVER write code, run tests, or create plans directly — every stage executes inside a stage subagent that invokes the stage's
/do-* skill.
- NEVER decide dispatch yourself —
sdlc-tool next-skill is the only source of dispatch decisions. It encodes all guards (G1–G8) and dispatch rows. Do not second-guess it, reorder stages, or skip it "because the next stage is obvious".
- NEVER continue past a
blocked decision — surface the reason to the human and stop. Guards block for a reason.
- ALWAYS pass
model: per the Stage→Model table when spawning a stage subagent. Never rely on the inherited default.
- ALWAYS record the dispatch before spawning the subagent — this preserves the G4 oscillation signal even if the subagent crashes.
- ALWAYS dispatch with
run_in_background: false, never end the turn waiting on a background child. This skill runs in a forked context (context: fork) that gets exactly one turn. The Agent tool defaults to background execution — it returns immediately and notifies later. A fork has no later turn to be notified on, so a background dispatch is unrecoverable: the fork reports "running in the background, I'll continue when it completes" and then never does (issue #1915). Every stage subagent, including both halves of a multi dispatch, must be spawned with run_in_background: false so its result is in hand before the loop advances.
- NEVER spawn agent teammates for stage work. Where Claude Code agent teams are enabled, ignore those affordances: a teammate's idle notification is not a completion signal (teammates go idle mid-task with deliverables unfinished), and an in-process teammate cannot be reliably resumed. Every dispatch is a foreground subagent per Rule 6.
Worktree & branch ownership
Slug identity always wins. Each issue's build fork exclusively owns .worktrees/{slug} and session/{slug}, derived from the plan slug — this is the single source of truth (worktree_manager.py + resolve_branch_for_stage). Do NOT pre-allocate per-supervisor .worktrees/sdlc-{N} lanes: nothing reads a lane override, so lane instructions are silently dropped and every issue's builders land in .worktrees/{slug} regardless. Converging fork + supervisor onto one branch per plan is deliberate — it structurally collapses duplicate PRs, since GitHub permits only one open PR per head branch. Concurrent builders inside the one slug worktree must write disjoint file sets (do-build's Parallel: true convention: no shared-file writes).
Stage→Model Dispatch Table
Mirrors the engineer persona's table (config/personas/engineer.md) — the local equivalent of valor-session create --model.
| Stage | Skill | model: | Rationale |
|---|
| ISSUE | /do-issue | sonnet | Structured writing |
| PLAN | /do-plan | opus | Adversarial reasoning, architectural design |
| CRITIQUE | /do-plan-critique | opus | Adversarial review (its internal critics self-pin to sonnet) |
| BUILD | /do-build | sonnet | Tool-heavy plan execution |
| TEST | /do-test | sonnet | Deterministic test runs |
| PATCH | /do-patch | sonnet | Targeted fix |
| REVIEW | /do-pr-review | opus | Nuanced code review judgment |
| DOCS | /do-docs | sonnet | Structured writing |
| MERGE | /do-merge | sonnet | Programmatic gate |
Step 1: Resolve the Issue
Same resolution as /sdlc (in this repo) Step 1:
- Issue reference (
208, issue #208): gh issue view {number}
- PR reference (
PR 363): gh pr view {number} --json number,title,state,headRefName,reviewDecision,statusCheckRollup,body and extract the linked issue number from the body (Closes #N / Fixes #N)
- Bare feature description (no number): dispatch a
/do-issue stage subagent first (sonnet), read the created issue number from its report, then proceed.
Do not proceed without an issue number.
Step 2: Ensure the Tracking Session
SDLC_REPO=$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || git remote get-url origin | sed 's/.*github.com[:/]//;s/.git$//')
SDLC_TARGET_REPO=$(git rev-parse --show-toplevel 2>/dev/null || echo "")
export SDLC_TARGET_REPO
sdlc-tool session-ensure --issue-number {issue_number} --issue-url "https://github.com/$SDLC_REPO/issues/{issue_number}" 2>/dev/null || true
Read the JSON from the tool result and record the run_id ({"session_id": ..., "created": ..., "run_id": "<hex>"}) — carry it through every iteration of the Step 3 loop. Reuses the existing sdlc-local-{N} session on re-runs. The ownership contract:
- Every state-mutating
sdlc-tool call (dispatch record, stage-marker, verdict record, meta-set) MUST pass --run-id {run_id} explicitly. A missing flag is a named non-zero error (RUN_ID_REQUIRED) — the call never mints or adopts an identity.
- A foreign run_id (another live run owns the issue lock) yields
ISSUE_LOCKED with the owning run_id/session_id — treat it like a router block: stop and report.
- Recovery after run_id loss (context compaction, restarted supervisor): re-run the
session-ensure above. While the old lock is live it returns ISSUE_LOCKED (bounded by the ≤300s lock TTL, since nothing renews the orphaned run's lock); after the TTL lapses a fresh contest mints a new run_id. If you still have the run_id, add --reuse-run-id {run_id} to recover immediately under the same identity — the tool verifies the claim against the live lock (or, on a free lock, the session record) and never adopts an unverified one.
Step 3: Supervision Loop
Repeat the following cycle. Iteration cap: 15 dispatches (a happy path is 8 stages; the cap is a backstop above realistic patch/re-review cycles — G4 catches genuine oscillation long before it).
3a. Ask the router
sdlc-tool next-skill --issue-number {issue_number}
(Read-only — next-skill takes no --run-id.)
Interpret the JSON from the tool result:
{"blocked": true, ...} → STOP the loop. Report the reason and guard_id to the human, plus a summary of stages completed so far. Do not retry, do not guess an alternative skill.
{"skill": "...", "dispatched": true, ...} → single dispatch; continue to 3b.
{"multi": true, "dispatches": [...], ...} → parallel-safe pair (e.g. DOCS + PATCH); continue to 3b, spawning BOTH subagents in one message so they run concurrently, each on its own stage's model.
- Anything else (error key, empty) → STOP and surface the error.
3b. Record the dispatch
sdlc-tool dispatch record --skill {skill} --issue-number {issue_number} --run-id {run_id}
For a multi-dispatch, record only the FIRST skill in the list (the pair is guard-gated as one decision).
3c. Spawn the stage subagent
Use the Agent tool (general-purpose), with model: from the Stage→Model table and run_in_background: false (Hard Rule 6 — this fork cannot be resumed by a background notification). For a multi dispatch, both calls go in the same message with run_in_background: false each; the harness runs them concurrently and blocks for both results before your next turn. Prompt template:
You are executing ONE SDLC stage for issue #{issue_number} in {repo_path}.
Invoke the Skill tool now: skill "{skill-name-without-slash}", args "{issue_number / pr_number / slug as the skill expects}".
The skill is the procedure — follow it exactly. Do not improvise the stage yourself.
Context:
- Issue: #{issue_number} — {title}
- PR: {#pr or "none yet"}
- Plan: {docs/plans/{slug}.md or "none yet"}
- Prior stage outcome: {one-line summary, or "None — first stage"}
- Run identity: {run_id} — pass --run-id {run_id} on every state-mutating sdlc-tool call (stage-marker, verdict record, meta-set, dispatch record); read-only calls take none.
When done, report back (this is data for the supervisor, not prose for a human):
- outcome: success | failure
- verdict: any verdict string the skill emitted (READY TO BUILD / NEEDS REVISION / APPROVED / CHANGES REQUESTED / ...)
- artifacts: plan path, PR number, branch name — whatever was created or changed
- failures: test failures, blockers, or errors verbatim if any
Carry forward context between iterations: the run_id goes into every stage prompt, and once BUILD reports a PR number, include it in every subsequent prompt and in dispatch record --pr-number.
3d. Backfill stage markers (TEST and PATCH only)
/do-test and /do-patch do not write their own stage markers — on the bridge, the worker's dev-completion handler does it. Locally, the supervisor must:
sdlc-tool stage-marker --stage TEST --status completed --issue-number {issue_number} --run-id {run_id} 2>/dev/null || true
All other stage skills self-mark; do NOT double-write markers for them.
3d.4. REVIEW self-check gate (issue #2193)
If the stage just dispatched in 3c was /do-pr-review, do not treat its
return as sufficient to advance. /do-pr-review should have already called
sdlc-tool verdict finalize internally (an atomic verdict+trailer+marker
write) before returning — but the supervisor is the loud, committed backstop
for that contract, not a formality. Call the read-only self-check yourself:
sdlc-tool verdict selfcheck --pr {pr_number} --issue-number {issue_number}
Interpret the typed JSON result ({ok, verdict_present, trailer_matches_head, marker_completed, reason}):
{"ok": true, ...} → the verdict, its freshness trailer, and the REVIEW
completion marker are all confirmed present. Proceed to 3e / loop back to
3a as normal.
{"ok": false, ...} → HALT the loop immediately. Print the machine-readable
reason field loudly to the operator (e.g. "REVIEW SELF-CHECK FAILED:
reason={reason} — pr={pr_number} issue={issue_number}"), along with which of
verdict_present / trailer_matches_head / marker_completed is false.
Do NOT re-dispatch REVIEW yourself, do NOT advance to DOCS/MERGE, and do NOT
silently loop back to the router — this is a single loud refusal, replacing
the old failure mode where the router would silently re-dispatch REVIEW
forever on the same missing state. Report this as a stop condition in Step 4
(Final Report) just like a blocked router decision.
This gate is prose/logic in this skill body only — it does not modify
agent/sdlc_router.py's dispatch rows (those already fail-closed and
re-dispatch on missing state; see the router's rows 8/8b/9). It exists
specifically to make the supervised /do-sdlc path loud on the exact
failure the router would otherwise handle silently over multiple iterations.
3d.5. Tool-availability mismatch guard (issue #2022)
Inspect every stage subagent's final report before acting on it. If the final message is (or begins with) a bare shell command — it starts with git , gh , cd , pytest, python , or otherwise reads as a command line rather than the outcome/verdict/artifacts report the prompt template asks for — AND the child made zero tool calls, the child was spawned on an agent type without the tools its first step needed: it emitted the command it could not run as plain text. Treat this as a tool-availability mismatch, never a normal completion:
- Log it: "TOOL-AVAILABILITY MISMATCH: stage={skill}, final message is a bare shell command with zero tool calls"
- Re-dispatch the same stage once on a Bash-capable agent type (
general-purpose)
- If the re-dispatch shows the same signature, stop and surface the mismatch to the human — do not loop
3e. Check exit conditions
- Dispatched skill was
/do-merge AND the subagent reports a merge → verify with gh pr view {pr} --json state,mergedAt from the tool result. If MERGED: exit the loop, success.
- Router returned
blocked → already stopped in 3a.
- Iteration cap reached → stop and report how far the pipeline got.
- Otherwise → loop back to 3a. Brief one-line progress note per iteration (e.g. "CRITIQUE done (READY TO BUILD) → dispatching BUILD on sonnet").
Step 4: Final Report
On exit (any path), report:
- Outcome: merged / blocked (with guard + reason) / cap reached
- Stage trail: each dispatch in order with its outcome and verdict
- Artifacts: issue, plan path, PR number, merge commit
- Anything needing human attention: unresolved blockers, skipped acknowledgments, follow-ups
Relationship to /sdlc (in this repo)
| /sdlc (in this repo) | /do-sdlc |
|---|
| Contract | dispatch ONE stage, return | loop until merge/blocked |
| Progression | PM session re-invokes | this skill re-invokes the router |
| Model assignment | PM passes --model when spawning dev sessions | supervisor passes model: on the Agent tool |
| Where it runs | bridge PM sessions + local | local Claude Code sessions |
Both consume the same router (sdlc-tool next-skill → agent.sdlc_router.decide_next_dispatch()) and the same stored stage state — there is exactly one source of dispatch truth.