---
name: tdd-workflow
description: Enforces a strict red-green-refactor loop where a failing test is written and observed before any implementation code, with small commits and a verification gate at the end. Use for any feature, bug fix or refactor in a codebase that has (or should have) an automated test suite.
license: MIT
compatibility: Any language with a test runner (Jest, Vitest, pytest, Go test, RSpec, cargo test, JUnit). Requires permission to run tests.
metadata:
  category: testing
  version: "1.0.0"
---

# TDD Workflow

The point of test-driven development with an agent is not ideology; it is that a test you watched fail is the only proof that the test can fail. Code written before its test tends to get tests that assert whatever the code happens to do.

## Ground rules

1. No production code without a failing test that demands it.
2. Run the test and **see it fail for the right reason** before writing the implementation. A test that fails because of a typo or missing import proves nothing.
3. Write the smallest implementation that passes. Resist generalising until a second test forces it.
4. Refactor only on green, and re-run the suite after refactoring.
5. Keep each cycle under roughly ten minutes of work; if a step is bigger, split the behaviour.

## Procedure

### 0. Discover the harness
- Find the runner and command: `package.json` scripts, `pyproject.toml`, `Makefile`, CI config. Note how to run a single file (`vitest run path`, `pytest path::test_name`, `go test ./pkg -run Name`).
- Read two existing tests to copy naming, fixture and assertion style. Match them; do not introduce a new testing style.

### 1. Turn the request into behaviours
List the observable behaviours as one-line test names before writing any test:

```
- returns empty list when cart has no items
- applies percentage discount before tax
- rejects negative quantities with ValidationError
```

Order them simplest first. Confirm the list with the user if the requirement is ambiguous.

### 2. Red
- Write exactly one test for the first behaviour. Arrange, act, assert. One logical assertion per test.
- Run only that test. Paste the failure output. Confirm the failure message is about missing behaviour, not a broken setup.

### 3. Green
- Implement the minimum. Hard-coding a return value is acceptable for the first test; the next test will force real logic.
- Run the single test, then the surrounding file. Both must pass.

### 4. Refactor
- Remove duplication, rename for clarity, extract helpers — in both test and production code.
- Run the full relevant suite. If it goes red, undo the refactor rather than patching.

### 5. Commit
- One commit per green cycle or per small group of cycles: `feat(cart): apply percentage discount before tax`. Small commits make review and bisecting cheap.

### 6. Repeat for the next behaviour, then finish with the verification gate.

## Verification gate (before saying "done")

- [ ] Full suite passes locally with the exact command CI uses.
- [ ] Every new behaviour has a test whose failure you observed.
- [ ] No test was weakened (`skip`, loosened assertion, widened timeout) to get to green.
- [ ] Coverage of the changed files did not drop (run the coverage command if one exists).
- [ ] Lint and type checks pass.
- [ ] Paste the final test run summary in your report; do not describe it from memory.

## Writing good tests

- Test behaviour through public interfaces; avoid asserting on private state or call counts unless the interaction *is* the behaviour (e.g., "sends one email").
- Use real collaborators where cheap (in-memory DB, real date library); mock only network, time and randomness.
- Name tests as sentences: `test_refund_is_rejected_after_30_days`.
- Deterministic inputs: freeze time, seed randomness, avoid sleeping.
- For bug fixes: first write the test that reproduces the bug (it must fail on the current code), then fix.

## Output format

Report as a log of cycles:

```
Cycle 1 — returns empty list when cart has no items
  RED: FAIL cart.test.ts > "TypeError: total is not a function"
  GREEN: PASS (1 test)
Cycle 2 — ...
Final: 47 passed, 0 failed, 0 skipped (vitest run) — coverage 91% (+2%)
Commits: 3
```

## Pitfalls

- Writing all tests up front, then all code, is not TDD; the feedback loop is lost and tests end up coupled to a guessed design.
- Do not assert on error message strings that are likely to change; assert on the error type or code.
- Snapshot tests are not a substitute for behavioural tests; use them for serialised output only.
- If you cannot make a test fail, the behaviour already exists or the test is not testing what you think.
- When the harness is slow, run the narrowest command possible during the loop and the full suite only at the gate.
