2026-06-30 20:45:25 +02:00

7.1 KiB

name, user-invocable, description
name user-invocable description
tdd-minimalism true Use when: implementing code to pass tests, writing minimal implementations, TDD workflow. Enforces strict minimalism by analyzing tests first and generating only the absolute minimum code needed. Prevents overengineering and anticipatory code. Run this skill before implementation on any failing test.

TDD Minimalism Guardrails

This skill enforces strict Test-Driven Development discipline by ensuring implementations contain only the code that tests require—nothing more.

Principle

Generate only the absolute minimum needed to pass tests. Anticipatory code, infrastructure for future scenarios, defensive checks, comments explaining future work, or over-engineered data structures all violate TDD and should be deleted before writing implementation code.

Pre-Implementation Workflow

Before writing implementation code, ALWAYS execute this checklist:

1. Analyze the Test

  • Read the test completely
  • Identify exactly what methods/functions the test calls
  • Identify exactly what the test asserts
  • Document nothing else matters for this test

2. Define Minimal State

Ask: "What data must persist across the test's method calls to satisfy the assertions?"

  • Only that data gets stored as member variables
  • Everything else is deleted before writing code

3. Implement Only Tested Paths

  • Implement only the specific code paths the test exercises
  • No conditional logic unless the test verifies multiple branches
  • No utility methods or helper classes
  • No defensive code (null checks, boundary conditions the test doesn't trigger)
  • No comments explaining future features
  • No includes you don't use

4. Question Every Line

For each piece of code, ask: "Does this test directly exercise this line?"

  • No → Delete it immediately
  • Yes → Keep it

Anti-Patterns to Eliminate

Anti-Pattern Why It's Wrong Corrected
Store data in a map/vector when test uses only 1 item Implements multi-item support not tested Use single member variable
Add if checks for untested scenarios Defensive code the test doesn't exercise Return value directly
Include #include <map> when using int Unused dependency Remove it
Define lastChoice member when select() doesn't read it Dead code Remove unused members
Comment explaining "future work" or "TODO" Out of scope for current test No implementation comments
Use compound assignment (+=, -=) when test calls method only once Anticipates future multi-call scenarios not covered by any test Use simple assignment (=) instead
Create utility methods not called in test Anticipatory code Delete before commit

Red-Green-Refactor Phases

Each phase must include running the test suite to confirm the expected outcome. The test command is ./run_tests.sh (or the project's equivalent).

Red Phase (Writing Tests)

  1. Write a test describing one specific behavior

  2. Run the test suite (./run_tests.sh) — confirm the test fails (either fails to compile or fails at runtime on an assertion).

    • If the test passes: The behavior is already covered by existing code. STOP. Abort and discuss with the user. Options include: the test is redundant (delete it), the test name/assertions are wrong, or the requirement changed. Do NOT proceed without user input.
    • If the test fails to compile: Write the absolute minimum stubs (empty classes, empty method signatures) needed to make the code compile, then run the test suite again — confirm the test now fails at runtime on the assertion (EXPECT_*/ASSERT_*), not just on compilation. This validates the assertion logic is correct before implementing.
    • If the test fails at runtime: You may proceed to the Green phase.
  3. Keep scope narrow (single scenario, not edge cases)

  4. Test only what's currently required by the story

Green Phase (Implementation)

  1. Implement only what the test directly calls

  2. Use the simplest possible data types

  3. Use the shortest possible logic

  4. Run the test suite (./run_tests.sh) — confirm it passes

  5. Reference this skill's anti-patterns table and eliminate violations before committing

  6. Present the implementation to the user for review before committing or proceeding to the next test.

    • Show the final state of all changed files (diff or summary)
    • Ask explicitly: "Please review and approve before I proceed"
    • Do NOT move to the next test, commit, or merge without explicit user approval
    • If the user requests changes, make them and re-run the test suite before asking for review again

Refactor Phase

  1. Improve code clarity (rename variables, format nicely)
  2. Run the test suite (./run_tests.sh) — confirm tests still pass
  3. Never add functionality without a failing test first
  4. Never add infrastructure for "future" requirements

Post-Implementation Verification

After implementing and tests pass, run this verification:

  • Can I remove any member variables? (Are they all accessed in at least one method?)
  • Can I remove any #include directives? (Do I use everything I include?)
  • Can I remove any conditional branches? (Does the test exercise all paths?)
  • Can I remove any comments? (Are they implementation comments, not documentation?)
  • Can I remove any method parameters? (Are they all used in the method?)
  • Can I replace any compound assignment (+=, -=, etc.) with simple assignment (=) without changing test outcomes?

If you answer yes to any question, make those deletions immediately. Do not proceed to the next test/feature without eliminating unnecessary code.

Example: The Vending Machine

Test (what it calls and asserts):

machine.addChoice(Choice::Cola, 0);        // Calls this
machine.assignProduct(Choice::Cola, Brand::Coke);  // Calls this
EXPECT_EQ(machine.select(Choice::Cola), Brand::Coke);  // Asserts this

Minimal Implementation:

// Header: ONE member variable (lastBrand), because select() returns it
Brand lastBrand;

// Implementation
void addChoice(Choice, int) {}  // Body empty; test calls but doesn't verify behavior
void assignProduct(Choice choice, Brand brand) { lastBrand = brand; }
std::optional<Brand> select(Choice choice) { return lastBrand; }  // No if; test only selects once

Eliminated Code:

  • #include <map> — Not using maps
  • std::map<Choice, Brand> productMap — Test uses only one assignment
  • if (it != productMap.end()) — Test doesn't verify "choice not found" scenario
  • Comments explaining future work — Out of test scope
  • Choice lastChoiceselect() doesn't read it; never used