| name | atdd-run |
| description | End-to-end ATDD pipeline. Use when the user wants to take a user story from impact mapping all the way to merged code, or types /atdd-run. |
| phase | orchestrate |
| parallel | true |
| inputs | ["us-slug"] |
| outputs | ["merged-prs"] |
| escalation | human-checkpoint |
/atdd-run
End-to-end driver for the ATDD pipeline. Chains every phase. Stops at two hard human checkpoints:
- Checkpoint #1 — post-spec. Before any code is written.
- Checkpoint #2 — final integration PR. When all scenario sub-PRs are merged into the integration branch and the final PR
integration → trunk is opened.
Every scenario sub-PR in between is handled autonomously by pr-auto-merge: CI watch, bot idle watch, /apply-pr-feedback on retours (bounded), then auto-merge into the integration branch. No per-scenario human prompt.
When to use
Trigger when the user provides a user-story slug (e.g. /atdd-run cart-checkout) or pastes a US with its business rules.
Inputs
us-slug — kebab-case identifier (used for filenames and GitHub labels).
- Optional flags:
--sequential — run reviewers serially instead of in parallel (Codex fallback).
--stage3=<sequential|workflow> — Stage 3 execution mode. Overrides .atdd-pipeline.json:stage3_mode. Default sequential. workflow is the opt-in parallel path and is ignored (falls back to sequential) when the Workflow tool is unavailable.
--dry-run — generate scenarios + show issue plan, do not create issues, do not write code.
--from-stage <spec|sync|red|green|auto-merge|final-pr> — resume from a specific phase.
--no-auto-merge — temporarily disable pr-auto-merge for this run; falls back to the legacy per-scenario human checkpoint #2 inside green-cycle.
Workflow
Stage 0 — Capture (skill: impact-map)
If no us-slug provided, run impact-map to elicit the story, actor, action, goal, and business rules. Emit specs/<us-slug>/context.md.
Stage 1 — Spec (skills: spec-generate, spec-review)
- Run
spec-generate. It MAY interview the user for ambiguities before producing Gherkin.
- Output:
specs/<us-slug>/*.feature files (one per scenario, tagged by test level).
- Run
spec-review. Read-only review across four axes: branches, coherence, gaps, triangulation.
- Output:
specs/<us-slug>/review.md.
- Checkpoint #1 — Human gate. Present the features + review report and ask:
OK → continue to Stage 2.
REGENERATE <reason> → loop back to step 1 with the reason as guidance.
Stage 2 — Sync (skill: to-issues-atdd)
Run to-issues-atdd. Idempotent. Creates:
- Parent issue (business goal aggregated from
context.md).
- User Story issue, linked to parent.
- One sub-issue per
.feature file, labelled by test level.
- Milestone for the target version (if provided in
context.md).
Output: a JSON manifest specs/<us-slug>/issues.json mapping scenario-slug → issue-number.
Stage 3 — Execute (skills: red-cycle, green-cycle, pr-auto-merge)
Stage 2 already provisioned the integration branch atdd/<us-slug>/integration and wrote it to issues.json.
Stage 3 has two execution modes. Pick with .atdd-pipeline.json:stage3_mode (sequential | workflow) or the --stage3=<mode> flag. Default is sequential — it is the only mode under Codex and the safe path everywhere.
Sequential mode (default)
For each sub-issue in issues.json.scenarios, iterate sequentially — sub-PRs target the same integration branch, so concurrency would race the merge queue:
- Run
red-cycle <issue>. Generates failing test + runs review-fidelity. Bounded auto-correct ×2 then escalate (comment on issue, skip to next scenario).
- Run
green-cycle <issue>. Generates minimal implementation + runs review-architecture and review-intent (parallel by default). Bounded auto-correct ×2 then escalate. On success: opens a draft PR targeting the integration branch.
- Hand the PR number to
pr-auto-merge <pr>. It will:
- Mark the PR ready.
- Watch CI to completion.
- Watch bot reviewers (CodeRabbit, codex, github-actions, etc.) until an
idle_window_minutes quiet period has elapsed.
- If actionable bot feedback exists, invoke
apply-pr-feedback (bundled with this plugin), push, and loop. Capped by max_fix_iterations.
- When CI is green AND no
CHANGES_REQUESTED AND no actionable bot comments remain: gh pr merge --squash --delete-branch.
- Escalates (comment on PR, leave open) on any timeout / exhausted fix iterations / failing CI it could not repair.
- After auto-merge succeeds, move to the next scenario. On escalation, record the PR number in
specs/<us-slug>/escalations.md and continue with remaining scenarios (do NOT abort the whole pipeline).
Workflow mode (opt-in, experimental — Claude Code only)
When stage3_mode == "workflow" and the Workflow tool is available (dynamic workflows are research-preview: a recent Claude Code, a paid plan, not org-disabled), produce all scenarios in parallel and serialize only the merge. This is the substrate that removes the merge-queue serialization from the per-scenario work while keeping the merge itself single-lane.
- Capability check. If the Workflow tool is unavailable (Codex, free plan,
disableWorkflows, older version), silently fall back to sequential mode above. Never hard-fail on a missing capability.
- Launch the shipped script. Call the Workflow tool with
scriptPath = ${CLAUDE_PLUGIN_ROOT}/workflows/atdd-stage3.workflow.mjs and args = { usSlug, integrationBranch, specsDir, scenarios, conventions } (flatten issues.json.scenarios to [{slug, issue, branch, level, rule, feature}]; conventions is the once-extracted shared language + relevant ADRs, passed so the N parallel scenario agents don't each re-discover them — a token cache, not a replacement for an agent consulting the ADRs its own diff touches). The script:
- runs
red-cycle + green-cycle per scenario in an isolated worktree (agentType atdd-scenario), each branch cut from the integration tip — parallel, no barrier;
- merges each draft PR through a single serialized lane (agentType
atdd-merge) that rebases onto the live integration tip, re-runs CI, then pr-auto-merges — so the merge queue never races.
- Consume the result. The script returns
{ merged: [slug], escalated: [{slug, issue, pr, reason}] }. Write each scenario's terminal phase into run-state.json and record escalations in escalations.md, exactly as sequential mode does.
The human gates stay OUTSIDE the workflow: checkpoint #1 (spec) already happened before Stage 3; checkpoint #2 (final PR) happens in Stage 4 after the workflow returns. Dynamic workflows take no mid-run human input, which is why the gates must bracket Stage 3, never sit inside it. This mode is experimental and opt-in; the sequential loop is the supported default until it has real-repo mileage.
Write run-state.json after every phase boundary of every scenario (after RED commits, after the draft PR opens, after merge, after escalation), in both modes. This is what makes a multi-hour run crash-resumable rather than restart-from-scratch — see Run state. The per-scenario phase transitions are the only durable record of where a fanned-out or interrupted run actually stopped.
There is no per-scenario human checkpoint in Stage 3. This is the explicit goal of auto_merge.enabled == true.
Stage 4 — Final integration PR (terminal stop)
After every scenario in issues.json.scenarios has either been merged into the integration branch or escalated:
- Classify every scenario from
run-state.json (see Run state) reconciled against GitHub into exactly one of merged, escalated, unmerged:
- If ANY scenario is
unmerged (neither merged nor escalated), REFUSE to proceed. The run is not finished; resume the unmerged scenarios first.
escalated scenarios do NOT block the final PR, but they MUST be surfaced — an escalated scenario is a hole in the user story, and shipping it silently into the trunk PR is the failure this step exists to prevent.
- Push the integration branch tip (it already has all merged scenario commits via squash merges).
- Open a single PR
atdd/<us-slug>/integration → trunk_branch:
-
Title: [US] <us-slug>: <Goal sentence>.
-
Draft status: open it as a draft when escalated.length > 0, otherwise ready. A draft can't be fast-merged, which keeps an incomplete US from slipping past the human gate (and dovetails with the trunk-merge guard hook).
-
Body: aggregated summary — goal, list of scenarios (each linking its sub-PR). When escalated.length > 0, the body MUST LEAD with a blocking section:
## ⚠️ Incomplete — <N> scenario(s) escalated, not implemented
This user story is NOT fully delivered. The following scenarios exhausted
auto-correction and were skipped:
- #<issue> <title> — see specs/<us-slug>/escalations.md
...
Resolve or consciously accept each before merging into <trunk_branch>.
Then the normal summary and a link to specs/<us-slug>/ artifacts.
-
Labels: atdd, us/<us-slug>, integration; add incomplete when escalated.length > 0.
- HARD STOP. Checkpoint #2 — Human gate on the trunk PR. Do NOT invoke
pr-auto-merge here. pr-auto-merge refuses to operate on PRs whose base is trunk_branch by contract, and the trunk-merge guard hook blocks it deterministically. Draft status is a guardrail, not the gate — the human still reviews and promotes.
- Print the PR URL (and, if drafted, the escalated-scenario list) and return.
Parallelism
When parallel: true (default), Stage 3 reviewers run via the Agent tool on Claude Code (formerly Task; the alias still works). With --sequential (or under Codex), reviewers are invoked in series within the same session.
Failure escalation
An escalation is a first-class signal, not a buried comment. In a sequential run the orchestrator sees each one inline; the moment Stage 3 fans out (workflow mode), a single blocked scenario is easy to miss, so escalation has one contract the orchestrator owns end to end.
Every escalation does four things:
-
Comment on the GitHub issue / PR (the sub-skill already does this) with: the failing skill name, the last reviewer report, the auto-correction attempt diffs, and the ESCALATED: phrase. GitHub stays the system of record (principle #5).
-
Append a structured entry to specs/<us-slug>/escalations.md — one block per escalation, machine-readable enough for Stage 4 to classify:
- scenario: <slug> · issue: #<n> · pr: #<n|—> · phase: red | green | auto-merge · at: <iso8601>
reason: <one line>
artifacts: specs/<us-slug>/.cycles/<n>/<report>.md
-
Mark it in run-state.json — set the scenario's status: "escalated" and phase to where it died, so resume and Stage 4 both read one source.
-
Emit a best-effort push — call PushNotification (Claude Code) so a human is actually told a scenario is blocked, e.g. ATDD <us-slug>: scenario <slug> (#<n>) escalated at <phase> — <reason>. This is best-effort and no-op under Codex (no such tool); it never gates the pipeline.
The pipeline does NOT continue past an escalated sub-issue's work; it moves to the next scenario. It does NOT abort the whole run. Stage 4 then refuses to ready the final PR while any scenario is unmerged, and surfaces every escalated one in the blocking PR header — so an escalation can't ship silently into the trunk PR.
Run state
specs/<us-slug>/run-state.json is the orchestrator's durable, per-scenario progress record. GitHub Issues remains the system of record (principle #5); this file is the local index that lets a crashed or interrupted run resume at the exact scenario+phase it stopped at, instead of replaying a whole stage. It is also what Stage 4 classifies on.
Shape:
{
"us_slug": "cart-checkout",
"integration_branch": "atdd/cart-checkout/integration",
"updated_at": "<iso8601>",
"scenarios": {
"<scenario-slug>": {
"issue": 130,
"branch": "atdd/cart-checkout/scenario-130",
"phase": "red | green | auto-merge | merged | escalated",
"pr": 142,
"status": "pending | in_progress | merged | escalated",
"attempts": { "red_fidelity": 0, "green_review": 0, "fix_iterations": 0 },
"updated_at": "<iso8601>"
}
}
}
Two rules:
- Write after every transition. The orchestrator rewrites the relevant scenario entry the moment a phase boundary is crossed (RED committed, draft PR opened, merged, escalated). Never batch the writes to the end — the value is entirely in surviving an interruption.
- Reconcile against GitHub on resume, never trust the file blindly. A crash can land between "merged on GitHub" and "wrote run-state.json". On resume, for each scenario re-derive ground truth from
gh (gh pr view <pr> --json state,mergedAt, gh issue view <issue> --json state, branch existence) and correct any drift before continuing. The file is a fast index; GitHub is the truth.
run-state.json is seeded from issues.json at the start of Stage 3 (every scenario pending).
Outputs
specs/<us-slug>/context.md
specs/<us-slug>/*.feature
specs/<us-slug>/review.md
specs/<us-slug>/issues.json (includes integration_branch)
specs/<us-slug>/run-state.json (per-scenario phase/status; the crash-resume index — see Run state)
specs/<us-slug>/escalations.md (only if any sub-issue / sub-PR escalated)
specs/<us-slug>/.cycles/<issue-number>/auto-merge.log per scenario (pr-auto-merge timeline).
- One scenario branch + auto-merged sub-PR per successfully completed scenario.
- ONE final PR
atdd/<us-slug>/integration → main, left OPEN for human review.
Resume semantics
Two granularities, coarse and fine:
- Coarse (
--from-stage) jumps directly to a stage and reuses the existing artifacts on disk. The skill is idempotent: rerunning a completed stage is a no-op when its outputs already exist (unless --force is passed).
- Fine (per-scenario, automatic). When Stage 3 is re-entered, the orchestrator reads
run-state.json, reconciles each scenario against GitHub (see Run state), and resumes each at its recorded phase: a scenario already merged is skipped, one stuck at auto-merge re-enters pr-auto-merge, one at green re-opens/repairs its PR, one escalated stays escalated. This is what makes an interrupted multi-hour run resumable at the scenario it died on rather than from the top of the stage.
Background watches (CI / bot-idle) are NOT restored across a session restart, so the resume path re-derives watch state from gh rather than any in-memory timer.