Skip to content
Development
Skill

/tdd-loop

Drive the red-green-refactor loop for any non-trivial code change. Auto-detect the project's test runner (pytest / vitest / jest / node --test), invoke it via the `tdd.py` helper, and act on the normalized exit code. Use when you write a test, TDD this feature, let me test-drive

From plugin
jig
720 skills3 agents5 hooks

Claude Code

$ npx -y skills add ramboz/jig --skill tdd-loop --agent claude-code

SKILL.md

tdd-loop.SKILL.md
name: tdd-loop
description: >
  Drive the red-green-refactor loop for any non-trivial code change. Auto-detect
  the project's test runner (pytest / vitest / jest / node --test), invoke it via the
  `tdd.py` helper, and act on the normalized exit code. Use when you write a
  test, TDD this feature, let me test-drive a behaviour, are about to
  implement [feature], want to run my tests, ask is my coverage complete, or
  ask are tests green. Do not use for one-off exploratory scripts, throwaway
  spikes, or pure-documentation edits that touch no executable code.
user-invocable: true

> Spec 006 promoted this skill from a dangling auto-install reference to an > active skill. The deterministic runner detection + subprocess invocation > live in `tdd.py`; this SKILL.md drives the judgment layer.

What this skill does

Codifies the red-green-refactor loop that the `implementer` subagent (`agents/implementer.md`) already encodes as "non-negotiable TDD discipline." The skill:

  • Detects the project's test runner from filesystem signals (one of `pytest`,

`vitest`, `jest`, or Node's built-in `node --test`).

  • Invokes the runner as a subprocess against a target, a focused

`--test-path`, or a single `--test` selector.

  • Streams the runner's stdout/stderr through to the caller — you see real

output, not a swallowed summary.

  • Normalizes the runner's native exit code so callers can branch

deterministically:

  • `0` — all green
  • `1` — at least one red test (runner started but reported failure)
  • `2` — could not detect a runner, OR the runner binary is missing

The red-green-refactor loop

1. **Red.** Write a failing test for one acceptance criterion (or one well-bounded behaviour). Run `tdd.py run <target>` and confirm the new test is the *only* new failure — exit 1. 2. **Green.** Write the minimum implementation that makes the test pass. Run `tdd.py run <target>` again; expect exit 0. 3. **Refactor.** Clean up only after green. Re-run after each meaningful refactor; exit 0 must hold.

Stay in this loop one AC at a time. Do not write three tests and then three implementations — the loop's value is the tight feedback cycle.

When a test you did NOT just write goes red

The Green step above is about the *new* failing test you wrote for this AC — making it pass is the work. A **pre-existing** test going red is the opposite signal: it is a guard reporting that your change touched something load-bearing. The reflex to "make it green" is wrong here, and an agent is very good at satisfying it — deleting the assertion, loosening the expectation, or marking the test skipped all turn the light green while removing the protection.

So: **when a test you did not just write fails, do not fix the test — first suspect that your change broke what the test guards.** Only after you have ruled that out (the test encodes an intent that has genuinely, deliberately changed) may you touch the test, and then update its assertion *and its rationale* together so the next reader sees why. Never weaken, skip, `xfail`, or quarantine a pre-existing test to reach green. (This mirrors the standing rule for driving a PR to green — a red guard is work now, not an obstacle to route around.)

Helper invocations

Two subcommands cover the loop: `tdd.py detect` figures out which runner the project uses, and `tdd.py run` invokes it.

Detect the runner

python3 "${CLAUDE_PLUGIN_ROOT}/skills/tdd-loop/tdd.py" detect [target]
  • `target` defaults to `.` when omitted.
  • Stdout: `pytest`, `vitest`, `jest`, or `node` (one line).
  • Exit 2 with stderr `no test runner detected at <target>` if no signal matches.

Signals checked (priority order — first hit wins):

  • **pytest** — `pytest.ini` OR `conftest.py` OR `[tool.pytest` in

`pyproject.toml` OR a `test_*.py` / `*_test.py` file at root or in any direct subdirectory.

  • **vitest** — `vitest.config.{ts,js,mjs}` OR `vitest` in `package.json`'s

`dependencies` / `devDependencies`.

  • **jest** — `jest.config.{ts,js,json}` OR `jest` in `package.json`'s

