Prompt file imported from ralfcam/restaurant-system (
.cursor/commands/sdd-to-tdd.md). Fill in{{CURSOR_AGENT_SOCKET}}before use. Copyright stays with the author.
sdd-to-tdd
You delegate the actual code/test writing to three phase subagents in .cursor/agents/:
tdd-red— writes ONE failing test; tests/** only; never source.tdd-green— writes MINIMAL source to pass it; never tests. Uses installed skills (Next.js, Supabase, shadcn) for version-correct APIs.tdd-refactor— cleans up + enforces constraints; re-runs tests/lint/typecheck/Prettier on touched source.
Support subagents:
- Installed skills (Next.js, Supabase, shadcn) — used by
tdd-greenfor APIs. docs-updater— syncsdocs/after implementation ships (you delegate it; background). Fallback finding-registrar only when Linear is unavailable (appends to a backlog doc).linear-resolver— Linear issue manager. START (execution): posts a single boundedWork started:summary comment (Problem/Approach/Out-of-scope findings; the plan file itself is never posted to Linear; no workflow-state write). CLOSE-OUT (FIX): posts the structured resolution comment (In Progress/In Review/Done are automation-owned). Any mode: registers out-of-scope findings as new, linked Linear issues (with confirmation) so deferred discoveries aren't lost.
Repo rules that govern this loop:
.cursor/rules/supabase-migrations.mdc— DB changes extend canonical baselines..cursor/rules/powershell.mdc— shell commands use PowerShell syntax..cursor/rules/linear-project-routing.mdc— configured Linear team key, dynamic configured-team projects with canonical version keyV-X.X, fail-closed allocation, and work-type M1–M9 routing.
Fix-mode data source: the Linear MCP server (get_issue, list_comments,
get_diff) for issue/bug input.
Invocation forms:
/sdd-to-tdd @path/to/SPEC.md→ FEATURE mode: decompose an existing spec document./sdd-to-tdd "new spec details…"→ FEATURE mode: draft a new spec from the inline details./sdd-to-tdd <Linear issue URL or ID>(e.g.<issue-id>or alinear.app/.../issue/...URL) → FIX mode: triage a bug/missing edge case, update the spec first, then write a regression test./sdd-to-tdd "bug: <symptom / repro>"→ FIX mode from a free-text defect when there is no issue. A/pushwhole-suite red is a valid FIX trigger only in this form (bug: <classified failure>) or as<issue-id>— never an empty/sdd-to-tdd. The trailing input after the command name is the argument.
GOLDEN RULE (applies to every mode, fixes especially)
The specification is a living document and the single source of truth. You never fix tests or application code directly to make a bug go away. You update the spec first — encode the missing business rule / edge case as a new acceptance criterion — and only then drive that criterion through Red → Green → Refactor. Patching code without updating the spec creates context debt: a future feature run, blind to the unwritten rule, will hallucinate around it and re-break the fix.
FINDINGS LEDGER (don't let discoveries evaporate — but don't let it bloat)
While triaging and running the loop you will notice problems outside the
current scope — an adjacent bug, a security smell, dead/duplicated code, a
mismatched contract, a deferred scope-question item (e.g. "the route also calls
getPublicUrl() on a private bucket — out of scope for this fix"). The right
move is never to silently scope-creep into them, and never to silently
drop them. At close-out, register each open entry as a tracked Linear issue via
linear-resolver.
During a run, the ledger lives in a run-scoped scratch file:
docs/findings/runs/<plan-slug>.md (<plan-slug> = this plan file's basename,
e.g. <plan-slug>), with a category section per bucket
(## security · ## tech-debt · ## test-debt · ## product-gaps). This
isolates one run's findings and survives an interrupted loop. The categorized
files docs/findings/{security,tech-debt,test-debt,product-gaps}.md (see the
findings README.md) are the cross-run bus /triage reads; at close-out you
merge the run file's open lines into them (Step 4C). archive.md holds
history. Working memory and the (read-only) plan table both lose execution-time
findings across a long loop, so findings are persisted to disk.
Capture discipline (keep these files lean). A residual finding is appended
only when all three hold: (1) out of scope for the current spec/criteria,
(2) won't be handled this run — NOT something a later criterion in this plan
implements (those are plan dependencies, not findings), and (3) a real
code/test/product/security issue. Do NOT record TDD-process/meta notes
("Green pre-empted Red", "fake timers hang", "criterion X covers this") and do
NOT record anything you resolve within the run — those belong in your prose,
not the durable files. Each entry: [category] · one-line title · file:line/area
· why it matters · severity · (found: <<issue-id>>/<criterion>/<phase>).
Curate continuously, merge + prune at close-out (active ledger = open only).
Every phase subagent ends its report with a mandatory ## Residual findings
block. The orchestrator writes docs/findings/runs/<plan-slug>.md directly
after each phase — that scratch path is exempt from the findings write guard —
so make that touch a revision pass, not a blind append
(see Step 3): remove entries this phase resolved in-run, dedupe/sharpen existing
ones, append only the genuinely new, and re-home miscategorized entries. Do not
Write a category file or archive.md yourself. Because
curation happens every phase, the run file stays lean in real time rather than
ballooning until close-out. At close-out, docs-updater ledger-apply merges
the run file's open lines into docs/findings/<category>.md, linear-resolver
reads those (already-curated) category files, and once each remaining finding is
filed, ledger-apply moves it to docs/findings/archive.md with its issue id
and deletes the run file (never truncate it). A finding that reaches a file
is safe; one left only in a subagent's result is lost; one left open after it's
filed or fixed is noise.
STEP 0 — PLAN MODE GATE (do this before anything else)
This command's default is Plan Mode. Cloud Agents support Plan Mode; prefer it when the launch surface exposes it. The managed-Cloud path below is a narrow one-shot exception for unattended Agent-mode launches, not a claim that Cloud lacks Plan Mode.
First, determine whether you are in Plan Mode.
-
If you ARE in Plan Mode: proceed. Producing this plan must not write any application or test files — all writes happen later, during plan execution, through the subagents.
-
If you are NOT in Plan Mode: do not assume local Agent Mode and do not stop yet. Probe the documented Cloud Agent metadata API first (this probe only — no repo reads, no edits, no subagents):
curl -fsS --unix-socket "{{CURSOR_AGENT_SOCKET}}" \ http://cursor-agent/v1/meta-data/agent/runtimeClassify from the response body, trimmed:
- Exactly
managed(Cursor-managed Cloud Agent VM) → enter STEP 0B — MANAGED CLOUD ONE-SHOT. Do not emit the Plan Mode stop. - Socket missing, HTTP error, empty body, self-hosted,
unknown, or any value other than exactlymanaged→ fail closed. Output exactly: "/sdd-to-tdd runs in Plan Mode only. Switch to Plan Mode (Shift+Tab, or the mode picker) and re-run/sdd-to-tdd <spec>." Then end the turn.
If the socket is missing immediately after boot, retry the connection once; then fail closed. Do not infer Cloud from branch name (
cursor/…), OS, or available tools./v1/meta-data/agent/runtimereturning exactlymanagedis the only positive signal. - Exactly
STEP 0B — MANAGED CLOUD ONE-SHOT (narrow exception)
Applies only after STEP 0 classified agent/runtime as exactly managed.
What this waives (only these):
- The Plan Mode requirement and the "switch to Plan Mode" stop.
- The final "stop for operator approval before execution" boundary (STEP 5).
- A second human "yes" for the exact spec create/edit and existing-test
edits enumerated in this run's
## Permissions Requested— the initiating Cloud task is that authorization. - Newly discovered Linear finding-issue creation: persist to the ledger and continue; do not auto-confirm.
Cloud one-shot does not waive (non-overridable; same methodology as local):
missing or empty input; unresolved clarification, spec conflict, or vague
inline details that belong in /design; unproven FIX evidence / unconfirmed
root cause; the ≥3-hypothesis thrash cap; unavailable Task/delegation; a phase
whose exit condition is unmet; any BLOCKED / BLOCKED (infra); skipped
tests, 0 tests collected, or infra that cannot be brought up; write-scope
violations (orchestrator touching tests/** / source; Red touching source;
Green/Refactor touching tests); failed verification (no fresh command
evidence; skipped suite treated as green); a /commit CHANGES-REQUESTED,
FAIL, or permission/verification stop; a /push BLOCKED, whole-suite-gate,
or safety stop.
Durable work-order (required before any spec/test/source mutation):
Render the complete plan in the output format below to
.cursor/plans/<plan-slug>.plan.md. This is a repository work-order, not a
silently accepted native Cursor Plan. Do not invoke CreatePlan or wait
for native plan acceptance — that blocks one-shot continuation. Hand every
phase Task this work-order path + the criterion/part id.
Then execute immediately in the same turn: START (if STEP 2B applies) → the
enumerated spec / existing-test edits → Criterion 1 Red → the rest of the
loop → on successful close-out, .cursor/commands/commit.md → on PASS,
.cursor/commands/push.md. Bound by the Execution Protocol and the
work-order text. A spec or existing-test path not listed in
## Permissions Requested still STOPS. Do not continue past a BLOCKED,
CHANGES-REQUESTED, FAIL, permission, verification, or /push safety stop.
Never gh pr ready. Never gh pr merge.
Ready brief. When the invoked issue description has a complete
## Ready brief (node .cursor/checks/ready-brief.mjs): Decisions: resolve
STEP 1A clarifications, Allowed edits: pre-grant ## Permissions Requested,
and Verification: sets the phase commands.
In-loop fix. An invocation
bug: CodeRabbit finding <local-ref> on PR #<n> from /conduct stays on the
PR branch and skips START and CLOSE-OUT.
Cloud test stack (Linux Cloud VM only). Before a fail-closed integration
or e2e phase, run sh .cursor/cloud-supabase-up.sh. Write every integration
or e2e phase command in the work-order as
set -a; . supabase/.temp/cloud-test.env; set +a; <command>, so each phase
subagent's own shell loads the env. A non-zero exit is BLOCKED (infra).
STEP 1 — CLASSIFY INPUT, THEN RESOLVE THE SPEC (source of truth)
Classify the input first:
- FIX mode — a Linear issue URL/ID (
<issue-id>,linear.app/.../issue/...) or text starting withbug:/describing a defect → do Step 1B first, then continue. - FEATURE mode — a spec file or new requirement details → continue here.
STEP 1A — VALIDATE PROJECT AND MILESTONE ROUTE
Route by the work being performed, not by command name:
- clarification/correction of an existing spec contract may remain M2 — Requirements Sign-Off;
- FEATURE/FIX implementation maps to M4 — Code Complete (Feature Freeze);
- test/audit, real-user beta validation, UAT/RC, launch-critical/security/money, and maintenance map to M5, M6, M7, M8, and M9 respectively.
Do not impose a blanket M4+ rule on /sdd-to-tdd. When a tracked <issue-id>
issue is supplied, resolve the team once, paginate list_projects,
retain nonterminal configured-team projects with canonical version key V-X.X, and
validate its current/target project
and existing milestone against this work type. Apply the allocation precedence
from
.cursor/rules/linear-project-routing.mdc.
For untracked input, record the milestone route as a planning hint only.
If one issue mixes unresolved M1–M3 design with implementation, require two linked issues: the decision/design issue blocks the implementation issue. Stop before decomposition until the governing spec is testable; dispatch must not schedule the implementation issue early.
For a tracked issue with a route conflict, missing decision, or unresolved
allocation tie, read list_comments. Resume only after an unambiguous human
answer. Otherwise render a stable Clarification required comment keyed
clarify:<RES-id>:<spec-basename>:<rule-or-ac> and stop. Local Plan Mode
requires approval before delegating linear-resolver CLARIFY; a managed Cloud
task launched from that issue preauthorizes only this bounded comment. An
operator-created automation run counts as a launch from the tracked issue. It
never authorizes a spec, scope, state, or assignment change.
The comment uses the resolver's fixed schema: source command, exact spec
evidence, one conflict/missing fact, one decision question, bounded options,
recommended default, route after resolution, and milestone hint. The tracked
issue remains with its current workflow state unchanged and is not dispatchable.
An unresolved run emits only a clarify-<issue-id> execution todo:
"Invoke the linear-resolver subagent to request the approved clarification
on , using this exact bounded comment: ." It emits no spec/TDD,
START, commit, or push todos. Re-run /sdd-to-tdd <issue-id> after the human
answer; do not continue in the comment-posting turn.
Prefer existing specs (progressive disclosure). Before treating input as new, resolve ownership via the OKF hub walk — do not open every REQ body first:
- Identify the feature area / domain (from the issue,
@file, or details). - Open the matching domain hub:
docs/specs/domains/<domain>/index.md(listed fromdocs/specs/README.md). - Pick the owning concept from the hub list; confirm with frontmatter
req_ids:on that file. - If
status: folded, followcanonical:with the OKF path map (leading/= bundle rootdocs/, so/specs/X.md→docs/specs/X.md) before reading or editing acceptance criteria. - Reuse that normative concept as the source of truth (the inline details may extend it rather than warrant a new file).
Read the trailing argument:
@<file>given → if it is a folded stub, resolvecanonical:first; then read the normative file. If it is not found, stop and ask.- Inline
"details"given → run the hub walk above for an existing owner. If one exists, propose extending it (see permission gate below). Otherwise treat the details as a NEW spec: draft the content inside the plan (do not write the file yet) underdocs/specs/<kebab-slug>.md(fallback: rootSPEC.md), to be created at the start of execution. - Nothing given → stop and ask the operator for a spec file, Linear ID,
or inline details. Empty remains invalid. A
/pushwhole-suite red is a valid FIX trigger only as/sdd-to-tdd "bug: <classified failure>"(or<issue-id>); do not treat a bare/sdd-to-tddafter lint + typecheck + test:unit red as input.
Spec writes require explicit permission. Creating a new spec file or editing
an existing one (including reconciling spec↔code drift discovered during the
loop) is a deliberate act: present the exact change and get the operator's
explicit "yes" before writing. Managed Cloud one-shot: the initiating task
pre-authorizes only the exact spec create/edit paths listed in
## Permissions Requested. A spec change discovered later, or any path not
listed there, still STOPS for a human yes. Never silently edit a spec to match
code — if they disagree, the spec wins unless the operator (or that enumerated
Cloud pre-authorization) approves changing it.
Then, before decomposing, surface clarifying questions for anything ambiguous, underspecified, or conflicting in the spec (especially acceptance criteria that aren't independently testable, missing edge cases, or unclear money/auth/state-machine rules). Do not invent requirements. If clarifications are needed, ask them and pause — do not guess your way into a plan.
If inline "details" are too vague to draft testable acceptance criteria
from — even after clarifying questions — stop and point to /design instead
of inventing scope. /design's dialogue-driven spec authoring exists
exactly for this case (a genuinely new concept with no shape yet); /sdd-to-tdd
decomposes an already-shaped spec, it does not originate one from a vague idea.
Harden the spec with a named elicitation method before decomposing. A vague "look for gaps" produces vague answers; a named reasoning method forces a specific angle (BMAD advanced-elicitation). Run at least one against the spec and its acceptance criteria, and fold the results into the clarifications above:
- Pre-mortem (default): assume this feature shipped and caused a money/auth incident — work backward to the acceptance criterion that was missing.
- Inversion / red-team: ask how a criterion could be satisfied by a test that
passes while the behavior is still broken, then tighten the criterion so that
vacuous pass is impossible (this directly defends the skip/always-green failure
mode this loop exists to prevent).
Surface what the method exposes as clarifying questions or proposed criteria; per
the permission gate, any resulting spec edit still needs the operator's explicit
"yes" (managed Cloud: only if that exact path is already listed in
## Permissions Requested; otherwise STOP).
Scope-deferral → ledger. Whenever a scope question is resolved by excluding something (the operator says "stay scoped / file it separately", or you propose keeping an adjacent problem out), add that excluded item to the Findings Ledger immediately. A deferral is a decision to track it elsewhere, not to forget it.
STEP 1B — FIX MODE: TRIAGE, THEN UPDATE THE SPEC FIRST
Only for bug/issue input. The order is non-negotiable: spec before tests, tests before code.
- Reproduce / evidence. If a Linear issue ID/URL was given, fetch it via
the Linear MCP (
get_issue, pluslist_comments/get_difffor context). Capture: observed vs. expected behavior, the repro/trigger, the affected area, and any recent changes in that area (git log/get_diff) that could explain when the behavior regressed. For free-textbug:input, restate the defect and the expected behavior. Do not proceed on a symptom alone — if you cannot reproduce or evidence the failure, say so and ask rather than guessing at a cause. - Pattern compare. Before hypothesizing, compare the failing area against
a working sibling (a similar route/component/flow that behaves correctly)
or the owning hub concept's documented model. Find the sibling file with
Grep/Glob if you only have a path pattern; use
codegraph_exploreonce you have a named symbol — per .cursor/rules/codegraph.mdc. A working sibling that handles the same class of input correctly usually reveals exactly what the failing path is missing, and grounds the hypothesis in evidence rather than speculation. - Single hypothesis → map to missing/wrong AC. Form ONE hypothesis for
the root cause (not a list of possibilities to try in sequence) and test it
against the evidence before naming the fix. Once confirmed, identify which
spec rule was absent or wrong that allowed the bug — e.g. an unhandled edge
case, a missing invariant, an incorrect state transition. Name the owning
spec via the STEP 1 hub walk (
docs/specs/domains/<domain>/index.md→ concept →req_ids:/ folded→canonical:; e.g. billing → payments-billing hub then<issue-id>/ADR-<issue-id>; job FSM → jobs-requests hub). Do not describe the fix as "change line X"; describe it as "the spec must require Y". A pre-mortem ("assume the bug already shipped and caused harm — which spec rule, had it existed, would have stopped it?") is the structured way to name this missing constraint. - Update the spec FIRST (with explicit permission), then Red regression.
Propose the exact addition to the owning spec — a new business rule / edge
case / acceptance criterion that, had it existed, would have prevented the
bug. Get the operator's explicit "yes" (per the Step 1 permission gate).
Managed Cloud one-shot: list that exact spec path in
## Permissions Requested; the initiating task is the yes — still STOP if the addition is ambiguous or unproven. Then this spec edit becomes the first execution action, ahead of any test or code. The newly added acceptance criterion is what feeds Step 2/3: the Red phase writes a regression test for that criterion; it must fail on today's code (reproducing the bug) before any fix.
Cap the thrash. If ≥3 hypotheses (step 3) have been proposed and tested without landing on a confirmed root cause, stop iterating and escalate to the operator as an architecture question rather than continuing to guess — this usually means the failure spans a design boundary the spec doesn't cleanly own, which needs a human decision, not a fourth patch attempt.
Never go straight to Green/code on a bug. If the operator declines the spec
update (local), or the required spec path cannot be enumerated in
## Permissions Requested (managed Cloud), stop — do not patch code around an
unwritten rule (that is the context debt this workflow exists to prevent).
STEP 2 — DECOMPOSE INTO A TEST-FIRST EXECUTION PLAN
Extract every acceptance criterion from the spec and turn each into one or more independently verifiable, ordered TDD steps. For each step define:
- Criterion id + one-line behavior statement.
- Risk priority
P0–P3(BMAD risk-based prioritization):P0= money, auth/authz, data-integrity or state-machine invariants (highest blast radius);P1= core feature behavior;P2= secondary paths;P3= cosmetic/low-impact. Drives ordering (below) and anchors the Refactor adversarial pass. ThisP0–P3class is also the Linear-priority signal: when a finding from this run is filed (Step 4C), map it through the priority crosswalk indocs/findings/README.md(P0 → Urgent,P1 → High,P2 → Medium,P3 → Low) solinear-resolversets a priority consistent with/auditand/triage. - The exact test to write: target file path (
tests/unit/...,tests/integration/...,tests/e2e/..., ortests/e2e/deployed/...), test name (named after the behavior), and the precise assertion / expected vs. actual. - The test command that will run it (e.g.
pnpm test:unit <path>,pnpm test:integration <path>,pnpm exec playwright test <path> --project=chromium-desktop, orpnpm deployed/pnpm deployed:mutating). - Dependencies/ordering (which criteria must be green first).
Reuse the existing test suite. For each criterion, prefer the existing test
file that owns the area (tests/unit/**/*.test.ts,
tests/integration/**/*.integ.test.ts, tests/e2e/**/*.spec.ts,
tests/e2e/deployed/*.deployed.spec.ts) and existing fixtures/helpers/seeds;
only propose a new test file when no owner exists. If satisfying a criterion
would require modifying, renaming, or deleting an existing test (not just
adding one), flag it explicitly in ## Permissions Requested. Local Plan Mode:
the operator's explicit permission is required before the Red phase touches it.
Managed Cloud one-shot: listing that exact test path there is the
pre-authorization; a later existing-test edit not listed still STOPS.
Order steps so each builds on green predecessors, and within what dependencies
allow, drive higher-risk criteria first (P0 before P3) so the costliest
behavior goes through Red→Green→Refactor while attention is freshest. Prefer the
smallest slice that proves one behavior. Do not reserve e2e/Playwright for
last — order by risk + dependencies. When a criterion is split across
layers, place e2e after its unit/integration prerequisites; otherwise an e2e
criterion may run as soon as its deps are green (it is a first-class Red→Green→
Refactor path, not an afterthought).
Prefer the lowest test layer that can actually prove the criterion — and that can run here. Choose the layer from this table (expand capability, not e2e volume — do not duplicate the same assertion at three layers):
| Criterion proves… | Layer | Path | Command / infra |
|---|---|---|---|
| Pure logic, money math, transitions, mocked route guards | unit |
tests/unit/**/*.test.ts |
pnpm test:unit <path> — no infra |
| Real handler + Postgres/RLS/cookies/seed personas | integration / rls |
tests/integration/**/*.integ.test.ts |
pnpm test:integration (fail-closed); local Supabase up + seeded |
| Multi-page portal UX, wizard submit, role chrome, Filters/View IA | e2e |
tests/e2e/<domain>/**/*.spec.ts |
Playwright project; seed auth via global-setup / seedStorageState; @p0/@p1/smoke placement as appropriate |
| Preview env / live cron auth / CONTRACT PI 409 / degradation | deployed |
tests/e2e/deployed/*.deployed.spec.ts |
pnpm deployed or pnpm deployed:mutating; prefer after local e2e green |
| Live Stripe Connect / screen-reader / visual theme / C5-preview | manual-UAT |
runbook stamp only | Do not fake Vitest/Playwright |
Default to a unit test with a mocked Supabase client whenever the behavior is decidable in code. Choose integration/RLS when Postgres/RLS or a real handler decides the outcome. Choose e2e when the AC requires multi-page portal UX that unit/integ cannot prove. Choose deployed for preview-env / live-cron contracts. Splitting into unit gate + thin RLS + one e2e shell is fine and often correct (API 403 at unit, policy at integ, Filters chip at e2e).
For every e2e criterion the plan MUST name: the tests/e2e/... path,
@p0/@p1/smoke placement, seed persona (or storage-state helper), and the run
command (pnpm exec playwright test <path> --project=chromium-desktop or
pnpm test:e2e:chromium <path>).
Infra-awareness is mandatory for integration/RLS, e2e, and deployed criteria
(see .cursor/rules/test-execution-integrity.mdc
for why integration suites silently skip and report green; the same skipped≠pass
doctrine applies to Playwright). For any criterion whose test is
integration/RLS, the plan MUST record an explicit execution precondition:
local Supabase is up and seeded (npx supabase start && npx supabase db reset --local). Fail-closed is unconditional (see
test-execution-integrity):
a down DB fails hard instead of skipping. For e2e: local app + seeded DB (or whatever
global-setup needs) so storage state and the target UI exist — a server-down
or empty-seed run that skips/collects 0 tests is BLOCKED, never Red/Green. For
deployed: preview URL + required secrets for the pack. If that infra cannot be
brought up at execution time, the loop STOPS for that criterion — a skipped
suite is never accepted as Red or Green.
Some criteria are not automatable — classify them manual-UAT and defer, do
NOT fake a test. A few acceptance criteria cannot be proven by Vitest/Playwright
in this loop because they depend on a real external system, a human judgment, or
inherently-manual judgment — e.g. live third-party flows needing provider
approval (real Stripe ACH / Connect onboarding), screen-reader / manual a11y
passes, visual themes (light/dark). Preview-env contracts that can be
automated belong in deployed, not manual-UAT. Judge this from the criterion's
own nature, not from any docs-legacy/UAT runbook (UAT has no maintained writer
and may be stale — treat it as a manual/deployed archive, not a source of truth;
docs/specs is authoritative). For a manual-UAT criterion: do not write a
vacuous always-green test; mark it manual-UAT in the plan, point to the
relevant manual runbook if one exists, and exclude it from the automated
Red→Green→Refactor loop. Faking automated coverage for an inherently-manual
criterion is the same failure as accepting a skipped suite.
STEP 2B — EXECUTION START: ANNOUNCE WORK ON THE LINEAR ISSUE
This step runs only during plan execution — after operator approval on a
local Plan Mode run, or after the managed-Cloud work-order is written at
.cursor/plans/<plan-slug>.plan.md. Never during plan production. Plan Mode
stays read-only; Cloud plan production is read-only except that work-order
write. Do not call save_issue / save_comment / save_document while
producing the plan.
When FIX mode was invoked with a Linear ID/URL, or FEATURE mode's plan has
linear_issue: <issue-id> (not none): as the first execution action, before
the approved spec edit (FIX) or Criterion 1 Red (FEATURE), delegate:
"Use the linear-resolver subagent to start work on <> (plan: ), posting this bounded summary as the Work started: comment:" + the plan's ## Linear Plan Digest block. Invoke that Task with run_in_background: true. Do not wait, poll, or AwaitShell for its report before the approved spec edit or Criterion 1 Red. In the emitted plan, replace <plan-slug> with this plan file's basename; this command file keeps the placeholder.
One payload (linear-resolver posts it verbatim; never the plan file itself):
The ## Linear Plan Digest block, filled in with Problem (2–3 sentences —
observed vs expected, the missing constraint), Approach (2–4 sentences —
the design constraint shaping this wave/plan), and Out-of-scope findings
(ledger titles + severities only, or "none"), plus Full plan: set to
not posted to Linear (size-bounded digest) · local copy <plan-file-basename>.
Digest labels MUST be disjoint from the CLOSE-OUT headers (Root cause:,
Spec updated:, Regression test:, Fix:, Verification:, Commit:,
Follow-up:) — use Owning spec: not Spec:; omit FIX-mode root cause
(close-out owns it). Rewrite any @Cursor token to "the Cursor integration"
before the MCP call. The digest MUST fit
START_SUMMARY_MAX_CHARS (see
.cursor/hooks/lib/linear-comment-size-policy.mjs);
the ## Execution Protocol, the per-criterion TDD Execution Loop, the FSM
seed-state tables, the Traceability Matrix, and the Docs Sync packet stay
local to the plan file and git — never post them to Linear, chunk them
across multiple comments, or spawn a nested Task to upload them. Idempotency:
list_comments for a Work started: comment with this plan slug; create
when absent, update by id when the Full plan: line is stale.
Scope: the invoked issue only — do not walk relatedTo, parent, or
children. Skip for free-text bug: input with no tracked issue, and skip
FEATURE when linear_issue is none.
The summary is unconditional whenever the issue exists. A non-BLOCKED START
always posts this run's bounded summary to the corresponding issue — In Review
and terminal included. A "Do not change status" prompt (or equivalent) does
not skip START: still delegate linear-resolver START; still do not
call save_issue. START does not write workflow state. In Progress
is derived from a linked draft/open PR (see
.cursor/rules/linear-automation.mdc);
until that PR exists, the issue may remain Todo.
START is non-blocking and must not stall the loop: invoke
linear-resolver START with run_in_background: true and continue immediately.
If it later returns ## Linear — BLOCKED, that is visibility only. Non-blocking
does not make the summary optional — only ## Linear — BLOCKED or the
absence of a tracked issue exempts it. Do not auto-assign. In Progress / In
Review / Done stay automation-owned — START posts the Work started: comment
only (see
.cursor/rules/linear-automation.mdc).
The plan you emit MUST include a start-linear todo as the first execution
todo when this step applies, phrased as a subagent-invocation imperative.
That Task is run_in_background: true. Do not wait for it before spec
edits or Criterion 1 Red. If other todos were assigned (whole-plan Run),
continue them immediately after the START Task is launched. A solo
start-linear click launches only START. A stale start-linear todo
cannot override STEP 2B: if it waits for START, ends the turn, or lacks
run_in_background: true, ignore that wait/stop wording and follow this
step.
STEP 3 — DEFINE THE RED→GREEN→REFACTOR EXECUTION LOOP
The plan's execution body is a loop, run once per criterion, in order. For each criterion, lay out three delegated handoffs with explicit exit conditions:
- Red — "Use the
tdd-redsubagent to write the failing test for ", handing it the criterion, a scoped spec excerpt (criterion + minimal related AC — not the whole REQ), and the target test path/name. Exit condition: the new test is RED for the right reason (assertion/missing-symbol failure, not a harness error). Forbid it from touching source. - Green — only after Red confirms failure: "Use the
tdd-greensubagent to make pass" with the minimal source change. It may consult the matching installed skill for version-correct framework/library APIs. Exit condition: target test GREEN, typecheck clean, no regressions. Forbid it from editing tests or the spec. - Refactor — only after Green: "Use the
tdd-refactorsubagent to clean up and re-verify". Exit condition: tests still GREEN,pnpm lint(0 warnings),pnpm typecheckclean, andpnpm exec prettier --checkclean on the source files this criterion touched, no behavior change. Its report also returns an adversarial## Residual findingspass (treat a bare "none" as suspect), a concern-ordered Suggested Review Order (risk-taggedpath:linestops), and any Reusable pattern candidate — the orchestrator appends the latter two todocs/verifier-reports/tdd/<plan-slug>.mdafter each Refactor (Steps 4D and 4E read that log).
Run subagents sequentially (each phase depends on the previous one's result); do not parallelize phases of the same criterion. Only advance to the next criterion after its Refactor exits green. If any phase reports BLOCKED, stop the loop and bring the blocker back to the operator rather than improvising.
If Green reports pre-RED source for this criterion, route back through Red —
never "keep and test after." tdd-green deletes/reverts source it finds
already written before this handoff's verified RED (see its Hard limits). If
that happens, do not accept the resulting Green as satisfying the exit
condition until Red's failure for the current test is confirmed first; re-run
or re-verify Red before advancing. Pre-written code adapted after the fact is
not a substitute for having watched the test fail.
Dispatch hygiene. Each Task dispatch to tdd-red/tdd-green/tdd-refactor
hands the subagent only: the plan file path + the criterion/part id ("read
that section first"), and any interfaces/decisions from prior phases it
actually needs to do this phase — never a pasted dump of the session history
or a prior phase's full report. The subagent's own report already lives in
this thread and, for Refactor, in docs/verifier-reports/tdd/<plan-slug>.md;
re-sending it back as dispatch context is redundant and just bloats the call.
Expect each subagent to return status + a short summary — durable detail
belongs in the tdd log or findings run file, not in a longer return message.
This mirrors /audit's per-dispatch-params pattern (hand only the spec/report
path or part text + report path, never the full brief) — the same discipline
applies to every phase dispatch here. The model for each named subagent is
pinned only in that agent's frontmatter under .cursor/agents/<type>.md.
Never pass model on the Task call. Omitting model is what lets the
agent frontmatter apply; passing the parent chat's model overrides the pin
and is forbidden. Pass model only if the operator explicitly requested a
one-off override for this run.
Run a findings revision pass after EVERY phase — to the run file,
immediately. Each tdd-red/tdd-green/tdd-refactor report ends with a
mandatory [category]-tagged ## Residual findings block. The instant a phase
returns — before the next Task call, never deferring to close-out — open
docs/findings/runs/<plan-slug>.md (create it on first finding; write into its
matching ## <category> section — security · tech-debt · test-debt ·
product-gaps) and reconcile, don't blind-append. This touch is mandatory for
durability anyway, so use it to curate:
- Resolve in-run. If this phase fixed or obsoleted an existing open
- [ ]entry, remove that line (it's no longer outstanding — and must NOT become a Linear issue at close-out). This is how "later phase fixed it" items leave the run file instead of lingering as noise. - Dedupe / sharpen. If a reported finding already has an open entry, update it
in place (tighter
file:line, better severity) rather than adding a second line. - Append the genuinely new. Add a line only for findings with no existing
entry, under its
## <category>section:- [ ] <title> · <file:line/area> · <why> · <severity> · (found: tdd/<plan-slug>/<criterion>/<phase>). - Re-home. If a phase reveals an entry belongs in a different category, move
it to the right
## <category>section. After the pass, the run file should hold only currently outstanding debt. Do not Prettier it —docs/findings/runsis prettierignored.
After each tdd-refactor phase — append to the durable close-out log (same
pass, before the next Task call). Parse that phase's report for its
Suggested review order: and Reusable pattern: lines (verbatim). Append a
### <criterion-id> section to docs/verifier-reports/tdd/<plan-slug>.md
(create the file on first refactor; <plan-slug> = this plan file's basename,
e.g. <plan-slug>). Do not Prettier this log —
docs/verifier-reports is prettierignored. This log is the durable source
Steps 4D/4E and /commit read — do not rely on transcript memory. Red/Green
phases do not append to this log (Refactor only). After the approved spec
edit, pnpm exec prettier --write that spec path.
Filter before you write — the run file is an open-debt worklist, not a journal. Append a finding only if it is (1) out of scope, (2) not something a later criterion in this plan will implement, and (3) a real issue. Drop process/meta notes and anything resolved within the run. This keeps the run file small so it stays useful at close-out merge. Do not expand the current criterion to chase a finding — it gets a tracked issue at close-out, not a detour now.
A skipped test never satisfies an exit condition (doctrine:
.cursor/rules/test-execution-integrity.mdc).
If a phase reports BLOCKED (infra), treat it exactly like any other BLOCKED:
stop the loop and surface the infra remedy. For integration/RLS criteria, hand
each phase the strict run command so a down DB fails loudly:
pnpm test:integration <path>.
For e2e criteria, hand each phase
pnpm exec playwright test <path> --project=chromium-desktop (or
pnpm test:e2e:chromium <path>) and treat server-down / missing storage state /
0 tests collected as BLOCKED (infra). For deployed, hand pnpm deployed or
pnpm deployed:mutating as the plan specified.
Fix-mode nuances: the Red test is a regression test that must reproduce the bug (fail on current code); the Green change is the minimal fix that must not break any existing test; the Refactor phase re-verifies the entire relevant suite (not just the new test) to confirm no regression elsewhere.
Close-out sequence (mandatory)
After the loop completes for the feature (all criteria green through Refactor),
run close-out steps in this order — do not delegate docs-updater until
Steps 4D and 4E have written to the tdd log and the Docs sync packet is assembled:
| # | Step | Owner | Writes to |
|---|---|---|---|
| 1 | 4D — Collate review trail | Orchestrator | docs/verifier-reports/tdd/<plan-slug>.md |
| 2 | 4E — Traceability final + pattern list | Orchestrator | Same tdd log |
| 3 | Packet — Assemble Docs sync packet | Orchestrator | Thread (markdown block) |
| 4 | 4 — Docs sync | docs-updater |
docs/** + thread report |
| 5 | 4C — Findings merge + register | docs-updater ledger-apply + linear-resolver |
docs/findings/** |
| 6 | 4B — Linear close-out | linear-resolver |
Linear (FIX only) |
| 7 | Format — Prettier this run's dirty paths | Orchestrator | git status --porcelain paths (pnpm exec prettier --write <path> …; never .) |
| 8 | 4G — CLI lives on /push (do not run here) |
Orchestrator | none — /push runs coderabbit-gate.mjs --branch-diff |
| 9 | 4F — Commit / push handoff | Orchestrator | Local: operator /commit. Managed Cloud: execute commit.md; on PASS execute push.md |
Keep docs-updater is_background: true, but do not hand off to /commit
(local) or execute .cursor/commands/commit.md (managed Cloud) until
the docs-updater report appears in the thread (wait/poll the Task) and the
close-out format pass (step 7) has run. STEP 4G does not run the CLI —
continue to STEP 4F. A missing docs-updater report → /commit
→ CHANGES-REQUESTED.
STEP 4D — ASSEMBLE THE REVIEW TRAIL (hand off to /commit)
Read docs/verifier-reports/tdd/<plan-slug>.md — the accreted per-criterion
sections the orchestrator appended after each Refactor phase (Step 3). Each
section holds that criterion's Suggested review order: (path:line stops, the
1–3 costliest risk-tagged [auth]/[billing]/[schema]/[public-api]/
[security]). A raw diff is in file order, which is rarely the order that builds
understanding (BMAD checkpoint-preview) — so collate those sections into a single
change-level Suggested Review Order, grouped by concern and ordered
highest-risk-first, and append it to the tdd log as
## Suggested Review Order (collated) in
docs/verifier-reports/tdd/<plan-slug>.md. Do not duplicate this into the
owning spec body — specs stay normative AC; /commit reads the tdd log (and
optional thread summary). Do not mine agent transcripts — the log is the
source of truth. This is a reading guide, not a verdict — /commit still runs
the gates and emits PASS/CHANGES-REQUESTED/FAIL.
STEP 4E — RETROSPECTIVE: PROMOTE PATTERNS + FINALIZE TRACEABILITY
Before docs sync, capture what's reusable and durable:
- Pattern list. Read every
Reusable pattern:line fromdocs/verifier-reports/tdd/<plan-slug>.md(not transcripts). Assemble the list for the Docs sync packet (patterns_to_promote). Promotion intodocs/TRD/testing/happens viadocs-updaterin Step 4 — do not let patterns accrete in the phase agent prompt files. - Traceability (final). Append or overwrite
## Traceability (final)in the same tdd log. One row per shipped criterion (shipped) and permanual-UATcriterion (manual-uat, no fabricated test path). Overwrite a prior## Traceability (final)section on re-run (idempotent). Per-criterion Refactor sections above stay — do not delete them.
## Traceability (final)
Run: <YYYY-MM-DD> · plan: <plan-slug> · issue: <issue-id> | none
| Criterion | Spec ref | Test file::name | Source file(s) | Risk | Status |
| --------- | ------------ | ---------------------------------------- | -------------- | ---- | ---------- |
| AC-1 | REQ-001 §… | <test-file>::… | <source-file> | P1 | shipped |
| C10-theme | REQ-001 §… | — | — | P3 | manual-uat |
Set traceability_log in the packet to this file path.
Run metrics. Immediately after ## Traceability (final), append or overwrite
## Run metrics in the same tdd log — a small observability stamp so a run's
health is visible without re-reading the whole transcript:
## Run metrics
Run: <YYYY-MM-DD start> → <YYYY-MM-DD end> · plan: <plan-slug>
Criteria: <N shipped> shipped · <M> manual-uat · <N+M> total
Phases delegated: <count of tdd-red/green/refactor Task calls this run>
Back-loops: <criterion id: N extra Red/Green/Refactor cycles it took>, … | none
BLOCKED events: <count> — <one-line reason each, e.g. "C29: infra (Supabase down)"> | none
Issues: <N filed> filed · <M> attached-to-existing · <K> left on ledger (below floor/cap) — cap 3/run
The Issues: line is populated from linear-resolver's STEP 4C REGISTER
FINDINGS report (filed/attached/left-on-ledger counts) — omit it (write n/a)
only when the ledger was empty and STEP 4C was skipped.
Verify the loop's own artifacts before assembling the packet. Run
node .cursor/checks/harness-lint.mjs <plan-slug> — it lints docs/verifier-reports/tdd/<plan-slug>.md
(criterion sections, the two required close-out headings, every traceability
row's status/test-ref) and the findings ledger (open-line format, and the
spec: token on any product-gaps.md entry this run merged) — a check on the
loop's own trajectory, not the shipped code. If it exits non-zero, fix the
cited artifact (append the missing section, correct the malformed line) before
proceeding to the Docs sync packet; do not silence a violation by weakening the
check.
Docs sync packet (assemble before Step 4)
After 4E, paste this block in the thread and pass it to docs-updater:
## Docs sync packet
- plan_slug: <plan-basename>
- spec: docs/specs/<file>.md
- mode: FEATURE | FIX
- linear_issue: <issue-id> | none
- criteria_shipped: [AC-1, AC-2]
- criteria_manual_uat: [C10-light-theme] | none
- req_ids: [REQ-001]
- source_paths: [<source-path>]
- test_paths: [<test-path>]
- architecture_touch: [<concept>] | none
- uat_flows_to_stamp: [<issue-id>-<flow-name> (Design-And-Patterns map row)] | none
- patterns_to_promote: ["<pattern>"] | none
- traceability_log: docs/verifier-reports/tdd/<plan-slug>.md
- drift_flagged: none | ["REQ-001 rule X vs <file>:<line>"]
- skip_reason: none | docs-only | no-implementation-impact
criteria_manual_uat: Plan criteria classifiedmanual-UAT— listed for traceability but exempt from automated test row requirement in/commit.skip_reason: Must be set explicitly in thread when skipping Step 4; no silent auto-skip on "no user-facing impact."
STEP 4 — SYNC DOCS AFTER THE FEATURE SHIPS
After Steps 4D and 4E and packet assembly, delegate the docs-updater
subagent in the background:
"Use the docs-updater subagent to sync docs using this packet:" + the full
Docs sync packet block above.
- Skip only when
skip_reasonis set explicitly in the thread (docs-onlyorno-implementation-impact). - Do not commit doc edits yourself;
docs-updaterleavesdocs/dirty for human review. - Wait for the docs-updater report in-thread before proceeding to 4C/4B.
After 4C/4B, run the format pass, then STEP 4G (do not run
coderabbit-gate.mjshere), then STEP 4F (local: point to/commit; managed Cloud: execute commit then push on PASS). The CLI lives on/push.
STEP 4C — MERGE + REGISTER OUT-OF-SCOPE FINDINGS (any mode)
First, merge the run file into the bus through ledger-apply. If
docs/findings/runs/<plan-slug>.md has open - [ ] lines, delegate one
docs-updater ledger-apply per category file: "Use the docs-updater subagent
to apply ledger-apply to docs/findings/<category>.md: merge these open
run-file lines — reconcile, don't blind-append: dedupe/sharpen against any
existing open entry, drop anything a later phase resolved." Do not Write the
category file yourself.
product-gap / spec-gap entries must include a primary spec path resolved
from the docs/specs/README.md catalog,
or folded→canonical: (leading / = bundle root docs/, so
/specs/X.md → docs/specs/X.md) — e.g.
· spec: docs/specs/<spec-file>.md. Sharpen run-file lines before
merge if the spec path is missing.
After the merge the category files hold this run's still-open findings alongside
the standing backlog, and are the single set linear-resolver reads below. (The
run file is deleted after the prune step. Never truncate it.)
If the (now-merged) docs/findings/*.md files (or the plan's Out-of-Scope Findings
table) have open entries, this is a required close-out step — a finding that isn't
tracked is a finding that's lost, but "tracked" means on the ledger, not
necessarily filed as a new Linear issue (see the Issue-filing policy in
docs/findings/README.md — filing is throttled by design). After docs sync
(Step 4), delegate linear-resolver to register the findings: "Use the
linear-resolver subagent to register the open out-of-scope findings from
docs/findings/ (security, tech-debt, test-debt, product-gaps). Managed Cloud
close-out is attach-only: leave new-issue findings on the ledger and
continue.", pointing it at those files (each entry:
category, title, file:line/area, why it matters, severity hint) plus the source
issue ID/URL (if any) to link back to. Expect most entries to come back
"left on ledger" rather than filed — that is the intended outcome, not a
shortfall.
Scope — this step files only THIS run's incidental findings. /triage
owns findings + Linear Triage intake, de-duplication, and terminal cleanup;
/dispatch owns full scoped Backlog metadata finalization and the separate
selected daily activation wave. This loop owns neither. Both registration
routes use linear-resolver for de-duplication. Managed Cloud close-out is
attach-only and keeps new-issue findings on the ledger. Local Plan Mode
confirmation before net-new issues may still apply the filing floor,
attach-over-create ladder, and per-run cap of 3. Preserve source
severity/effort and the shared label taxonomy from docs/findings/README.md
so dispatch can schedule without guessing.
- Managed Cloud close-out is attach-only and keeps new-issue findings on the ledger. Managed Cloud one-shot does not auto-confirm new Linear finding issues: persist them on the durable ledger and continue. Do not stop the loop for that confirmation. Local Plan Mode still stops for operator confirmation before creating net-new work. Attach-over-create and left-on-ledger outcomes do not need a new-issue yes.
- New ordinary issues are filed in Backlog without a cycle, linked to the
source issue, and never auto-assigned or marked done. Their source
severity/effort remains in the description;
/dispatchlater finalizes project/milestone/priority and an evidence-backed optional estimate across the scoped Backlog portfolio, then counts existing active work and promotes only approved daily-wave IDs to Todo/current cycle. The/triageUrgent fast lane is not inferred here. - Team/issue resolution: FIX mode reuses the source issue's team; FEATURE mode uses the team the operator names (ask if ambiguous).
- Fallback when Linear is unavailable (no team/issue context, MCP down, or
linear-resolverreturnsBLOCKED): the findings are already persisted in thedocs/findings/*.mdfiles, so nothing is lost — surface them to the operator for manual triage and leave them dirty for human review. linear-resolveronly reads the active findings files and returns a finding→outcome mapping (filed / attached / umbrella / left-on-ledger; it makes no local file writes). After it reports, delegate ledger-apply to prune: "Use the docs-updater subagent to apply ledger-apply to the named category files: archive each filed or attached line intodocs/findings/archive.mdwith its outcome (→ <issue-id> (filed)or→ <issue-id> (attached)), then deletedocs/findings/runs/<plan-slug>.md." Never truncate the run file. Entries reported "left on ledger" (below floor or cap overflow) stay in the category file untouched — do not archive them;/curateowns their eventual fate (keep, attach, or TTL expiry)./triagestill owns intake of lines that clear the filing floor. Category files must end the run holding only still-open findings (filed/attached ones removed), andruns/must not retain this plan's scratch — this is what keeps both from growing without bound.
Skip the register step only if the run file, all active docs/findings/*.md
files, and the plan table are empty (nothing to merge or file).
STEP 4B — FIX MODE: CLOSE OUT THE LINEAR ISSUE
Only when FIX mode resolved a tracked issue (a Linear ID/URL was given). After
Steps 4, 4C (and findings registration when applicable), delegate the
linear-resolver subagent to post a structured resolution comment on the
issue, handing it: the issue ID, the root-cause constraint, the updated spec
path, the regression test path, the changed source files, and the verification
results. It posts the comment only — no workflow state write (save_issue
for In Review/In Progress/Done is forbidden in CLOSE-OUT; In Progress is
derived from a linked PR, not START; Linear automations own In Progress/In
Review/Done). The
tree is still dirty/uncommitted; commit + push + operator merge are pending.
State this as the final Linear step of a fix. Skip entirely for free-text
bug: input with no tracked issue.
In Review is automation-driven: team comment/message automation on the
close-out comment and/or GitHub PR review activity / ready-for-merge once a
linked PR exists (see
.cursor/rules/linear-automation.mdc).
In Progress fires when /push opens or updates a linked draft/open PR;
until then the issue may remain Todo. /commit then /push are the
consequential next steps that advance the issue toward Done. Advancing to
Done is Linear's team-configured GitHub automation
(On PR merge → Done, Settings → Workflows & automations) reacting to a
closing-linked PR merging to the default branch once the operator merges
it — not a state write by any agent. The sequence: /commit re-verifies
the gates ran green-and-executed, reviews the shipped diff against the
criteria, emits a PASS/CHANGES-REQUESTED/FAIL verdict, and on PASS
commits the run's work locally with a Linear closing magic word
(Fixes / Closes / Resolves <issue-id>) in the message. /push then
publishes that commit: if it lands directly on a PR to the default branch,
the automation fires once the operator merges it, with no further prep. If
it lands on an accumulator branch (e.g. staging) that gets promoted later
via a batch PR, /push <promotion-PR-URL> is what guarantees that promotion
PR itself carries the closing words before the operator merges it — Linear
does not resurface links from commits already merged into an earlier branch.
Neither /commit nor /push performs a Linear state write, and neither
ever merges. Local: leave the tree dirty (no commit) and point the
operator to /commit. Managed Cloud: after the format pass, execute
.cursor/commands/commit.md in this turn; on PASS execute
.cursor/commands/push.md (STEP 4F).
Combine with finding registration. If Step 4C already registered findings, reference the spun-off issue IDs in the close-out comment. Do not re-register.
STEP 4G — LOCAL CODERABBIT CLI LIVES ON /push
After 4C/4B and the close-out format pass, do not run coderabbit-gate.mjs
here. The mandatory advisory local CodeRabbit attempt is /push's job:
one committed-branch pass against origin/staging before git push.
/commit does not run the CLI.
- Do not write a work-order or spawn the CLI during close-out.
- Do not write the receipt or fingerprint into
docs/verifier-reports/tdd/<plan-slug>.md. - Continue to STEP 4F (
/commit, then/pushon managed Cloud)./pushrecordsclean,findings, orunavailableon its ignored receipt and, when needed, on the PR body.
STEP 4F — COMMIT / PUSH HANDOFF (after format pass; CLI on /push)
After 4C/4B, the
Truncated - read the full file at https://github.com/ralfcam/restaurant-system/blob/e7b142a926a9184ed90d263d92fb22f524f54bfe/.cursor/commands/sdd-to-tdd.md.
