Test-Driven Development (TDD) Guidelines
Strict outside-in test-driven development. The main session owns the cycle; sub-agents handle individual steps.
STARTER_CHARACTER = 🔴 for a red test, 🌱 for green, 🌀 for refactor, always followed by a space.
/retroactive-test-check)The TDD cycle drives design via a sequence of small, auditable state changes. Every production code change that carries behavior traces to a specific failing test. Tests describe behavior (inputs, outputs, side effects), not implementation (method calls, internal state). Behavior-free code is exempt — see "Don't Test Behavior-Free Code" below.
Predict every run. Before you run any test, state the exact failure you expect — the message and the reason it should fail. Running without a prediction is a code smell: if you can't say what should happen, you don't yet understand the behavior you're driving. A run that fails for a reason other than the one you predicted (a "wrong-reason red" — unrelated compile/import/syntax/setup error) does not count as a valid red; fix the cause and re-predict.
TDD drives behavior. Code that holds no behavior has nothing for a test to drive, and a test against it asserts the language's own semantics, not your design. Do NOT write tests for:
@Data, framework-generated code)These need no failing test and no test backfill. Adding one is the getter/setter anti-pattern: it inflates the suite, couples tests to structure, and breaks on harmless refactors without catching real defects.
The test arrives when behavior arrives. The moment an accessor gains logic — a computed value, a conditional, validation, a transformation, a side effect — it is behavior, and the new behavior is driven by a failing test like anything else. Test the behavior, not the field.
If you reach for a test only because "every change needs a test" or to satisfy the TDD gate, and the code under test has no behavior, that is the wrong reason — skip the test and make the edit.
Do NOT delegate an entire RGR loop to a single sub-agent. Individual steps may use task agents ("write the failing test", "write minimum code to pass", "refactor X") — but the main session runs the tests and verifies state between steps.
When the surface under test does not exist yet, take the red in two predicted steps:
Only then move to green. This separates "the thing isn't there" from "the thing is wrong", and each transition is predicted and observed.
After a green where you hardcoded a literal, you often need a second red to force generalization. Pick deliberately:
Use example-based tests when…
Use property-based tests when…
If you're about to write a THIRD example-based test against the same function to force the same generalization — STOP and convert to a property test. Beyond three, each example adds cost without confidence; a property covers the whole space.
decode(encode(x)) == xf(f(x)) == f(x)f(x, identity) == xFor TypeScript projects, prefer fast-check for property-based testing.
Each turn addresses ONE failure. Change only what the current failure message requires, then re-run. Let the next failure tell you what to do next.
| Failure | Allowed change this turn |
|---|---|
| Syntax / missing import | Add the import or fix the syntax. Nothing else. |
| Unknown symbol / "not defined" | Declare as empty stub (throws "not implemented" or returns trivially invalid value). Match arity to call site. |
| Wrong arity | Adjust signature to match caller. Do not wire new parameter through body. |
| "Must implement method Y" | Add Y as stub that throws "not implemented". |
| "Not implemented" thrown by stub | Replace throw with simplest expression that could satisfy next check the test makes (usually a literal). |
| expected X, got <trivial> | Return the literal X. Fake it. Do not write general logic. |
| expected X, got Y from real logic | Inspect the logic, make narrowest correction to yield X for this input. Do not re-architect. |
| Unexpected exception | Fix only the line in your own code on the first frame of the stack trace. |
| Expected exception NOT thrown | Add the throw in exactly the single branch the test exercises. |
| External dependency unreachable in unit test | STOP — return to red. Wrong boundary under test, or missing mock at system edge. |
| Fixture / before-hook failure | STOP — return to red. Test-side changes are not a green-phase activity. |
While driving the current failure to green, do NOT:
If the minimum fix would be in a test file, fixture, or mock — STOP. Green is for production code only. Discard the in-progress production edit, update the test in a red-phase turn, re-run to see the real red, then re-enter green.
Test: expect(sum_of_squares([2, 3])).toBe(13)
return 13 (literal)If you're tempted to write code beyond what the current failure demands — stop, delete it, re-run, let the next failure speak.
If a failing test cannot be written because the harness is missing, flaky, unsafe, or blocked by unrelated infrastructure, state why. Create the closest executable characterization or documented verification command instead, then keep the production change as small as possible.
Implementation plans must be iterative RGR cycles, not layer-by-layer construction.
Step 1: Define all domain types
Step 2: Define all events
Step 3: Implement all command logic
Step 4: Write unit tests
Step 5: Write acceptance tests
This batches implementation before tests and does not drive design from failing tests.
Step 1: Write failing acceptance scenario → run → see fail
Step 2: Identify first compilation/runtime failure
Step 3: Write failing unit test for that specific need
Step 4: Write minimum code to pass the unit test
Step 5: Refactor
Step 6: Repeat 3-5 until acceptance scenario passes
Plans describe acceptance scenarios + design decisions + module structure for context. They do NOT prescribe "build X, then Y, then test." The implementation sequence emerges from the TDD cycle itself.
If you're writing a test that passes on the first run (the behavior is already implemented), you MUST mutation-verify it before trusting it. See the /retroactive-test-check skill.
When the cycle (or batch of cycles) is done, report the validation evidence:
Search for places (restaurants, cafes, etc.) via Google Places API proxy on localhost.
Interact with GitHub using the `gh` CLI. Use `gh issue`, `gh pr`, `gh run`, and `gh api` for issues, PRs, CI runs, and advanced queries.
Create or update AgentSkills. Use when designing, structuring, or packaging skills with scripts, references, and assets.
Start voice calls via the OpenClaw voice-call plugin.
Notion API for creating and managing pages, databases, and blocks.
Gemini CLI for one-shot Q&A, summaries, and generation.
Category:developer