`dependencies` / `devDependencies`.

  • **node** — `package.json` `scripts.test` invokes `node --test`, OR a shallow

JS/TS file imports from `node:test`.

When multiple runners are detected, priority is **pytest > vitest > jest > node**.

Run the suite

python3 "${CLAUDE_PLUGIN_ROOT}/skills/tdd-loop/tdd.py" run [target] [--test-path PATH]
python3 "${CLAUDE_PLUGIN_ROOT}/skills/tdd-loop/tdd.py" run [target] [--test SELECTOR]
  • Auto-detects the runner via the same logic as `detect`.
  • Default commands:
  • pytest → `python3 -m pytest <path>`
  • vitest → `npx vitest run <path>` (note the `run` — keeps watch mode off)
  • jest → `npx jest <path>`
  • node → `node --test` for the full suite; `node --test <path>` with

`--test-path`

  • Output streams through to the caller's terminal.
  • Exit code is normalized (0 / 1 / 2) per the table above.

Use `--test-path` when you want to run a focused subset (e.g. a single test file) while still letting the helper detect which runner to invoke. Use `--test` when you want one named test: pytest selectors are passed as node ids (`path::test_name`), vitest/jest selectors map to file plus `-t`, and node selectors map to file plus `--test-name-pattern` when shaped as `path::test name`.

When NOT to use

  • **One-off scripts** or **REPL exploration** — TDD overhead isn't worth it

for code that won't survive the session.

  • **Exploratory spikes.** Spikes are research artifacts; they answer "what

shape should this take?" — tests come *after* the spike has settled the shape.

  • **Pure-documentation edits** that touch no executable code.
  • **Auto-generated code** (codegen output, vendored deps). Test the

generator, not the generated artifact.

Sibling helper: `quality.py` (test-quality snapshot)

Alongside `tdd.py` the skill ships `quality.py`, a deterministic test-quality preflight. It reads a unified diff (`--diff-file PATH` or `--again

Read more
@ramboz
Ships withjig

A Claude Code and Codex plugin that scaffolds AI-native development practices into new projects. jig adds a repeatable spec, implementation, review, and memory workflow to AI-assisted software projects.

720 skills3 agents
Get the whole plugin

Stars on GitHub

Momentum

stars in total
7
Star history of ramboz/jig on GitHub, a chart by star-history.com

Other skills on jig.

adr-workflow

adr-workflow

Scaffold, accept, index, and link Architectural Decision Records (ADRs). Use when the user says "write an ADR", "record this…

@ramboz@rambozInstall Skill
analyze

analyze

Cross-artifact consistency report for jig specs — a non-destructive six-category audit at CRITICAL/HIGH/MEDIUM/LOW severity,…

@ramboz@rambozInstall Skill
arch-review

arch-review

Team baseline for architecture, design-doc, and RFC review — produces summary, strengths, concerns, and open questions.…

@ramboz@rambozInstall Skill
bug-fix

bug-fix

Drive the teeth-gated lifecycle for reported defects: diagnose root cause, prove it, and prevent regression through REPORTED →…

@ramboz@rambozInstall Skill
clarify

clarify

Lightweight spec clarification scan for jig projects — a six-category ambiguity audit that asks up to five prioritized questions…

@ramboz@rambozInstall Skill
code-health

code-health

Run a static-analysis pass on a project — detect the ecosystem (Python or Node), drive its linter (ruff / eslint, plus advisory…

@ramboz@rambozInstall Skill

More skills from other plugins.

brainstorming
Auto-invoked

brainstorming

You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…

@obra@obraInstall Skill
ask-matt

ask-matt

Ask which skill or flow fits your situation. A router over the skills in this repo.

skills10d309k
@mattpocock@mattpocockInstall Skill
accessibility
Auto-invoked

accessibility

Design, implement, and audit accessible UI to WCAG 2.2 Level AA across Web, iOS, and Android — semantic ARIA roles and labels,…

ecc12d293k
@affaan-m@affaan-mInstall Skill
compiler-commit

compiler-commit

Use when you want to verify compiler changes and commit with the correct convention. Runs tests, lint, and format, then commits…

react1d252k
@facebook@facebookInstall Skill

Questions about tdd-loop.

Which coding agents can use tdd-loop?
tdd-loop installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.