← Blog

TDD you can't skip: a phase-gate inside your coding agent

May 22, 2026 · 3 min read · tdd, claude-code, workflow

Most “AI writes tests” stories are test-after in disguise: the model implements, then back-fills assertions shaped to whatever it already wrote. That’s not TDD — it’s a green checkmark with none of the design pressure.

Zensu takes a different line. When you approve a plan that adds executable code, Zensu asks whether to run the strict /zensu:tdd flow. Say yes and it runs in the main thread under a PreToolUse phase-gate — a small finite-state machine:

  • RED_WRITE — you may create the failing test.
  • RED_FAIL — the test ran and failed for the right reason.
  • IMPL — production edits are allowed only after a RED_FAIL for this step.
  • GREEN_PASS — the test passes.

Try to edit a source file before its test has failed and the gate denies the write. Not a lint warning you can ignore — the edit doesn’t happen.

> edit src/auth/session.ts            # no failing test yet
  ✗ gate  state=RED_WRITE — production edit denied
          write the failing test for this step first

> add test/auth/session.test.ts
  ✓ RED_WRITE   test created
> run
  ✗ RED_FAIL    expired token still accepted   (fails for the right reason)

> edit src/auth/session.ts
  ✓ IMPL        edit allowed — a test is now demanding it
> run
  ✓ GREEN_PASS  1 passed

Why a gate beats a prompt

Prompt discipline degrades. Ten steps into a session, “remember to write the test first” has fallen out of the context window. A gate doesn’t forget. It keys off a per-session state file, so the rule holds on step one and step fifty alike.

It’s opt-in — and off by default

Strict TDD is a choice, not a tax on every edit. Out of the box Zensu runs in vanilla mode: you implement directly, and the parts that always pay for themselves stay on — the evidence audits (build, coverage, a witness that cross-checks what actually ran) and the full review chain. What’s switched off is the red-green ceremony and the edit gate.

You turn the gate on when you want it: answer “yes” to the question at plan approval for a single session, or set hooks.tddImplementation: true in ~/.zensu/config.json to make strict the default for a repo. The mode is frozen per session when the flow arms — flipping the config mid-flight changes nothing already in motion.

The ~30% tax

Strict TDD is not free. Every step becomes write-the-test → run → watch it fail → implement → run → watch it pass, plus the phase markers the gate reads. That’s more turns and more tokens than a direct implementation — in our runs, on the order of 30% more. On a metered agent that’s a real line item, and the reason the gate is a deliberate opt-in rather than an always-on default.

When it earns its keep

Spend the 30% where a regression is expensive and the invariants are subtle: auth and sessions, billing and money movement, permissions, state machines, parsers — anything where a silent break ships quietly and bills you later. There the design pressure shapes a cleaner interface and you walk away with a permanent regression net. The tax pays for itself the first time a future edit trips a test instead of production.

For exploratory spikes, throwaway scripts, UI glue, and low-stakes changes, vanilla is the right call: keep the review chain, skip the ceremony, save the tokens.

What it feels like

You still drive. You write the test, watch it fail, implement, watch it pass — the same loop you’d run by hand, except the harness refuses to let you cut the corner when you’re tired. The payoff is a diff where every line of production code is there because a test demanded it.

That’s the whole point: tests that shape the code, not tests that rationalize it. Skip it when the stakes are low. Turn it on when a regression would hurt — and then you can’t skip it.