tdd-red-green-refactor
Strict red/green/refactor/simplify discipline for the `/build <task-id>` command. Use when implementing any task in Phase 4. The agent must NOT write production code without a failing test recorded in
SKILL.md.claude/skills/tdd-red-green-refactor/SKILL.md
GitHubTDD: red / green / refactor / simplify
The four phases (per task)
1. RED — write a failing test
Read the task entry in
04-tasks.md. ConfirmTest-IDsandAC-IDs.Write the smallest possible test that asserts the behavior described by the AC.
- Annotation block on every
@Testmethod is non-negotiable:@Test→@Tag("AC-NNN")→@DisplayName("..."). Write the@DisplayNamebefore the test body — the act of articulating the display name forces clarity about what the test verifies. Format for AC-traced tests:"<T-ID>: given <precondition>, when <action>, then <outcome>". Short BDD-style sentence is acceptable for utility/regression tests not tied to a specific AC. (Project rule:feedback_displayname_on_every_test.md.)
- Annotation block on every
Run only that test (
mvn -Dtest=ClassName#method test).The test must fail, and the failure must be for the right reason (the asserted behavior is missing, not a typo or compilation error).
Append a
redblock to05-implementation-log.mdwith the failing command and the first 10 lines of the failure output.Update
.specs/<id>/.tdd-state.json:{ "active_task": "T-001", "tasks": { "T-001": { "phase": "red", "red_at": "2026-04-18T10:00:00Z", "red_test_signature": "...", "red_failure_excerpt": "..." } } }
The block-impl-without-failing-test hook reads this file. No production-code edit (src/main/**, or an Angular source file other than *.spec.ts) is allowed unless the active task has a recorded failing test (red_at set and red_failure_excerpt non-empty) and its phase is red, green, refactor, or simplify.
2. GREEN — minimum production code
- Edit only files in the task's
Files in scope. The hook enforces this. - Write the minimum code to make the failing test pass. No speculative features. No "while I'm here" cleanups.
- Re-run the test (
mvn -Dtest=...). It must pass. - Run the module's full Surefire suite to ensure no regression (
mvn -q test -pl <module>). - Append a
greenblock to05-implementation-log.md. - Set
.tdd-state.jsonphasetogreen.
3. REFACTOR — improve internals, hold behavior
- Eliminate duplication, extract helpers, rename for clarity, push logic to the right layer.
- Run the full module suite after every edit. Suite must stay green.
- Allowed: extract method, extract class, inline variable, rename, move to module-internal package.
- Forbidden: changing public signatures (breaks the test), changing behavior, adding features.
- Append a
refactorblock per substantive change.
4. SIMPLIFY — /code-simplify pass
- Apply the
clarity-over-clevernessskill: if there's a simpler way to express the same logic that a junior engineer would understand at a glance, use it. - Specifically watch for: nested ternaries, clever streams when a
forloop is clearer, premature abstraction, dead options, "options bag" parameters with one used field, helper methods with one caller. - Also scan for repeated literals: any string or number appearing 2+ times in the same class (production OR test) must be extracted to a
private static finalconstant at the top of the class with a domain-meaningful name. Avoids SonarQubejava:S1192(string literal duplicated). Single-use literals are fine inline. (Rule:feedback_extract_repeated_literals.md.) - Audit every newly-authored or modified test method in the diff for
@DisplayName. Any@Testor@ParameterizedTestwithout one gets it added here. This is the safety net for the red-phase rule — if a test slipped through without it, simplify is the last chance to catch it before the task isdone. Pre-existing legacy tests in the same file that were not touched by this task are NOT retroactively rewritten (scope-creep guard). (Rule:feedback_displayname_on_every_test.md.) - Suite must stay green.
- Append a
simplifyblock. Setphasetodone.
5. COMMIT — surface stopping report
When phase reaches done, STOP. Do not auto-start the next task.
Surface a stopping report:
- Files changed (
git status) - Tests passing (module suite green)
- Suggested commit message (e.g.,
feat(gift-card): T-001 apply gift card to order)
Recommend the 3-step follow-up:
git status # review what changed
git commit # commit the task
/build <next-task-id> # start the next task
If the user asks the agent to run git commit, the agent must ask for explicit permission immediately before that specific commit. Permission is single-use and never carries forward to later commits.
Next /build includes a pre-flight commit check: if uncommitted changes from a prior task are detected, refuse to start and surface this reminder.
Exception: The user can explicitly request task chaining (e.g., "chain to T-002", "continue without committing") — respect the explicit instruction.
Logging format
Each block in 05-implementation-log.md:
### T-001 · red · 2026-04-18T10:00:00Z
**Command:** `mvn -q -Dtest=ApplyGiftCardRequestTest#rejectsBlankCode test`
**Result:** FAIL (expected)
**Excerpt:**
[ERROR] ApplyGiftCardRequestTest.rejectsBlankCode:23 expected ConstraintViolationException but nothing was thrown
What "minimum code" means
- Hardcoding a constant to make the test pass is OK if there's only one test. The next test will force generalization.
- Don't introduce an interface for a single implementation.
- Don't introduce a config option for a single caller.
- Don't add a parameter "for future use".
This pressure to triangulate is the entire point of TDD.
Anti-patterns the hook will block
- Writing a test that already passes (no actual red).
- Deleting a test to make CI green.
- Editing files outside
Files in scope. - Skipping the red step ("I'll add the test after").
- Modifying an existing test's assertion to match new (wrong) behavior.
- Marking a task
donewithout all four blocks logged. - Don't add a method without a real consumer. A tautological test that asserts a method returns a fixed value is not a real consumer. If no genuine API boundary exists for the method, surface a
Q-NNNdesign gap instead of writing the method. - Auto-starting the next task without giving the user a chance to commit. Always stop at
phase: doneand surface the commit reminder (Step 5). Only chain tasks when the user explicitly requests it.