tdd-ratchet

Enforces failing-first tests for Rust projects via git history.

tdd-ratchet wraps cargo nextest and tracks per-test states in a committed .test-status.json. It enforces that every new test must fail before it can pass - verified by inspecting git history. No shortcuts.

State machine

Every test goes through exactly two states. Each transition is a separate commit.

(new test)
fails
pending
passes
passing

A test that passes on its first appearance is rejected. A passing test that starts failing is a regression. A tracked test that disappears is rejected.

Workflow

# 1. Write a failing test
# 2. Run the ratchet - test added as pending
cargo ratchet
git add -A && git commit -m "test: describe expected behavior"

# 3. Implement the feature
# 4. Run the ratchet - test promoted to passing
cargo ratchet
git add -A && git commit -m "feat: implement the behavior"

Install

cargo install tdd-ratchet

Alternative (bare binary release):

# Linux x86_64
curl -Lo ~/.local/bin/cargo-ratchet https://tdd-ratchet.maxeonyx.com/releases/cargo-ratchet-x86_64-linux
chmod +x ~/.local/bin/cargo-ratchet

Then initialize:

cargo ratchet --init

Status file

The .test-status.json file is committed to your repo. It maps full nextest test names to their expected state:

{
  "$schema": "https://tdd-ratchet.maxeonyx.com/schema/test-status.v1.json",
  "tests": {
    "my-crate::tests$it_does_the_thing": "passing",
    "my-crate::tests$planned_feature": "pending"
  }
}

JSON Schema - add "$schema" to your status file for editor validation.

How it enforces TDD

The ratchet checks three things on every run:

  1. State transitions - new tests must fail, passing tests must stay passing, tracked tests must be present.
  2. Git history - walks commits since a baseline to verify every test appeared as pending before passing. No test can skip the failing step.
  3. Bypass prevention - a gatekeeper test ensures cargo test run directly (outside the ratchet) fails with instructions.