Imported from Marsouino/test_claude_3 (
.cursor/skills/tdd-step/SKILL.md). Install upstream withnpx skills add Marsouino/test_claude_3 --skill tdd-step. Copyright stays with the author.
TDD Step Skill
Execute one full red-green-refactor-commit cycle for a single step, with strict phase discipline.
In manual mode (default), the human confirms scope at the start and pushes at the end.
In autopilot mode (when invoked with --autopilot), the scope
confirmation and per-cycle commit approval are skipped, and the cycle
auto-chains to the next uncommitted slice until a stop condition is
met. Pushing remains a human action in both modes: autopilot commits
locally and asks the human to push when the run exits. See
docs/agent/norme_autopilot.md for the full contract.
When to use
- A single, well-defined step is ready to be implemented.
- The step is small enough to be testable and committable as one unit.
- The user asks to run a TDD step, implement a step, or execute a slice.
- The user wants to chain through multiple consecutive slices with
--autopilot.
Do not use
- When the idea is still vague. Clarify scope with the human first, then use
/plan. - When the step is too large and should be decomposed. Use
architect-agentfirst. - When multiple unrelated steps are mixed. Refuse and ask the human to narrow scope.
Mode selection
Detect the mode from the invocation:
/tdd-step(no flag) → MANUAL mode/tdd-step <SLICE-ID>→ MANUAL mode targeting that slice/tdd-step --autopilot→ AUTOPILOT mode/tdd-step <SLICE-ID> --autopilot→ AUTOPILOT mode starting at that slice/tdd-step <SLICE-ID>..<SLICE-ID> --autopilot→ AUTOPILOT mode bounded to range
An independent, optional flag overrides the GREEN phase engine for
this invocation only. The durable choice is the harness: pin in
.cursor/agents/green-agent.md (cursor or claude; cursor when
absent), which majordome reads itself:
- (no flag) → pass nothing about the engine; majordome resolves it from the pin (see its "Green phase engines" section)
--green=claude→ headless Claude Code for this run--green=cursor→ the green-agent Cursor subagent for this run
When a flag is present, pass it to majordome in the handoff as
green engine override: <engine>. Combines freely with manual and
autopilot modes. The claude engine runs through the deterministic
runner scripts/governance/claude_green.py (majordome invokes it;
its exit code is the harness verdict). A harness failure (exit 3:
binary absent, timeout, usage limit, no parseable status after the
binary ran) falls back to the cursor engine for that phase and is
reported; it is NOT a phase failure and does not increment any
attempt counter. A configuration error (exit 2: invalid harness:
value, unapproved or missing claude-model: / claude-effort: pin
on the green-agent charter, inadmissible write scope, or claude
ignoring/rejecting those flags) is BLOCKED with no fallback -- a bad
pin must not be silently rescued by Grok. A scope violation (exit 4)
is BLOCKED with the evidence left in place.
Always pass the plan file path to majordome in the handoff: the
runner extracts the slice's own ### <SLICE-ID> section from it as
the GREEN spec.
Slice IDs follow the plan's naming: an arc prefix and a number
(e.g. S005, MLF-3, CON-12). A range uses the .. separator
with full slice IDs on both sides (e.g. MLF-1..MLF-4).
The invocation may also reference a plan file (e.g.
/tdd-step @docs/backlog/mlflow.md MLF-1..MLF-4 --autopilot). The
referenced file overrides plan discovery in Behavior step 1; it does
not replace the slice ID.
If bounds appear in any other form (free text such as "1 to 4", bare numbers without arc prefix), resolve them against the plan's slice IDs and confirm the interpretation in one line before starting; if no plan file is identifiable, ask the human to restate the range.
The detected mode governs the conditional branches in the Behavior and Phase rules sections below. The mode is held in the main agent's working memory for the duration of this Cursor turn.
Autopilot prerequisites
If MODE = AUTOPILOT:
- Read
.cursor/autopilot.yaml.- File absent → fail-fast with
[X] .cursor/autopilot.yaml is required for --autopilot mode - YAML invalid → fail-fast with
[X] .cursor/autopilot.yaml is invalid: <reason> - Required field missing → fail-fast with
[X] .cursor/autopilot.yaml is missing required field: <name>
- File absent → fail-fast with
- Apply
docs/agent/norme_autopilot.mdfor the full contract. - Initialize cumulative counters in working memory:
slices_completed = 0cumulative_diff_lines = 0start_slice = <first slice this run>
- If a range was specified (
<SLICE-ID>..<SLICE-ID>), record the range end as the bounded end. Otherwise honorend_at_slicefrom the YAML if set, else run until a stop condition fires.
Behavior
- Determine the target slice:
- If the invocation specifies a slice ID (e.g.,
MLF-3), use it. - If the invocation references a plan file, read the slice specs from that file instead of discovering the plan.
- Otherwise, locate the active plan document in
docs/backlog/(one document per arc; a completed arc's plan is deleted -- git history is the archive) andgit logto find the first slice not yet committed. - If no plan document is found, or several active plan documents are plausible candidates: MODE = MANUAL asks the human for the slice or the plan file; MODE = AUTOPILOT fails-fast.
- The plan's
Statutline must readplan validé humain(or a later state such asen cours). Abrouillonplan, or a plan file that is not tracked by git, is not executable: MODE = MANUAL tells the human to finish/plan(validation + plan commit); MODE = AUTOPILOT fails-fast with[X] plan <path> is not validated/committed. Slices are never taken from a chat message or from an agent's memory.
- If the invocation specifies a slice ID (e.g.,
- If MODE = MANUAL: confirm the exact step with the human, including its goal and acceptance criteria. If MODE = AUTOPILOT: log the slice ID and proceed. The slice spec from the plan IS the scope; no human confirmation needed.
- Delegate orchestration to
majordome. majordomemust enforce the phase sequence:- invoke
red-agent - then
green-agent - then
refactor-agent - then
commit-agent(or invoke the/commitskill in MANUAL mode; in AUTOPILOT mode, delegate tocommit-agentdirectly to bypass/commit's per-commit human approval pause)
- invoke
- After each subagent result, read its
statusfield from the returned text. - Apply the phase rules described below.
Phase rules
RED phase
- Call
red-agent. - Expect status in: GOOD_RED, BAD_RED, BLOCKED.
- If GOOD_RED: proceed to GREEN.
- If BAD_RED:
- MANUAL: ask
red-agentto revise. Do not start GREEN. - AUTOPILOT: increment per-slice BAD_RED counter. If counter exceeds
hard_stops.red_max_attempts, exit per autopilot hard-stop semantics. Otherwise askred-agentto revise.
- MANUAL: ask
- If BLOCKED:
- MANUAL: stop and report to the human.
- AUTOPILOT: exit per autopilot hard-stop semantics.
GREEN phase
- Call
green-agent. - Expect status in: GREEN_CONFIRMED, STILL_FAILING, BLOCKED.
- If GREEN_CONFIRMED: proceed to REFACTOR.
- If STILL_FAILING:
- MANUAL: iterate with
green-agentwithout touching tests. - AUTOPILOT: increment per-slice STILL_FAILING counter. If counter
exceeds
hard_stops.green_max_attempts, exit per autopilot hard-stop semantics. Otherwise iterate withgreen-agentwithout touching tests.
- MANUAL: iterate with
- If BLOCKED:
- MANUAL: stop and report to the human.
- AUTOPILOT: exit per autopilot hard-stop semantics.
REFACTOR phase
- Call
refactor-agent. - Expect status in: REFACTOR_DONE, REFACTOR_PARTIAL, BLOCKED.
- If REFACTOR_DONE: proceed to COMMIT.
- If REFACTOR_PARTIAL:
- MANUAL: proceed to COMMIT, but the commit must note remaining debt.
- AUTOPILOT:
- If
soft_stops.allow_refactor_partial: false(default): complete the commit for this slice, then exit per soft-stop semantics. - If
soft_stops.allow_refactor_partial: true: proceed to COMMIT, commit notes remaining debt, autopilot continues.
- If
- If BLOCKED:
- MANUAL: stop and report to the human.
- AUTOPILOT: exit per autopilot hard-stop semantics.
COMMIT phase
- MANUAL mode: Invoke the
/commitskill or delegate tocommit-agentdirectly. Show proposed commit to the human, executegit commitafter explicit approval. Never push. Ask the human to push. - AUTOPILOT mode: Delegate directly to
commit-agent(do NOT invoke/commitskill — it has a "never push" terminal rule that conflicts with autopilot semantics).- If
commit-agentreturns READY_TO_COMMIT: executegit commitusing the proposed title and body. The human is NOT prompted for approval (the slice spec serves as the contract). - If
commit-agentreturns NEEDS_MORE_EVIDENCE or BLOCKED: exit per autopilot hard-stop semantics.
- If
- Recording the message (both modes): write the message to a file
outside the repo (
$env:TEMP) with Python (Path.write_text(..., encoding="utf-8", newline="\n")) and rungit commit -F <file>. Never pass a multi-line message through a PowerShell here-string or-m: backticks and$in the body expand and the commit fails or records a corrupted message. - After successful commit in AUTOPILOT mode:
- Do NOT push. Pushing is always a human action, in both modes. Commits accumulate locally; the exit summary asks the human to push.
- Update cumulative counters:
slices_completed += 1,cumulative_diff_lines += <diff size of this slice>. - Proceed to the auto-chain decision (next section).
Gate-failure repair (git commit rejected by pre-commit)
When git commit is rejected by a pre-commit hook after commit-agent
returned READY_TO_COMMIT, the repair is ONE majordome re-invocation,
not a phase-by-phase relay through the main agent.
- If the failure is purely mechanical — an auto-fixing hook
(formatter, end-of-file-fixer, trailing whitespace) modified files
and failed the commit — re-stage the hook-fixed files and retry
git commitonce. No majordome re-invocation is needed. - Otherwise re-invoke
majordomewith a gate-failure repair input: the slice spec (unchanged), the failing hook name(s), and the verbatim hook output. Majordome routes the repair internally (test-side cause to red-agent, production-side cause to green-agent), re-validates, and finishes with commit-agent so the commit payload reflects the repair. - The main agent must NOT invoke red/green/commit agents individually for a gate repair: each phase agent is a cold start, and the relay multiplies context reloads without adding safety. Majordome already holds the slice context.
- Hooks whose failure requires a human decision (external-contract gate, sacred files, test marker changes) are NOT repairable: surface to the human instead of re-invoking majordome.
- MANUAL mode: after majordome returns the refreshed payload, show it to the human and commit on approval, as usual.
- AUTOPILOT mode: this repair path applies inline, once per slice.
On a pre-commit rejection, perform the single repair attempt
(mechanical re-stage or majordome re-invocation, as above) and
retry the commit. If the gate rejects again, exit per autopilot
hard-stop semantics (see
docs/agent/norme_autopilot.md). Human-decision hooks are never repaired in autopilot: immediate hard stop. - One repair pass. If the gate rejects the commit again after the repair, stop and surface to the human; do not loop.
Auto-chain (AUTOPILOT mode only)
After a successful commit for a slice, evaluate stop conditions in this order:
- Bounded range exhausted: if a range was specified and the
just-completed slice == the range end, exit cleanly with
exit_reason: "bounded range complete". - end_at_slice reached: if
.cursor/autopilot.yamldefinesend_at_sliceand the just-completed slice == that value, exit cleanly. - Soft stops: evaluate each enabled soft stop:
slices_completed >= soft_stops.max_consecutive_slices→ pausecumulative_diff_lines >= soft_stops.max_diff_lines→ pause- tests added in this slice >
soft_stops.max_tests_added_per_slice→ pause - coverage decreased and
allow_coverage_drop: false→ pause When a soft stop fires, exit cleanly withexit_reason: "<rule>".
- Otherwise: determine the next uncommitted slice from the plan
file and
git log. Start a new cycle from step 1 of Behavior.
If no next uncommitted slice exists (plan exhausted), exit cleanly
with exit_reason: "all planned slices committed".
Stop summary (AUTOPILOT mode only)
When AUTOPILOT exits (any reason), if reporting.pause_summary: true,
print a structured summary:
[autopilot summary]
mode: autopilot
started_at_slice: <slice-id>
last_completed_slice: <slice-id>
slices_completed: <int>
last_commit_sha: <short hash>
last_commit_subject: <subject line>
cumulative_diff_lines: <int>
exit_reason: <human-readable string>
suggested_next_action: <text>
This summary is the human's primary checkpoint between autopilot runs.
last_commit_sha (and any hash quoted in a handoff to majordome) is
read with git rev-parse HEAD after git commit has returned: the
post-commit hooks amend the commit (ratchet baselines), so a hash read
earlier is stale.
Hard rules (apply in BOTH modes)
- Tests are only written or changed by
red-agent. - Production code is only changed by
green-agentandrefactor-agent. - No bypass of pre-commit hooks. No
--no-verify. - Never push to the remote. Pushing is always a human action, in both modes.
- No modification of files listed in
.github/sacred-files.ymlwithout the standard unlock-governance workflow (this applies even in AUTOPILOT mode; if a slice would modify a sacred file, autopilot exits per hard-stop semantics). - If any subagent returns an unexpected status, treat as protocol violation and stop.
- One commit per slice. No bundling. No squashing within autopilot.
End state
MANUAL mode
- The step is either:
- committed locally, with the human asked to push, OR
- stopped with a clear explanation of the blocker.
- The cycle ends. It does NOT auto-start the next step.
AUTOPILOT mode
- One or more slices are committed locally.
- A structured summary is printed, and the human is explicitly asked to push.
- The cycle exits cleanly. The human resumes by re-invoking
/tdd-step(with or without--autopilot, as they choose).
Override mechanism
The Cursor stop button is the unique override mechanism for autopilot.
There is no .autopilot.stop file, no kill command, no escape hatch
beyond Cursor's standard interrupt. Pressing stop:
- Interrupts the current agent turn immediately
- May leave the in-progress slice partially completed (test written
but no commit, etc.). The human inspects
git statusto assess. - The next
/tdd-stepinvocation starts fresh from the first uncommitted slice.