with one click
sdlc
// Single-stage router for development work. Assesses current state, dispatches ONE sub-skill, then returns. The PM session handles pipeline progression.
// Single-stage router for development work. Assesses current state, dispatches ONE sub-skill, then returns. The PM session handles pipeline progression.
[HINT] Download the complete skill directory including SKILL.md and all related files
| name | sdlc |
| description | Single-stage router for development work. Assesses current state, dispatches ONE sub-skill, then returns. The PM session handles pipeline progression. |
| context | fork |
This skill is a router, not an orchestrator. It assesses where work stands, invokes ONE sub-skill, and returns. The PM session handles pipeline progression by re-invoking /sdlc after each stage completes.
You MUST NOT write code, run tests, or create plans directly -- delegate everything to sub-skills.
For cross-project SDLC work, three resolution mechanisms cover the three different operations the pipeline runs:
GH_REPO (e.g., tomcounsell/popoto) — The gh CLI natively respects this, so all gh commands automatically target the correct repository. Set by sdk_client.py.SDLC_TARGET_REPO (e.g., ~/src/popoto) — The absolute path to the target project's repo root. Use this for all local filesystem and git operations instead of assuming cwd is the target repo. Set by sdk_client.py.AI_REPO_ROOT (default ~/src/ai) — Used by the sdlc-tool wrapper to resolve where the SDLC tooling Python modules live, independent of cwd. The wrapper dispatches into tools.sdlc_* from the ai/ repo even when the orchestrator's cwd is a target project. See docs/features/sdlc-tool-resolver.md.When SDLC_TARGET_REPO is set, you MUST use it for plan lookups, branch listings, and any git commands. The orchestrator's cwd is the ai/ repo, NOT the target project. For SDLC tooling (sdlc-tool stage-query, sdlc-tool verdict record, etc.), no env var is needed — the wrapper resolves AI_REPO_ROOT itself with a ~/src/ai default.
Determine whether the input is an issue reference or a PR reference:
issue 123, issue #123): Fetch with gh issue view {number}PR 363, pr #363): Fetch with gh pr view {number} to get the branch name, review state, and check status. Then extract the linked issue number from the PR body (look for Closes #N or Fixes #N).# For issue references:
gh issue view {number}
# For PR references — get structured state for assessment:
gh pr view {number} --json number,title,state,headRefName,reviewDecision,statusCheckRollup,body
PR state informs Step 2 assessment: When a PR is provided, its current state (checks passing/failing, review approved/changes-requested, etc.) tells you which pipeline stage to resume from. Skip stages that are already complete -- do not restart from scratch.
If NO issue or PR number was provided (just a feature description), invoke /do-issue to create a quality issue. Do not proceed without an issue number.
After resolving the issue number, ensure a local SDLC session exists in Redis so that stage markers can track progress. This is a no-op for bridge-initiated sessions (which already have VALOR_SESSION_ID), but critical for local Claude Code sessions.
SDLC_REPO=$(gh repo view --json nameWithOwner --jq .nameWithOwner 2>/dev/null || git remote get-url origin | sed 's/.*github.com[:/]//;s/.git$//')
sdlc-tool session-ensure --issue-number {issue_number} --issue-url "https://github.com/$SDLC_REPO/issues/{issue_number}" 2>/dev/null || true
This is idempotent -- running it multiple times for the same issue reuses the same session. Inside a bridge-initiated session (where VALOR_SESSION_ID is set), the call is a true no-op — it returns the already-active session without creating a new record. Do NOT export AGENT_SESSION_ID -- env vars do not persist across Claude Code bash blocks. Instead, pass --issue-number to all subsequent sdlc_stage_marker and sdlc_stage_query invocations.
Check what already exists for this issue. Use $SDLC_TARGET_REPO for local operations (defaults to . for same-repo work). Run ALL of these checks — do not skip any.
Query the PM session's stage_states for authoritative stage completion data. This is the exclusive signal for routing decisions. Stage completion is determined ONLY by stored state — never by artifact inference.
Run the stage query tool directly and read its output from the tool result -- no shell substitution, no pipes, no environment-variable capture. The tool resolves the active session from VALOR_SESSION_ID, AGENT_SESSION_ID, or --issue-number internally.
sdlc-tool stage-query --issue-number {issue_number}
Interpret the JSON output from the tool result:
{"ISSUE": "completed", "PLAN": "completed", "BUILD": "in_progress"}): use it as the exclusive signal for the dispatch table. A stage is considered complete ONLY if its value is "completed". Skip steps 2a-2e.{} or an unavailable marker: fall through to the dispatch-history fallback in steps 2a-2e. Do NOT infer stage completion from artifacts.These checks run ONLY when stage_states is unavailable (empty JSON from step 2.0). When stage_states IS available, skip directly to the dispatch table using stage_states as the source of truth.
IMPORTANT: Never infer stage completion from artifacts (plan files, PR existence, docs/ files, etc.). Stage completion is exclusively determined by stored state.
When stage_states is unavailable, use conversation context to identify which skills were already dispatched in this session. Artifacts are used only to check preconditions (e.g., "does a PR exist?") — not to declare stages complete.
Run each check as a separate single-line command and read the output from the tool result. $SDLC_TARGET_REPO is exported by the harness so git -C picks it up without further shell composition; gh uses $GH_REPO automatically for the cross-repo case.
# 2a. Check if a plan doc references this issue
grep -r "#{issue_number}" docs/plans/
# 2b. List all branches (filter for session/ prefix in the tool result)
git -C "$SDLC_TARGET_REPO" branch -a
# 2c. Check if a PR already exists
gh pr list --search "#{issue_number}" --state open
Filter the outputs by reading the tool results -- do not pipe through grep / head / ||. The LLM interprets the output and decides the next step.
If a PR exists, fetch its full state for assessment:
# 2d. Get PR state: checks, review, branch
gh pr view {pr_number} --json number,headRefName,reviewDecision,statusCheckRollup,body
# 2e. Check review status — look for APPROVED, CHANGES_REQUESTED, or no review
# reviewDecision: "APPROVED" means formal GitHub review approved (non-self-authored PRs)
# reviewDecision: "CHANGES_REQUESTED" means formal GitHub review requested changes
# reviewDecision: "" (empty) — AMBIGUOUS for self-authored PRs:
# - For non-self-authored PRs: no review posted yet
# - For self-authored PRs: expected even after review — check _verdicts["REVIEW"] from sdlc_stage_query
# Always cross-check _meta.latest_review_verdict before concluding no review exists.
This step is REQUIRED when a PR exists and review is clean (APPROVED). Skip it only if the pipeline hasn't reached the REVIEW stage yet.
Run each check as a separate single-line command -- no pipes, no command substitution, no || fallbacks. The LLM interprets the tool results and decides the next step.
# 3a. List files changed in the PR (count docs/ entries from the tool result)
gh pr diff {pr_number} --name-only
# 3b. Find the plan path for this issue (first match from the tool result)
grep -rl "#{issue_number}" docs/plans/
# 3c. Read the plan's Documentation section (inspect for unchecked tasks in the tool result)
cat docs/plans/{plan-filename}.md
For the DOCS stage completion check, re-read the sdlc-tool stage-query output from Step 2.0. Do not pipe JSON through a shell here.
Decision logic for docs:
## Documentation section with unchecked tasks → docs NOT donedocs/ file changes AND plan requires doc tasks → docs NOT donedocs/ changes exist in PR → docs done/do-docs — it is idempotent and will no-op if nothing needs updatingBefore consulting the dispatch table in Step 4, evaluate the following guards against the enriched sdlc_stage_query output (stages + _meta). If any guard fires, it forces a specific dispatch or escalates to blocked, and Step 4 is SKIPPED.
The canonical Python implementation is agent.sdlc_router.decide_next_dispatch(). The parity test in tests/unit/test_sdlc_skill_md_parity.py asserts this markdown stays in sync with the Python rules.
| Guard | Condition | Forced Dispatch |
|---|---|---|
| G1: Critique loop | Latest critique verdict contains NEEDS REVISION or MAJOR REWORK AND last_dispatched_skill == /do-plan-critique | /do-plan |
| G2: Critique cycle cap | critique_cycle_count >= MAX_CRITIQUE_CYCLES (2) AND CRITIQUE is not completed | Escalate: blocked with reason critique cycle cap reached |
| G3: PR lock | pr_number is set AND (last_dispatched_skill OR proposed dispatch) is /do-plan or /do-plan-critique | /do-merge (if REVIEW and DOCS complete), /do-patch (if review requested changes), else /do-pr-review |
| G4: Oscillation (universal) | same_stage_dispatch_count >= 3 | Escalate: blocked with reason stage oscillation — {skill} dispatched {N} times without state change |
| G5: Unchanged critique artifact | _verdicts["CRITIQUE"] has artifact_hash AND current plan file hash matches | Use cached verdict: /do-plan (NEEDS REVISION) or /do-build (READY TO BUILD). Never re-dispatch /do-plan-critique on an unchanged plan. |
| G6: Terminal merge ready | pr_number set AND pr_merge_state == "CLEAN" AND ci_all_passing == True AND DOCS == "completed" AND _verdicts["REVIEW"] contains APPROVED | /do-merge {pr_number} |
| G7: Plan-revising lock | pr_number is None AND plan_revising == True AND revision_applied != True | /do-plan (if last_dispatched_skill == /do-plan-critique); Escalate blocked (if no /do-plan in last MAX_PLAN_REVISING_DISPATCHES + 1 turns) |
G4 is universal — it applies to EVERY stage, including DOCS and MERGE. Repeated dispatches of /do-docs or /do-merge without state change WILL trip the guard.
G5 applies to CRITIQUE only, not REVIEW. Review verdicts legitimately change on unchanged diffs (CI flips, new comments, linked issues). G4 handles REVIEW non-determinism instead.
G7 blocks build while plan revision is in flight. The lock is set by /do-plan-critique (Step 5.6) when the verdict requires a revision pass. It is cleared by /do-plan (Phase 4, Step 2b) after committing and pushing the revision. G7 self-heals when revision_applied: true is in the plan frontmatter even if the explicit lock-clear step was skipped. G7 is gated on pr_number is None so an already-shipped PR is never blocked.
After evaluating guards, record the dispatch decision via sdlc-tool dispatch record BEFORE invoking the sub-skill. This preserves the G4 oscillation signal even if the sub-skill crashes mid-execution.
# Record a dispatch event (call BEFORE invoking the sub-skill)
sdlc-tool dispatch record --skill /do-build --issue-number {issue_number}
# Record with PR context (for review/patch/merge stages)
sdlc-tool dispatch record --skill /do-pr-review --issue-number {issue_number} --pr-number {pr_number}
# Inspect the dispatch history (debug G4 state)
sdlc-tool dispatch get --issue-number {issue_number}
The CLI wraps agent.sdlc_router.record_dispatch() and tools.stage_states_helpers.update_stage_states() — it is the correct runtime entry point. Never call record_dispatch() directly from a shell or skill script; always use sdlc-tool dispatch record.
Do not pattern-match against a hand-edited table. Instead, call the routing tool and dispatch whatever skill it returns. The tool evaluates all guards (G1–G6) and dispatch rules (14 rows) against live state.
# Get the next dispatch decision
sdlc-tool next-skill --issue-number {issue_number}
The tool outputs JSON:
{"skill": "/do-build", "reason": "...", "row_id": "4a", "dispatched": true}
Or when blocked:
{"blocked": true, "reason": "G4: stage oscillation ...", "guard_id": "G4"}
How to use the output:
dispatched is true: record the dispatch via sdlc-tool dispatch record (see Step 3.5), then invoke the returned skill.blocked is true: surface the reason to the human and wait. Do NOT loop or guess an alternative skill.error field and escalate to the human.Before recording and dispatching, also supply --proposed-skill when you already know what skill you intend to invoke (enables G3 PR-lock detection):
sdlc-tool next-skill --issue-number {issue_number} --proposed-skill /do-build
Context notes for specific rows (for human reference — the tool encodes these internally):
/do-plan if revision_applied is not yet set in plan frontmatter, else /do-build.completed before merge. The tool enforces this internally.Do NOT restart from scratch if prior stages are already complete.
/do-build or /do-patch/do-test/do-plansession/{slug} branchesPipeline state transitions are defined in agent/pipeline_graph.py (state-machine bookkeeping: which stage is next-ready when one completes). Dispatch logic is defined in agent/sdlc_router.py (decide_next_dispatch). Both are accessed at runtime via sdlc-tool. The table below is for human readability only.
Happy path: ISSUE -> PLAN -> CRITIQUE -> BUILD -> TEST -> REVIEW -> DOCS -> MERGE
Cycles: CRITIQUE(fail) -> PLAN -> CRITIQUE (max 2 cycles)
TEST(fail) -> PATCH -> TEST
REVIEW(fail|partial) -> PATCH -> TEST -> REVIEW
| Stage | Skill | Dev Model | Notes |
|---|---|---|---|
| ISSUE | /do-issue | — | Or already exists |
| PLAN | /do-plan {slug} | opus | Adversarial design |
| CRITIQUE | /do-plan-critique | opus | Adversarial review |
| BUILD | /do-build {plan or issue} | sonnet | Plan execution |
| TEST | /do-test | sonnet | Deterministic runs |
| PATCH | /do-patch | sonnet | Targeted fix (see resume rules in PM persona) |
| REVIEW | /do-pr-review | opus | Code review judgment |
| DOCS | /do-docs | sonnet | Structured writing |
| MERGE | /do-merge {pr_number} | sonnet | Programmatic gate: verifies all stages, then merges |
The Dev Model column shows the model the PM should pass via --model when spawning a dev session for that stage (see Stage→Model Dispatch Table in PM persona).
This list is for reference only. This skill does NOT advance through stages -- it picks the right one and returns.