All tools
HarnessCurated · reviewed

TDD Loop Harness

Updated Jan 1, 2025

A starter Claude Code project config and README instructions that keep Claude in a test-first loop. Copy the settings to .claude/settings.json to gate tool permissions and add a PostToolUse hook that runs the test suite after every Write or Edit. The red-green-refactor discipline comes from Claude following the instructions, not from filesystem locks.

What it does

  • /run-tdd

    Start a red-green-refactor cycle: write failing test, implement, refactor.

  • /check-coverage

    Run coverage for a module and list uncovered lines and branches.

  • /scaffold-test

    Generate a failing test file for a new function or component.

Files (2)

README.mdprimary · markdown · 3.9 KB
# TDD Loop Harness

A Claude Code project configuration that guides Claude through strict test-driven development.

## What it does

When this harness is active, Claude Code follows the red-green-refactor loop without
shortcuts. The discipline is instruction-driven: Claude reads this README and treats
it as the working contract for the session.

1. **Red** — write a failing test that describes the intended behaviour.
2. **Green** — write the minimum code to make the test pass (nothing more).
3. **Refactor** — clean up the implementation while keeping all tests green.

The `settings.json` adds two practical nudges:
- **Permission gates** that restrict bash commands to test-related tools and block
  destructive operations.
- **A PostToolUse hook** that runs `npm test` automatically after every Write or Edit,
  so test results surface immediately without a manual run.

Neither of these prevents you from writing code in the wrong order — the discipline
comes from Claude following the instructions in this file.

## Files in this harness

| File | Purpose |
|------|---------|
| `README.md` | This guide — Claude reads it as its working instructions |
| `settings.json` | Claude Code project settings — copy to `.claude/settings.json` |

## Setup

1. Copy `settings.json` to `.claude/settings.json` in your project root.
2. Open your project in Claude Code. The permission gates and PostToolUse hook
   take effect as soon as the settings file is loaded.
3. Start a task by describing the behaviour you want. Claude will write the test first.

If your project already has a `.claude/settings.json`, merge the `permissions` and
`hooks` sections manually.

## Workflow reference

### Starting a new feature
Tell Claude: "Implement <feature description> using TDD."

Claude will:
- Confirm what the failing test should assert before writing it.
- Run the test suite to verify the new test fails (red state).
- Write the implementation only after the failure is confirmed.
- Run the suite again to verify green.
- Ask whether to refactor before moving on.

### Adding a test for existing code
Tell Claude: "Write a test for <function/module>."

Claude will write the test, run it, and — if it passes immediately — ask whether
the function has a gap worth testing instead. A test that passes without any code
change provides no safety net.

### Checking coverage
Tell Claude: "Check coverage for <module>."

Claude will run your coverage tool, list uncovered lines and branches, and propose
tests for the most important gaps.

## Commands

- `/run-tdd <feature description>` — Start a red-green-refactor cycle: write a failing
  test, implement to green, then refactor.
- `/check-coverage <module or file>` — Run coverage for a specific module and list
  uncovered lines and branches.
- `/scaffold-test <function or component>` — Generate a failing test file with the
  right imports and describe structure, ready for you to fill in the assertions.

## Allowed bash commands

The harness permits only test-related bash commands:

- `npm test`, `npx vitest`, `npx jest` — run the test suite
- `npx tsc --noEmit` — type-check without emitting
- `npm run coverage`, `npx vitest --coverage` — generate coverage reports

Write and Edit tools are unrestricted.
Destructive commands (`rm -rf`, `git push`, `git reset --hard`) are blocked.

## Customising the hook

To change the test command, edit the `command` value inside
`hooks.PostToolUse[0].hooks[0]` in `settings.json`. The default is `npm test`.
For a monorepo, scope it to the relevant workspace:
`npm test --workspace=packages/api`.

## Known limitations

- The harness does not prevent you from writing implementation code without a test;
  it enforces discipline through Claude's instructions, not filesystem locks.
- If you want a hard coverage gate, add a threshold check to your CI pipeline
  (e.g. `npx vitest --coverage --coverage.thresholds.lines=80`).
settings.jsonJSON · 726 B
{
  "permissions": {
    "allow": [
      "Read(**)",
      "Write(**)",
      "Edit(**)",
      "Bash(npm test*)",
      "Bash(npx vitest*)",
      "Bash(npx jest*)",
      "Bash(npx tsc --noEmit*)",
      "Bash(npm run coverage*)",
      "Bash(npx vitest --coverage*)",
      "Bash(git diff*)",
      "Bash(git status*)",
      "Bash(git log*)"
    ],
    "deny": [
      "Bash(git push*)",
      "Bash(git reset --hard*)",
      "Bash(git restore*)",
      "Bash(git clean*)",
      "Bash(rm -rf*)"
    ]
  },
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "npm test"
          }
        ]
      }
    ]
  }
}