| name | tdd-mutation-guard |
| description | Enforce a disciplined TDD workflow with CRAP score monitoring. Use after writing or modifying Go code to ensure tests are written first (Red-Green-Refactor) and complexity stays low (CRAP < 8). Triggers on every code change cycle. |
TDD Mutation Guard
Enforce a rigorous test-driven development cycle augmented with complexity monitoring for Go code in this repository.
When to Use This Skill
- Writing new functions or methods (tests first)
- Modifying existing code (ensure test coverage is meaningful)
- Refactoring (verify code remains clean and testable)
- Reviewing code quality metrics after changes
- When the user asks to "guard" or "tdd" their code
The Workflow: Red-Green-Refactor
Every code change follows this cycle.
⛔ HARD STOP — MANDATORY RULE
One test case at a time — never batch multiple tests or write a full test table before implementing.
You MUST write exactly one test case per RED phase. Writing two or more tests at once — even for the same function — is a violation of this workflow and you must stop, revert the extra tests, and restart the cycle one behavior at a time.
This applies even when the implementation is "obvious". The discipline is the point.
Phase 1: RED — Write Exactly ONE Failing Test
- Write one test case (or one table row) that expresses a single desired behavior.
- For table-driven tests: add only the next row, not the full table.
- STOP. Do not write more tests yet. Do not add additional sub-tests. Do not add additional t.Run blocks.
- Run the test and confirm it fails for the right reason — a behavior mismatch, not a compilation error.
go test ./path/to/package/... -run TestFunctionName -v
- If the failure reason is wrong (e.g. panics instead of a wrong value), fix the test until the failure is correct. Do not proceed until RED is confirmed.
FORBIDDEN: Writing multiple t.Run sub-tests, multiple test functions, or an entire test table before any implementation. Each RED step covers exactly one behavior — one t.Run block. If you find yourself writing more than one t.Run in a single RED phase, you are doing it wrong. Stop and revert.
Phase 2: GREEN — Minimum Code to Pass That One Test
- Write the minimum code necessary to make the one failing test pass.
- Do not implement logic for cases not yet tested.
- Do not optimize or generalize yet.
- Run
go test ./path/to/package/... -run TestFunctionName -v to confirm green.
- STOP. Do not move to the next test until this test is green.
Phase 3: REFACTOR — Clean Up, Then Check Complexity
- Improve code structure while keeping the test green.
- Mandatory: run gocyclo on the touched package and report the output.
gocyclo -over 8 ./path/to/package/
- The threshold is 8, not 5 — it aligns with the CRAP target. A function with complexity ≤ 8 and full coverage has CRAP ≤ 8. Only split when CRAP would breach 8, not merely when complexity exceeds an arbitrary lower bound.
- Mandatory: generate a coverage profile and check CRAP for every touched function.
go test -coverprofile=coverage.out ./path/to/package/
go tool cover -func=coverage.out
- Calculate CRAP for each function with complexity > 1. If CRAP ≥ 8, refactor before adding the next test.
- Run
go test ./path/to/package/... to confirm everything is still green after refactoring.
Only after a passing REFACTOR phase may you begin the next RED cycle.
Quick Reference
CRAP Score Target
Keep CRAP below 8 for every function.
CRAP(m) = complexity(m)^2 * (1 - coverage(m))^3 + complexity(m)
| Complexity | Coverage Needed | CRAP Score |
|---|
| 1 | 0% | 2 |
| 5 | 0% | 30 |
| 5 | 100% | 5 |
| 10 | 80% | 8.0 |
| 10 | 100% | 10 |
| 20 | 95% | 20.5 |
Takeaway: High complexity demands near-100% coverage. Better to split functions and keep complexity low.
Implementation Checklist (Per RED-GREEN-REFACTOR Cycle)
Each bullet is a gate — do not advance until it is ticked.
RED
GREEN
REFACTOR
Before Committing (after all cycles done)
Anti-Patterns to Avoid
- Batching tests before implementation — Writing a full test table or multiple test functions before writing any implementation code. Each cycle covers exactly one behavior.
- Skipping the gocyclo check — The complexity check is mandatory after every REFACTOR, not optional. Report the command output explicitly.
- Writing implementation first — Always test first, even for "obvious" code.
- Testing implementation details — Test behavior, not internals.
- Chasing 100% line coverage — Focus on meaningful assertions over coverage numbers.
- Large functions with high coverage — Split them; CRAP penalizes complexity exponentially.
- Mocking everything — Per repo conventions, prefer real dependencies where feasible.
- Stub too generous to produce RED — When starting from a stub (e.g.
return nil, nil), the stub must be minimal enough that the first test actually fails. If the test passes on the stub, the stub is doing too much — strip it back further until RED is confirmed before writing any real logic.
Integration with Repo Workflow
go test ./path/to/package/... -run TestName -v
gocyclo -over 8 ./path/to/package/
go test -coverprofile=coverage.out ./path/to/package/
go tool cover -func=coverage.out
make lint
make test
make test-e2e