Claude Skill

tdd

Write code through a disciplined, BDD-framed Test-Driven Development loop: build a behavior test list, then drive each behavior through red-green-refactor with an enforced observed-failure gate. Use when the user wants to implement, build, or write code test-first, "do TDD", foll

LLM Mart · 0 points · 0 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download testdouble-han-han-coding_skills_tdd-abba73a.zip · 24 KB
Part of testdouble/han — 47 skills

Install

skills CLI npx skills add https://github.com/testdouble/han/tree/main/han-coding/skills/tdd
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install testdouble-han@llmmart
Git git clone https://github.com/testdouble/han.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole testdouble/han collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Project Context

  • git installed: !which git 2>/dev/null || echo "not installed"
  • current branch: !git branch --show-current 2>/dev/null || echo unknown
  • CLAUDE.md: !find . -maxdepth 1 -name "CLAUDE.md" -type f
  • project-discovery.md: !find . -maxdepth 3 -name "project-discovery.md" -type f
  • personal config directory: !bash "${CLAUDE_PLUGIN_ROOT}/scripts/han-config-dir.sh" 2>/dev/null || echo "$HOME/.claude"
  • project .han/config.md: !cat .han/config.md 2>/dev/null || echo ""

As your first action, use the Read tool on .han/config.md inside the personal config directory path above. A read that returns no file is no personal configuration: continue silently. When that file or the project .han/config.md probe supplies content, apply it per config-rule.md, which governs precedence between the two files, relative-path resolution, and what to do with a file that reads but cannot be used.

Constraints (read before anything else)

This skill writes production and test code in your working tree. It is an execution skill, not a document generator. These constraints shape every step and override any instinct to move faster.

  • The observed-failure gate is load-bearing. No production-code change until a test has been run and observed to fail for the intended reason in this loop. A test that passes on first run is a stop-and-diagnose signal, not progress. This single rule is what separates real TDD from TDD-flavored code. The verbatim Three Laws and Canon TDD steps it derives from are in references/tdd-loop.md; pull that reference when a step needs the canon or the implementation gears.
  • The scope gate is the observed-failure gate's companion. The observed-failure gate proves a red is genuine. It does not prove the test deserved to exist in this build. No production-code change outside the scope boundary recorded in Step 1, and least of all in shared or cross-application code other consumers depend on. A list item whose green requires an out-of-scope edit is a stop, never an implement; Step 2 carries the resolution ladder that Step 3 works.
  • Two hats. Never refactor while any test is red. See references/tdd-loop.md for the canonical statement.
  • One behavior at a time. Exactly one test list item becomes one runnable test per loop. Newly discovered scenarios are written to the list and deferred, never implemented in the current loop.
  • BDD framing. Tests describe observable behavior, named in the project's existing test-naming convention, asserting outcomes through the public interface — never private state. The behavior-naming and Given/When/Then protocol is in references/bdd-framing.md; pull it when Step 2 needs it.
  • You will be tempted to fake this. The specific ways an agent fakes TDD, and the discipline that catches each, are in references/failure-modes.md; pull it when a loop feels off (a test passes on first run, no red is shown, the implementation has outrun the test, refactor is being skipped).
  • YAGNI governs the refactor step and the test list. Apply the rule in ../../references/yagni-rule.md: remove duplication, but do not add abstractions, configuration, or indirection without evidence. Speculative structure added "for flexibility" during refactor is a YAGNI candidate. Speculative scenarios on the test list are deferred with a reopen trigger, never silently added.

Test-Driven Development

Step 1: Resolve Project Config and Confirm Scope

Resolve commands. Read CLAUDE.md's ## Project Discovery section for the test command (under ### Commands and Tests, not ### Frameworks and Tooling), the lint command, the build command, language, and framework. If absent, fall back to project-discovery.md. If still absent, run ${CLAUDE_SKILL_DIR}/scripts/detect-tdd-context.sh and parse its output for git state and manifest-inferred commands. Store the resolved test, lint, and build commands for use in every later step.

Resolve standards and decisions. Resolve the coding-standards directory and ADR directory the same way: read CLAUDE.md's ## Project Discovery section; fall back to project-discovery.md; fall back to Glob defaults (docs/, docs/adr/, docs/coding-standards/, docs/decisions/). Also check CLAUDE.md and AGENTS.md for inline standards. Read the standards and ADRs whose titles, paths, or one-line summaries indicate they govern the area being built. Cap at five documents; if more than five look relevant, list them and read only the five with the strongest apparent relevance — defer the rest until refactor surfaces a need. These govern the green and refactor steps. If none exist, state that plainly and plan to infer conventions from the surrounding code instead.

Resolve the scope boundary. Name, in files and directories, what this work is allowed to change, because the scope gate tests every candidate edit against it. Inside the boundary: the files, directories, or module the request names, plus the tests that cover them. Outside it: everything the named code merely reaches, meaning shared libraries, engines, packages, and any code a second application or consumer also uses, plus code another team owns per CODEOWNERS. When the request names no files, take the application or package the requested behavior lives in as the boundary and treat its dependencies as outside it. Record the boundary; you will test list items and production edits against it.

Report scope, then proceed (no gate). This skill runs autonomously after the initial request: it does not stop for confirmation. State to the user, in a few lines: the behavior or feature to be built, whether this is net-new behavior or a fix to existing broken behavior (a reported bug, a failing case, a fix being driven back in after /investigate, or code that already exhibits the error — recognize the fix case from those signals, not only from the word "bug"), the scope boundary you just recorded, the resolved test/lint/build commands, the standards and ADRs found (or that none were), the current branch, and that the skill will now write code in a red-green-refactor loop. If current branch from Project Context is the repository's default branch (main or master), recommend working on a branch, but do not wait for an answer. This is a report the user reads while the work runs, not a gate. Continue immediately to Step 2 without waiting for a response.

The one exception. If the initial request or the provided context explicitly states the human wants to review, verify, or approve the plan or test list before implementation, then this becomes a gate: build the test list in Step 2, present it together with this scope report, and wait for approval before starting the Step 3 loop. Absent an explicit request like that, the skill runs to completion without further human input.

Two things can still block a run, both hard dependencies rather than discretionary checkpoints. A missing test command is the first: if it could not be resolved from CLAUDE.md, project-discovery.md, the discovery script, or manifest inference, ask the user for it, because TDD is impossible without a way to run tests. Exhaust inference before asking. The second is the top rung of the scope gate's resolution ladder in Step 2, reached only when the requested behavior cannot be delivered without an out-of-scope change.

Step 2: Build the BDD Test List

Turn the requested feature or behavior into a test list (Kent Beck's "test list" pattern). Each item is one observable behavior, phrased as a behavior sentence, not as an implementation note. "Returns the unrounded fee for a sub-dollar charge" is a list item; "use a BigDecimal" is not. Follow references/bdd-framing.md for how to phrase and name behaviors, and which test-naming convention to adopt (the project's existing convention and any discovered coding standard win over a literal "should" default).

Fixing existing broken behavior is a regression test, not a bug-asserting test. When the work fixes broken behavior, the list item names the desired correct behavior, not the current broken one: "returns the rounded total for a refund" (red now because the bug is present, green once the fix lands), never "raises ArgumentError on a refund" — a test that asserts the error the bug produces passes while the bug is present and breaks when you fix it, locking the bug in. The regression test asserts what the code should do. The boundary: asserting that the code raises is the correct test when raising is the specified desired behavior (raise on invalid input); it is wrong only when the raised error is the bug being fixed.

Order the list outside-in by user value: the next item is the most important thing the system does not yet do. When one behavior expands into several candidate tests (the empty case, the single case, the many case, the boundaries), order those tests simplest-first — Zero → One → Many — so each test forces the smallest generalization of the code. The ranking behind that order is in references/test-selection.md; pull it when the order is not obvious. For an item that is user-observable behavior at a system boundary, write the outer acceptance test for it first (it will be red until its inner behaviors exist) and record it as the outer loop for that item. For internal or utility behavior with no meaningful system boundary, the outer acceptance test is optional; the inner loop alone is correct.

Apply YAGNI to the list itself. A scenario earns a place only with evidence it is needed now (a user-described need, a named dependency, an existing code path that breaks, a regulation, a real incident). Scenarios that fail the evidence test go to a deferred list with the trigger that would reopen them. Do not pad the list for symmetry or completeness.

Then apply the scope gate to the list. YAGNI asks whether a behavior has evidence it is needed. The scope gate asks a question no amount of evidence answers: would making this test pass require changing a file outside the Step 1 boundary? Ask it of every item, and ask it hardest of items that arrived from a test plan, an analysis report, or an agent finding carrying a severity label. A CRIT or HIGH label is evidence the finding is real. It is never evidence the fix belongs to this ticket, and an item whose own text names a production change as a prerequisite ("this requires first adding an explicit order") is that production change wearing a test's clothes.

The resolution ladder. Work it in order and stop at the first rung that resolves the item. Never skip to implementing the out-of-scope change.

  1. Redesign the test. Most items that trip the gate are asking production code to supply something the test could arrange for itself. Rebuild the setup so the assertion holds without the out-of-scope behavior, and the item stays on the list in its rewritten form. A test that needs the out-of-scope behavior only to make a fixture deterministic always resolves here: that is a test-design problem, and leaning on a production change to make it disappear is the wrong direction of dependence.
  2. Defer the item as its own work. When the test cannot be redesigned, move it off the list and write it up as a ticket: the behavior, the file that would have to change, who else consumes that file, and the change it needs. Report it in Step 5 as work this build did not own. The finding stays alive; it just stops being this build's job.
  3. Escalate. Only when the requested behavior cannot be delivered at all without the out-of-scope change, stop and ask the user. Name the file, its other consumers, the change it needs, and the two ways forward: widen this build's scope to include it, or split it into separate work and drop the dependent behavior from this build. This is the one rung that pauses an otherwise autonomous run, and it is a hard dependency, not a review checkpoint.

Report the test list to the user, along with any item the scope gate moved and which rung resolved it. Unless the verify-plan exception from Step 1 applies, continue to Step 3 immediately without waiting for approval. When that exception applies, present the test list together with the Step 1 scope report and wait for approval before entering the loop.

Step 3: The Red-Green-Refactor Loop

Pick exactly one item from the list. Choose one that teaches you something and that you are confident you can implement in one cycle (Beck's "one step test"). When several items qualify, prefer the one whose passing requires the simplest transformation of the code: a test needing only a constant return comes before one forcing a conditional, and a conditional before a loop — the Transformation Priority Premise, made concrete by the ZOMBIES ordering, both in references/test-selection.md; pull that reference when the choice is not obvious. If every remaining item forces a big leap (a loop or recursion with no smaller test in between), a simpler test is missing: add it to the list and pick it. Then run these three phases in order. Do not collapse them.

Read once, don't reread. Within a single loop iteration, do not reread a file you have already read in this iteration unless you have edited it. When grep returns a line number, use Read with offset and limit to read 20-40 lines around the target — not the entire file. Rereading whole source files between Red and Green of the same behavior is overhead, not discipline.

Red

Write exactly one test for the chosen behavior. Name it for the behavior in the project's convention. Assert an observable outcome through the public interface (Given = arrange the state before; When = the one action under test; Then = assert the observable result). Write no more of the test than is sufficient to fail; a compilation failure is a failure.

Before you run it, check the assertion direction for a fix to broken behavior. The assertion must target the desired correct result, so the red you are about to observe is "correct behavior not yet produced" — not "the error the bug raises was raised successfully." A test that asserts the buggy behavior either passes immediately or goes red for the wrong reason; both look like a satisfied gate and both lock the bug in. (Asserting a raise is still correct when raising is the specified desired behavior; the trap is asserting the error that is the bug.)

Run the resolved test command directly with Bash. Paste the failing assertion plus enough surrounding output (5-10 lines) to confirm the failure reason — the assertion text or the missing symbol you expect, not an unrelated error. If the test passed on its first run, paste only the runner's summary line and stop to diagnose: the observed-failure gate has tripped.

If the test passes on its first run, the observed-failure gate has tripped. Stop. Diagnose one of three causes: the test is not exercising the behavior; the behavior already exists; or — for a fix to broken behavior — the test is asserting the current broken behavior (the error the bug raises), which passes precisely because the bug is still present. If the behavior already exists, cross the item off and pick the next one. If the test is asserting the bug, do not cross the item off — rewrite it to assert the desired correct behavior, so it goes red until the fix lands. Do not write production code off an unobserved red.

With the red observed, check where green would land. Name the files you would edit to make this test pass, before you edit any of them. If one sits outside the Step 1 boundary, the scope gate has tripped: do not edit it, and work the resolution ladder from Step 2 instead. A genuine red says the behavior is missing. It does not say this build owns producing it, and this is the only check that asks. Shared or cross-application code is where the gate matters most, because the blast radius of an edit there reaches consumers nobody in this build is testing.

Green

Write the minimum production code that makes this one test pass. Use the smallest gear that works: Obvious Implementation when you are certain, Fake It (return a constant, generalize later) when you are not, Triangulate (force the abstraction with a second example) only when you are really unsure. Gears are described in references/tdd-loop.md.

While going green, respect the coding standards and ADRs that govern correctness and architectural placement: where this code is allowed to live, which boundary or client it must go through, which contract it must honor. Violating an ADR boundary is not a sin you clean up later — it is the wrong code. Do not apply stylistic or structural polish here (naming sweeps, extraction, formatting passes). That is the refactor hat, and wearing it now violates "no more code than is sufficient to pass the test."

Run the full test suite with Bash. Paste the runner's summary line (pass and fail counts). Paste full output only if a previously passing test broke or something unexpected appears. The gate to leave green is: the new test passes and every previously passing test still passes. If a prior test broke, you are not green — fix it before refactoring.

Refactor (non-skippable)

Only with every test green. Neglecting this step is the most common way to ruin TDD, so it is not optional: either you change something, or you state explicitly "no duplication, structure, or standards issue this cycle" and move on.

Eliminate the duplication you just created. Bring the code into full conformance with the resolved coding standards and ADRs — this is the home for the stylistic and structural standards you deliberately skipped in green.

Apply YAGNI per ../../references/yagni-rule.md: remove duplication, do not add speculative abstraction. Defer speculative structure with the trigger that would reopen it; never add silently, never drop silently.

Change no behavior. Re-run the full suite after the refactor. Paste the runner's summary line — paste full output only if something unexpected appears. The suite must stay green. If a refactor reddened a test, revert it — a refactor that changes behavior is a defect, not a refactor.

Close the cycle

Cross the completed item off the list. Append any scenarios you discovered while implementing (deferred, with their reopen trigger if speculative), but do not implement them now. If the open list has grown past roughly ten items, do not stop for input: flag it prominently as a scope warning, keep going, and record in the final summary that the work exceeded the recommended size and should be split next time. A runaway list is a scope signal, not a reason to pause for a human.

Running collaboratively. When the request asks to review each behavior as it lands, which is what pairing does when it hands work here, stop at this point and hand control back instead of continuing. Present the stop in the shape collaborative-stop-rule.md specifies. Absent such a request, continue as below; an ordinary invocation is unchanged.

Return to the top of Step 3 with the next item. Continue until the list is empty.

Step 4: Close the Outer Loop

For any item that had an outer acceptance test (Step 2), run that test now. It should pass only because its inner behaviors are all implemented with real code (not mocks). If it is still red, the gap is a missing inner behavior: add the missing scenario to the test list and return to Step 3. The acceptance test going green is the signal the user-facing behavior is actually delivered.

Step 5: Final Verification and Summary

Run the full test suite, then the lint command, then the build command, using the resolved commands from Step 1. Paste the summary line from each. Paste full output only when one of them fails. If lint or build fails, that is in scope — fix it (a lint or build break is not a "pre-existing error" to wave off) and re-run.

Summarize for the user:

  • Behaviors implemented, and the state of the test list (done, and any deferred items with their reopen triggers).
  • Any item the scope gate moved, with the rung that resolved it: the redesign that kept it, or the ticket write-up for the out-of-scope change this build declined to make.
  • Which coding standards and ADRs were applied, and where they shaped the code.
  • Any YAGNI deferrals from refactor, each with its reopen trigger.
  • A scope warning if the test list exceeded roughly ten open items, with a recommendation to split future work.
  • The final test, lint, and build status, with output shown, not asserted.
Files (han)
  • references
    • bdd-framing.md 6.7 KB
      # BDD Framing for the TDD Loop
      
      BDD is the reason this skill frames every test as a _behavior_. Dan North started replacing the word "test" with
      "behaviour" because almost every misunderstanding of TDD traced back to the word "test" — where to start, what to test,
      how much, what to name it, why a failure matters. The framing below is how `/tdd` answers those questions mechanically.
      
      ## Name tests for behavior, in the project's convention
      
      A test name describes an observable behavior of the unit, not a method or an implementation detail. North's diagnostic:
      if you cannot phrase the name as a sentence about what the thing _should do_, the behavior may belong elsewhere.
      "should" also keeps the premise challengeable — when a behavior test fails, ask "should it? really?": is the code wrong,
      or is the asserted behavior now out of date?
      
      **Surface syntax follows the project, not a literal "should".** BDD governs the _focus_ (observable behavior), not the
      spelling. If the project uses `it("...")`, `test_...`, `describe/it`, xUnit `TestXxx`, Go `TestXxx` with
      `t.Run("when ...")`, or a discovered coding standard that mandates a naming pattern, use that. The discovered convention
      and any coding standard always win over the word "should". What does not change: the name states a behavior and an
      expected outcome, never an implementation step.
      
      ## Given-When-Then maps to Arrange-Act-Assert
      
      A scenario is one test. The three clauses are the three phases of that test:
      
      - **Given** — the state of the world before the behavior. The arrange/setup. Typically something that already happened.
        Commands that establish state.
      - **When** — the one event or action under test. The act. Exactly one per scenario. If you need an "and" in the When,
        you probably have two scenarios.
      - **Then** — the expected, observable outcome: the behavior the code _should_ produce, which for a fix to broken
        behavior is not what it produces today. The assert. It must be on an observable output: a return value, a visible
        effect, a message or record that leaves the unit (a raised exception is a valid observable output to assert when
        raising is the desired behavior). Never on internal or private state.
      
      Keep scenarios declarative, not imperative. "When the customer requests cash" is a behavior. "When the user clicks
      #submit then waits 200ms" is an implementation procedure that will break on every UI change without the behavior
      changing. Declarative scenarios survive refactoring; imperative ones do not.
      
      ## Outside-in: the double loop
      
      BDD-framed TDD is two nested loops:
      
      - **Outer loop** — a failing acceptance test for a user-observable behavior at a system boundary, written from the
        perspective of someone using the system. Slow loop (a feature's worth of work). It goes green only when every inner
        behavior is implemented with real code.
      - **Inner loop** — ordinary red-green-refactor that makes the acceptance test progressively pass.
      
      The procedure for a user-facing item: write the failing acceptance test, identify the entry point that gets called
      first, start implementing it, and when it needs a collaborator, introduce that collaborator as a test double at the call
      site. The collaborator's interface is _discovered by what the caller needs_, not designed up front. When the entry
      point's test passes, drop down and make the next mocked collaborator real. The acceptance test passing last, against
      real implementations, is the proof no collaborator was forgotten.
      
      Not every list item is user-facing. A pure utility or an internal algorithm has no meaningful acceptance boundary — the
      inner loop alone is correct there. Making the outer loop conditional on whether the behavior is user-observable is
      correct scaling, not a shortcut.
      
      ## Test doubles: mock commands, stub queries
      
      The five doubles (Meszaros, via Fowler): dummy (passed, never used), fake (working but shortcut implementation), stub
      (canned answers to queries), spy (a stub that records calls), mock (pre-programmed with expectations that form a
      specification). Only mocks insist on behavior verification; the rest use state verification.
      
      The working rule for the loop:
      
      - **Stub a query.** The collaborator only feeds data the unit needs to produce its outcome. Assert on the resulting
        state/output, not on the call.
      - **Mock a command / required collaboration.** The interaction _is_ the behavior under test (the unit must tell a
        collaborator to do something). Assert the interaction.
      
      Default to real objects unless using the real thing is awkward (the classicist default). Over-mocking couples the test
      to the implementation: mockist tests break on refactor even when behavior did not change, because they specify exact
      calls and parameters that are not the behavior under test. If a mock is only there to feed a value, it should have been
      a stub. If exact call order or parameters are not the behavior, do not assert on them.
      
      ## BDD-flavored failure modes
      
      - **Gherkin/test that describes UI mechanics** ("clicks the button, fills the field") instead of behavior. Rewrite
        functionally: name the intent, not the clicks.
      - **Asserting internal state** in the Then. Assert observable output only.
      - **Asserting the current buggy behavior** in the Then — for a fix to broken behavior, asserting the error the bug
        raises instead of the desired correct outcome. The test passes while the bug is present and breaks when you fix it.
        Assert what the code should do. (This is not a ban on raise assertions: asserting a raise is correct when raising is
        the specified desired behavior.)
      - **Imperative scenario** that encodes how instead of what. Make it declarative; logic changes should touch step
        definitions, not scenario text.
      - **Testing the mock instead of the behavior** — asserting call mechanics that are not the behavior under test, so the
        test breaks on refactor with no behavior change. Demote the mock to a stub, or assert the observable outcome instead.
      - **Back-filling scenarios from code.** Scenarios come from concrete examples of intended behavior, decided before the
        code, not reverse-engineered from what was written.
      
      ## Sources
      
      - Dan North, "Introducing BDD" (dannorth.net): behaviour over "test", the should-sentence template, "should it?
        really?", "what's the next most important thing the system doesn't do?".
      - Martin Fowler, "GivenWhenThen", "Mocks Aren't Stubs", "UnitTest" (martinfowler.com): GWT mapped to Arrange-Act-Assert;
        stub vs mock; classicist vs mockist; coupling risk of over-mocking.
      - Steve Freeman & Nat Pryce, _Growing Object-Oriented Software, Guided by Tests_: the outer/inner double loop,
        discovering interfaces via mocks at the call site, acceptance test green only with real implementations.
      - Cucumber Gherkin reference and "Writing better Gherkin": Given/When/Then semantics, observable-output rule,
        declarative over imperative.
      
    • failure-modes.md 9 KB
      # How an Agent Fakes TDD, and the Discipline That Catches It
      
      ## Contents
      
      - 1. Writing the test and the production code together
      - 2. Never seeing red
      - 3. Whole-feature steps
      - 4. Skipping the refactor
      - 5. Asserting on implementation detail
      - 6. Applying standards while going green
      - 7. Keeping no test list
      - 8. Refactoring into speculative abstraction
      - 9. Asserting the bug instead of the fix
      - 10. Making a test pass by changing code the ticket does not own
      - The one check that catches most of these
      
      An unguided coding agent reliably fakes TDD. The failure is rarely malice — it is the model optimizing for "tests are
      green at the end" instead of "the tests drove the code". Each failure mode below has a symptom you can observe and a
      gate in the SKILL body that catches it. When you feel the pull toward any of these, that pull is the signal the
      discipline exists to resist.
      
      ## 1. Writing the test and the production code together
      
      **Symptom.** The test file and the production file are created or edited in the same step, then the suite is run once
      and is green on the first run. Red was never observed.
      
      **Why it happens.** It is faster and reads as efficient. The model "knows" the implementation, so writing the failing
      test first feels like theater.
      
      **Discipline.** The observed-failure gate. Write only the test. Run it. Paste the real failure output. Only then write
      production code. A first-run pass is a hard stop, not a success — diagnose why the test did not fail.
      
      ## 2. Never seeing red
      
      **Symptom.** No test run is shown between writing a test and writing the code that satisfies it. The transcript jumps
      from "here is the test" to "here is the implementation".
      
      **Why it happens.** Running the suite mid-cycle feels like overhead when the outcome seems obvious.
      
      **Discipline.** Every cycle shows two pasted runner outputs at minimum: the red run (test fails for the intended reason)
      and the green run (new test passes, all prior tests still pass). Output is shown, never asserted from memory.
      
      ## 3. Whole-feature steps
      
      **Symptom.** One cycle implements the entire feature, then a batch of tests is written to cover it. Or the test list is
      converted into many tests at once and then made to pass together.
      
      **Why it happens.** The model can hold the whole feature at once, so decomposing into one-behavior steps feels
      artificially slow.
      
      **Discipline.** Exactly one list item becomes exactly one test per loop. No more production code than that one test
      requires. Everything else discovered goes on the list, deferred. A long red bar (many failing tests at once) is the
      symptom; the cure is the one-step rule.
      
      ## 4. Skipping the refactor
      
      **Symptom.** Cycles go red, green, red, green. Duplication and structure debt accumulate. Coding standards are never
      applied because the test is already green and the model has moved on.
      
      **Why it happens.** Green feels like done. This is the single most common way TDD is ruined, per Fowler.
      
      **Discipline.** Refactor is a non-skippable named phase. Either you change something (remove duplication, apply the
      standards deferred from green, conform to ADRs) or you state explicitly "no duplication, structure, or standards issue
      this cycle". Silence is not allowed; "green, moving on" is the failure.
      
      ## 5. Asserting on implementation detail
      
      **Symptom.** Tests assert internal state, exact private call sequences, or specific collaborator parameters that are not
      the behavior under test. The tests pass review and then break on the next refactor with no behavior change.
      
      **Why it happens.** Mocking everything and asserting calls is mechanical and looks thorough.
      
      **Discipline.** Assert observable behavior through the public interface. Stub queries, mock only genuine required
      collaborations. If a refactor that changes no behavior breaks a test, the test was asserting implementation — that is a
      test defect, not a code defect.
      
      ## 6. Applying standards while going green
      
      **Symptom.** During the green phase the model runs naming sweeps, extracts helpers, and reformats — adding code beyond
      what the one test needs.
      
      **Why it happens.** "Write clean code" is a strong prior and fires constantly.
      
      **Discipline.** Green obeys only correctness and architectural-placement constraints (where the code lives, which
      boundary it must use, which contract it must honor — violating these is wrong code, not deferrable mess). Stylistic and
      structural standards are the refactor hat. Wearing it during green violates "no more code than is sufficient to pass the
      test".
      
      ## 7. Keeping no test list
      
      **Symptom.** Scenarios are implemented as they occur to the model, or forgotten entirely. Scope drifts. Speculative
      cases get built because they came to mind.
      
      **Why it happens.** The model holds context in the conversation instead of in an explicit artifact, so the list feels
      redundant.
      
      **Discipline.** The test list is a first-class, visible artifact. Discovered scenarios are appended and deferred, never
      implemented in the current loop. Speculative scenarios are deferred with a reopen trigger (YAGNI), not built. The list
      draining is the progress signal; the list ballooning past ~10 open items is a scope warning the skill flags and records
      in its summary while continuing autonomously, not a reason to keep silently grinding through unbounded scope.
      
      ## 8. Refactoring into speculative abstraction
      
      **Symptom.** The refactor step introduces an interface with one implementation, a configuration knob no caller sets, or
      a generalization from a single example — all justified as "for future flexibility".
      
      **Why it happens.** Refactor is read as "make it sophisticated" rather than "remove the duplication you just made".
      
      **Discipline.** YAGNI is first-class in refactor. Duplication is a hint, not a command. Abstract only when two or more
      concrete examples force it (Rule of Three / Triangulate). Speculative structure is a YAGNI candidate: defer it with the
      trigger that would reopen it and tell the user. Refactor removes duplication; it does not add speculation.
      
      ## 9. Asserting the bug instead of the fix
      
      **Symptom.** The work is a fix to existing broken behavior, and the test asserts the error the bug raises (or the wrong
      value it returns) rather than the desired correct result. It passes on first run because the bug is still present, or it
      goes red for a reason other than "correct behavior not produced". Either way the gate looks satisfied, and fixing the
      code then breaks the test.
      
      **Why it happens.** The model describes the behavior it observes now. For a bug the observable behavior today _is_ the
      error, so "assert the observable outcome" reads as "assert that the error is raised".
      
      **Discipline.** A regression test asserts the _desired correct_ behavior: it fails because the bug is present and passes
      once the fix lands. A bug-fix test that is green before the fix is the tell — rewrite it, do not cross the item off. The
      boundary: asserting that the code raises is the _right_ test when raising is the specified desired behavior (raise on
      invalid input). The failure mode is asserting the error that _is_ the bug being fixed.
      
      ## 10. Making a test pass by changing code the ticket does not own
      
      **Symptom.** A list item goes red exactly as designed, and green arrives by editing a shared library, engine, or package
      that other applications consume. The observed-failure gate is satisfied, every test is green, and the branch carries a
      production change the ticket never authorized. It often compounds: a later test leans on the new behavior for a
      deterministic setup, so the out-of-scope change becomes load-bearing for work that could have arranged its own fixture.
      
      **Why it happens.** The item usually arrives carrying authority. A test plan or an analysis agent found something real,
      labeled it CRIT, and the label reads as permission. The red is genuine, the fix is small, and the finding is honest, so
      every local signal says proceed. Nothing in the moment asks the one question that matters.
      
      **Discipline.** The scope gate. A genuine red proves the behavior is missing, never that this build owns producing it.
      Before every production edit, name the file you are about to change and test it against the scope boundary recorded in
      Step 1. A severity label ranks a finding's importance and says nothing about whose ticket it belongs to; an item whose
      own text names a production change as a prerequisite is that change wearing a test's clothes. Work the resolution
      ladder: redesign the test so it does not need the out-of-scope behavior, defer the item as its own ticket, and escalate
      only when the requested behavior cannot be delivered without it. A test that needs the change only to make a fixture
      deterministic never reaches the second rung.
      
      ## The one check that catches most of these
      
      Before every production-code edit, you must be able to point to a specific test that you ran and watched fail for the
      intended reason in this loop, and to a scope boundary that contains the file you are about to change. If the first is
      missing, you are in one of failure modes 1 through 9: stop and get back to red. If the second is missing, you are in
      failure mode 10: stop and work the resolution ladder.
      
    • tdd-loop.md 5.8 KB
      # The TDD Loop: Canon, Laws, and Gears
      
      This is the canonical reference for the red-green-refactor mechanics the `/tdd` skill enforces. It is the single source
      for the verbatim rules — the SKILL body links here rather than restating them.
      
      ## Robert C. Martin's Three Laws of TDD
      
      Use this phrasing. It resolves the compile-vs-assertion question and the one-test-at-a-time question explicitly:
      
      1. You are not allowed to write any production code unless it is to make a failing unit test pass.
      2. You are not allowed to write any more of a unit test than is sufficient to fail; and compilation failures are
         failures.
      3. You are not allowed to write any more production code than is sufficient to pass the one failing unit test.
      
      The laws operate second-by-second. You iterate them roughly a dozen times before a single test is complete. You are
      never more than a few unwritten lines from a run, and never more than one change from a green or red bar.
      
      ## Canon TDD (Kent Beck)
      
      The reference loop. Deviations should be deliberate, not accidental:
      
      1. Write a list of the test scenarios you want to cover.
      2. Turn exactly one item on the list into an actual, concrete, runnable test.
      3. Change the code to make the test (and all previous tests) pass, adding items to the list as you discover them.
      4. Optionally refactor to improve the implementation design.
      5. Until the list is empty, go back to step 2.
      
      The test list is a first-class artifact. It starts with every operation you know you need and the null/degenerate
      version of each. As you make tests pass, the implementation implies new tests and refactorings — write them on the list,
      do not implement them now. Items left when the session ends are handled explicitly: continue them next session, or move
      clearly out-of-scope refactorings to a "later" list. A test you can think of that might not work is never moved to
      "later".
      
      ## The mantra
      
      - **Red** — write a small test that does not work, and perhaps does not even compile at first.
      - **Green** — make the test work quickly, committing whatever sins are necessary in the process (duplication, hard-coded
        values, ugliness).
      - **Refactor** — eliminate the duplication created in just getting the test to work.
      
      "Committing sins" means duplication and poor structure are tolerated _temporarily_, to be removed in refactor. It does
      not mean writing code that violates an architectural boundary or a correctness contract — that is not a sin you clean up
      later, it is the wrong code. This is why the `/tdd` skill honors correctness and placement standards during green but
      defers stylistic and structural conformance to refactor.
      
      ## Two hats
      
      Making a test pass and refactoring are different activities done at different times. "Make it run, then make it right."
      Mixing refactoring into the make-it-pass step is the classic two-hats mistake. Refactor only when every test is green. A
      structural change while a test is red is not a refactor.
      
      ## The observed-failure gate
      
      The single highest-value invariant, derived from Law 1 + Law 2 + Beck's test-first ordering: no production-code change
      is permitted unless a test has been run and observed to fail for the intended reason in this loop. A test that passes
      the first time it is ever run was never red — that is a process violation signal, not a success. Diagnose it with the
      three-cause check in the `/tdd` skill's Red step: the test does not exercise the behavior, the behavior already exists,
      or — for a fix to broken behavior — the test asserts the current broken behavior (the error the bug raises) and so
      passes while the bug is still present.
      
      ## Implementation gears (choosing step size)
      
      TDD is not blind rule-following; it is choosing step size and feedback by conditions. Three gears, smallest step last:
      
      - **Obvious Implementation (second gear).** You are certain how to implement the operation. Type the real code.
        Guardrail: if you start getting surprised by red bars, downshift immediately and take smaller steps.
      - **Fake It (small step).** You have a broken test and are not certain. Return a constant. Once green, generalize the
        constant into an expression. The duplication between test and code is removed in refactor — this is why Fake It does
        not violate "no unneeded code".
      - **Triangulate (smallest step).** You are really unsure of the right abstraction. Force it: add a second concrete
        example with different values so one parameter or branch cannot satisfy both, and let the two examples drive the
        generalization. Use only when genuinely unsure; otherwise Obvious Implementation or Fake It.
      
      The tougher or less familiar the problem, the smaller the step. Reserve Obvious Implementation for genuinely simple,
      certain operations.
      
      ## How much to test
      
      Test count is driven by the confidence the code needs, not by a coverage number. If knowledge of the implementation
      gives real confidence in the absence of a test, that test may not be worth writing. Tests that execute code but assert
      nothing meaningful are not TDD tests — start each test from the assertion (Assert First) and derive the expected value
      independently from the specification, never by pasting back the program's own output.
      
      ## Sources
      
      - Kent Beck, _Test-Driven Development: By Example_ (2002): Test List, Implementation Strategies (Fake It, Triangulate,
        Obvious Implementation), Assert First, step size and feedback.
      - Kent Beck, "Canon TDD" (tidyfirst.substack.com): the five-step loop and the named mistakes (two-hats, batch-converting
        the list, abstracting too soon).
      - Robert C. Martin, "The Three Rules of TDD" and "The Cycles of TDD" (blog.cleancoder.com): the Three Laws and the
        nested cycle timescales.
      - Martin Fowler, "Test Driven Development" and "Self Testing Code" (martinfowler.com): red-green-refactor summary;
        refactor is the most-skipped step; run the whole suite frequently.
      
    • test-selection.md 12.3 KB
      # Choosing the Next Test: TPP and ZOMBIES
      
      ## Contents
      
      - The Transformation Priority Premise (TPP)
      - Using the ranking to pick the next test
      - The decision-point rule
      - ZOMBIES: the same idea as a concrete ordering
      - How this composes with the rest of the loop
      - Worked example: `sum(numbers)`
      - Sources
      
      This is the canonical reference for the test-selection heuristics the `/tdd` skill uses when it orders the test list
      (Step 2) and picks the next item (Step 3). The loop tells you to write a failing test and make it pass. It does not tell
      you which test to write next. The Transformation Priority Premise and ZOMBIES both answer exactly that question, and
      they answer it the same way:
      
      > Choose the next test that can be satisfied by the simplest transformation of the code.
      
      The core principle (Robert C. Martin): **as the tests get more specific, the code gets more generic.** You steer that
      progression by selecting tests in order of the transformation they demand — simplest first. These are test-selection
      heuristics, applied while choosing what to test next. They are never a menu for hacking an implementation to green after
      the test is chosen.
      
      ## The Transformation Priority Premise (TPP)
      
      Transformations are the counterpart of refactorings: a refactoring changes structure without changing behavior; a
      transformation changes behavior by generalizing structure. TPP ranks the transformations a new passing test can force on
      the code, from simplest to most complex:
      
      | #   | Transformation               | A test at this rank forces…                          |
      | --- | ---------------------------- | ---------------------------------------------------- |
      | 1   | `{} → nil`                   | code to exist at all, returning nil/null             |
      | 2   | `nil → constant`             | a fixed constant value                               |
      | 3   | `constant → constant+`       | a simple constant to become a more complex one       |
      | 4   | `constant → scalar`          | a constant to become a variable or argument          |
      | 5   | `statement → statements`     | more unconditional statements                        |
      | 6   | `unconditional → if`         | the execution path to split with a conditional       |
      | 7   | `scalar → array`             | a variable to become an array                        |
      | 8   | `array → container`          | an array to become a richer container (list/map/set) |
      | 9   | `statement → tail-recursion` | tail recursion                                       |
      | 10  | `if → while`                 | a conditional to become a loop                       |
      | 11  | `statement → recursion`      | general (non-tail) recursion                         |
      | 12  | `expression → function`      | an expression to become a function or algorithm      |
      | 13  | `variable → assignment`      | a variable's value to be reassigned or mutated       |
      | 14  | `case`                       | a new case or else added to an existing conditional  |
      
      This is the canonical fourteen-transformation list from the original article. Martin notes the ordering is
      language-specific (in an imperative language you might rank iteration and assignment above recursion) and that "there
      are likely others." He also frames it as a _premise_, not a theorem — an informal ranking, not a law. Treat the ranks as
      a strong default and apply design judgment when several simplest-first paths exist.
      
      ## Using the ranking to pick the next test
      
      - When several candidate tests remain on the list, **write the one whose passing requires the highest-priority
        (simplest) transformation**. A test that needs only `nil → constant` comes before one that needs `unconditional → if`,
        which comes before one that needs `if → while`.
      - If the only test you can think of would force a low-priority transformation (a loop, recursion, a whole algorithm)
        early, that is the signal a **simpler test is missing** — find it, add it to the list, and write it first.
      - When two candidates demand the same transformation, the ranking has no opinion. Break the tie toward the smaller input
        or the example the user supplied; either keeps the step small, and the loser stays on the list.
      - The maintained test list is what makes this work: TPP selects by _scanning_ the behaviors not yet tested and comparing
        the transformations they would force. This is one more reason the test list is a first-class artifact.
      
      Why select tests this way:
      
      - **Simpler designs.** The code grows by the smallest generalization each cycle: a constant before a scalar, an `if`
        before a `while`.
      - **Fewer impasses.** A premature low-priority test can force the implementation into a corner that a simpler ordering
        would have avoided.
      - **Small, reversible steps.** Every green is one small transformation away from the last green, which keeps the loop's
        step size honest.
      
      ## The decision-point rule
      
      TPP's sharpest use is at a decision point: when more than one transformation could make the current failing test pass,
      prefer the one **higher on the list**. Martin demonstrates the stakes with a sort. At "two elements out of order" you
      can compare-and-swap (an assignment, near the bottom of the list) or compare and return a new array (no assignment).
      Follow the swap and you derive bubble sort; avoid the low-priority assignment and quicksort "almost falls out
      inevitably." The same failing test, two transformations, two very different algorithms — the ranking is what tips you
      toward the better one.
      
      Bending the ranking is sometimes correct. When a transformation legitimately needs a helper to land (a recursive step
      needs a function that takes the tail of a collection, say), taking that step is fine if it is the smallest _real_
      increment available. The goal is the next smallest increment, not literal obedience to the table.
      
      A single test may also legitimately force a small cluster of transformations at once — a loop usually brings an
      accumulator assignment with it, and a new scalar often arrives inside a new conditional. Compare candidates by the
      deepest-ranked transformation in the cluster each would force; the ranking still orders the tests even when no test maps
      to exactly one row.
      
      ## ZOMBIES: the same idea as a concrete ordering
      
      James Grenning's ZOMBIES mnemonic makes the simplest-first order memorable. **ZOM** (Zero, One, Many) is the core
      progression; **BIES** covers what else to select for as you go:
      
      | Letter | Stands for           | Choose a test that…                                                  |
      | ------ | -------------------- | -------------------------------------------------------------------- |
      | **Z**  | Zero                 | exercises the empty / zero / null case                               |
      | **O**  | One                  | exercises exactly one element or occurrence                          |
      | **M**  | Many / More          | exercises many — the test that forces the loop or collection         |
      | **B**  | Boundary behaviors   | probes limits: first/last, off-by-one, min/max                       |
      | **I**  | Interface definition | pins the smallest viable signature the caller needs                  |
      | **E**  | Exceptional behavior | drives invalid input and failures at the public boundary             |
      | **S**  | Simple               | keeps each scenario, and the step it implies, as simple as it can be |
      
      Pick tests **Z → O → M** for the happy path. The interface (**I**) emerges from the very first test; boundaries and
      exceptions (**B**, **E**) fill in once the core generalization exists. Never select a Many test before a One test has
      forced the code to handle a single case — Zero → One → Many is exactly TPP's simplest-transformation-first ordering,
      made concrete.
      
      Rule of thumb: each test should add **one** small new behavior and demand the **smallest** possible generalization. If
      the only remaining test forces a large leap, a smaller test is missing between it and the last green.
      
      ## How this composes with the rest of the loop
      
      - **User value picks the behavior; TPP/ZOMBIES picks the test.** Step 2 orders the list outside-in by user value — the
        next _behavior_ is the most important thing the system does not yet do. When driving that behavior expands into
        several candidate tests (the zero case, the one case, the many case, the boundaries), simplest-transformation-first
        governs their order. The two orderings answer different questions and do not compete.
      - **This is how you find Beck's "one step test."** Step 3 says to pick an item that teaches you something and that you
        can implement in one cycle. TPP and ZOMBIES are the deterministic way to find that item: the test needing the simplest
        transformation _is_ the one-step test.
      - **The gears stay small because the tests were chosen small.** Fake It is `nil → constant`. Triangulate adds the second
        example that forces `constant → scalar`. A test chosen simplest-first bounds how much the green step can require; a
        test chosen too coarse is what forces Obvious Implementation on something that deserved smaller steps.
      - **A first-run pass can mean the code has generalized.** When a chosen test passes on its first run, the
        observed-failure gate trips and one diagnosis is "the behavior already exists." In TPP terms the implementation has
        gone generic ahead of the tests — the loop or abstraction already covers this case. Cross the item off per the gate;
        the passing test stays as documentation of the boundary it confirms.
      
      Diagnostic signals, all about test choice:
      
      - _About to write a Many/loop test first?_ You skipped Zero and One. The Zero test needs only `nil → constant`.
      - _The only test you can think of forces recursion or a `while`?_ A simpler test is missing; a One-case test usually
        drops the required transformation to `unconditional → if` or `statement → statements`.
      - _A test forces a huge implementation leap?_ It is too coarse. Add a smaller test between it and the last green.
      - _The implementation came out over-engineered?_ The test selected was too big. Back up and choose a simpler one.
      - _The chosen test will not even run cleanly_ (a cascading error rather than a clean assertion failure)? Do not force
        it. Put it back on the list, pick an item that fails cleanly, and return to the deferred item once the code has caught
        up.
      
      ## Worked example: `sum(numbers)`
      
      Each row is the next behavior chosen because it needs the simplest remaining transformation:
      
      | Order | Behavior chosen (ZOMBIES)          | Transformation it drives (TPP)                              |
      | ----- | ---------------------------------- | ----------------------------------------------------------- |
      | 1     | **Z**ero — `sum([]) == 0`          | `nil → constant`: `return 0`, the simplest available        |
      | 2     | **O**ne — `sum([5]) == 5`          | `constant → scalar`: read the single element                |
      | 3     | **M**any — `sum([5, 3]) == 8`      | `if → while`: the first test that genuinely forces the loop |
      | 4     | **B**oundary — `sum([-1, 1]) == 0` | none — confirms the generalization holds                    |
      | 5     | **E**xception — `sum(None)` raises | `unconditional → if`: a guard at the public boundary        |
      
      Each row adds exactly one behavior and uses the simplest transformation that keeps the suite green. Row 3 is where Many
      forces the generic loop — and not a moment earlier. Row 4 is a confirming test: it passes without a production change,
      which is the "behavior already exists" arm of the observed-failure gate doing its job.
      
      ## Sources
      
      - Robert C. Martin, "The Transformation Priority Premise" (blog.cleancoder.com, 2013): transformations as the
        counterpart of refactorings, the fourteen-rank list, the language-specificity and premise-not-theorem caveats.
      - Robert C. Martin, "Transformation Priority and Sorting" (blog.cleancoder.com, 2013): the decision-point rule and the
        bubble-sort vs quicksort demonstration.
      - Robert C. Martin, "The Cycles of TDD" (blog.cleancoder.com, 2014): "as the tests get more specific, the code gets more
        generic."
      - James Grenning, "TDD Guided by ZOMBIES" (blog.wingman-sw.com): the Zero, One, Many, Boundaries, Interface, Exceptions,
        Simple mnemonic and ordering.
      - Jeff Langr, _Modern C++ Programming with Test-Driven Development_ (Pragmatic Bookshelf): the maintained test list as
        near-essential to TPP, bending the ranking for the smallest real increment, and deferring a test that will not fail
        cleanly.
      
  • scripts
    • detect-tdd-context.sh 3.3 KB
      #!/usr/bin/env bash
      # Detect git state and infer test/lint/build commands from project manifests.
      # Run ONCE in Step 1, only when CLAUDE.md / project-discovery.md did not
      # resolve the commands. Per-cycle test runs do NOT use this script — they call
      # the resolved test command directly so the loop is not interrupted by a
      # per-invocation approval every cycle.
      #
      # Inference is a best-effort fallback. Emitted commands are suggestions for the
      # user to confirm, not authority. Output is line-oriented key: value pairs.
      
      # --- git state ---------------------------------------------------------------
      if command -v git &>/dev/null && git rev-parse --is-inside-work-tree &>/dev/null; then
        echo "git-available: true"
        BRANCH=$(git branch --show-current)
        echo "branch: ${BRANCH:-none}"
        if git symbolic-ref --short refs/remotes/origin/HEAD &>/dev/null; then
          echo "default-branch: $(git symbolic-ref --short refs/remotes/origin/HEAD)"
        else
          echo "default-branch: none"
        fi
      else
        echo "git-available: false"
        echo "branch: none"
        echo "default-branch: none"
      fi
      
      # --- manifest-inferred commands ----------------------------------------------
      # First match wins per category. These are suggestions; the user confirms.
      TEST_CMD=""
      LINT_CMD=""
      BUILD_CMD=""
      
      if [ -f package.json ]; then
        echo "manifest: package.json"
        grep -q '"test"[[:space:]]*:' package.json && TEST_CMD="npm test"
        grep -q '"lint"[[:space:]]*:' package.json && LINT_CMD="npm run lint"
        grep -q '"build"[[:space:]]*:' package.json && BUILD_CMD="npm run build"
      fi
      
      if [ -z "$TEST_CMD" ] && { [ -f pyproject.toml ] || [ -f pytest.ini ] || [ -f setup.cfg ] || [ -f tox.ini ]; }; then
        echo "manifest: python (pyproject/pytest)"
        TEST_CMD="pytest"
      fi
      
      if [ -z "$TEST_CMD" ] && [ -f go.mod ]; then
        echo "manifest: go.mod"
        TEST_CMD="go test ./..."
        BUILD_CMD="go build ./..."
      fi
      
      if [ -z "$TEST_CMD" ] && [ -f Cargo.toml ]; then
        echo "manifest: Cargo.toml"
        TEST_CMD="cargo test"
        BUILD_CMD="cargo build"
      fi
      
      if [ -z "$TEST_CMD" ] && { [ -f Gemfile ] || [ -f Rakefile ] || [ -d spec ]; }; then
        echo "manifest: ruby (Gemfile/Rakefile)"
        if [ -d spec ]; then TEST_CMD="bundle exec rspec"; else TEST_CMD="bundle exec rake test"; fi
      fi
      
      if [ -z "$TEST_CMD" ] && [ -f mix.exs ]; then
        echo "manifest: mix.exs"
        TEST_CMD="mix test"
      fi
      
      if [ -z "$TEST_CMD" ] && { [ -f pom.xml ]; }; then
        echo "manifest: pom.xml"
        TEST_CMD="mvn test"
        BUILD_CMD="mvn package"
      fi
      
      if [ -z "$TEST_CMD" ] && { [ -f build.gradle ] || [ -f build.gradle.kts ]; }; then
        echo "manifest: gradle"
        TEST_CMD="gradle test"
        BUILD_CMD="gradle build"
      fi
      
      if [ -z "$TEST_CMD" ] && [ -n "$(find . -maxdepth 3 -name '*.csproj' -print -quit 2>/dev/null)" ]; then
        echo "manifest: dotnet (.csproj)"
        TEST_CMD="dotnet test"
        BUILD_CMD="dotnet build"
      fi
      
      # A Makefile with a test target is a common override regardless of language.
      if [ -f Makefile ] && grep -qE '^test:' Makefile; then
        echo "manifest: Makefile (test target)"
        [ -z "$TEST_CMD" ] && TEST_CMD="make test"
        grep -qE '^lint:' Makefile && [ -z "$LINT_CMD" ] && LINT_CMD="make lint"
        grep -qE '^build:' Makefile && [ -z "$BUILD_CMD" ] && BUILD_CMD="make build"
      fi
      
      echo "inferred-test-command: ${TEST_CMD:-none}"
      echo "inferred-lint-command: ${LINT_CMD:-none}"
      echo "inferred-build-command: ${BUILD_CMD:-none}"
      
  • SKILL.md 22 KB
    ---
    name: tdd
    description: >
      Write code through a disciplined, BDD-framed Test-Driven Development loop: build a behavior test list, then drive each
      behavior through red-green-refactor with an enforced observed-failure gate. Use when the user wants to implement,
      build, or write code test-first, "do TDD", follow "red-green-refactor", drive code from tests, choose the next test by
      the Transformation Priority Premise (TPP) or ZOMBIES ordering, or grow a feature behavior-by-behavior with tests
      leading. This skill writes and changes code; it does not produce a test plan document (use automated-test-planning, or manual-test-planning for a plan a person
      runs by hand), review or
      audit existing code (use code-review), restructure existing code outside a TDD loop (use refactor, or plan-a-change to
      plan a multi-module restructure), specify what a feature should do (use plan-a-feature), or find the root cause of a
      bug (use investigate). Runs its loop to
      completion without pausing for review; to review each behavior as it lands, use pairing.
    argument-hint: "[what to build, a behavior to drive, or a path to a spec/plan]"
    allowed-tools:
      Read, Write, Edit, Glob, Grep, Agent, Bash(git *), Bash(find *), Bash(npm *), Bash(npx *), Bash(pnpm *), Bash(yarn *),
      Bash(pytest *), Bash(python3 *), Bash(go *), Bash(cargo *), Bash(make *), Bash(bundle *), Bash(rake *),
      Bash(bash "${CLAUDE_PLUGIN_ROOT}/scripts/han-config-dir.sh")
    ---
    
    ## Project Context
    
    - git installed: !`which git 2>/dev/null || echo "not installed"`
    - current branch: !`git branch --show-current 2>/dev/null || echo unknown`
    - CLAUDE.md: !`find . -maxdepth 1 -name "CLAUDE.md" -type f`
    - project-discovery.md: !`find . -maxdepth 3 -name "project-discovery.md" -type f`
    - personal config directory: !`bash "${CLAUDE_PLUGIN_ROOT}/scripts/han-config-dir.sh" 2>/dev/null || echo "$HOME/.claude"`
    - project .han/config.md: !`cat .han/config.md 2>/dev/null || echo ""`
    
    As your first action, use the Read tool on `.han/config.md` inside the `personal config directory` path above. A read
    that returns no file is no personal configuration: continue silently. When that file or the `project .han/config.md`
    probe supplies content, apply it per [config-rule.md](../../references/config-rule.md), which governs precedence
    between the two files, relative-path resolution, and what to do with a file that reads but cannot be used.
    
    ## Constraints (read before anything else)
    
    This skill writes production and test code in your working tree. It is an execution skill, not a document generator.
    These constraints shape every step and override any instinct to move faster.
    
    - **The observed-failure gate is load-bearing.** No production-code change until a test has been run and observed to
      fail for the intended reason in this loop. A test that passes on first run is a stop-and-diagnose signal, not
      progress. This single rule is what separates real TDD from TDD-flavored code. The verbatim Three Laws and Canon TDD
      steps it derives from are in [references/tdd-loop.md](./references/tdd-loop.md); pull that reference when a step needs
      the canon or the implementation gears.
    - **The scope gate is the observed-failure gate's companion.** The observed-failure gate proves a red is genuine. It
      does not prove the test deserved to exist in this build. No production-code change outside the scope boundary recorded
      in Step 1, and least of all in shared or cross-application code other consumers depend on. A list item whose green
      requires an out-of-scope edit is a stop, never an implement; Step 2 carries the resolution ladder that Step 3 works.
    - **Two hats.** Never refactor while any test is red. See [references/tdd-loop.md](./references/tdd-loop.md) for the
      canonical statement.
    - **One behavior at a time.** Exactly one test list item becomes one runnable test per loop. Newly discovered scenarios
      are written to the list and deferred, never implemented in the current loop.
    - **BDD framing.** Tests describe observable behavior, named in the project's existing test-naming convention, asserting
      outcomes through the public interface — never private state. The behavior-naming and Given/When/Then protocol is in
      [references/bdd-framing.md](./references/bdd-framing.md); pull it when Step 2 needs it.
    - **You will be tempted to fake this.** The specific ways an agent fakes TDD, and the discipline that catches each, are
      in [references/failure-modes.md](./references/failure-modes.md); pull it when a loop feels off (a test passes on first
      run, no red is shown, the implementation has outrun the test, refactor is being skipped).
    - **YAGNI governs the refactor step and the test list.** Apply the rule in
      [../../references/yagni-rule.md](../../references/yagni-rule.md): remove duplication, but do not add abstractions,
      configuration, or indirection without evidence. Speculative structure added "for flexibility" during refactor is a
      YAGNI candidate. Speculative scenarios on the test list are deferred with a reopen trigger, never silently added.
    
    # Test-Driven Development
    
    ## Step 1: Resolve Project Config and Confirm Scope
    
    **Resolve commands.** Read CLAUDE.md's `## Project Discovery` section for the test command (under
    `### Commands and Tests`, not `### Frameworks and Tooling`), the lint command, the build command, language, and
    framework. If absent, fall back to `project-discovery.md`. If still absent, run
    `${CLAUDE_SKILL_DIR}/scripts/detect-tdd-context.sh` and parse its output for git state and manifest-inferred commands.
    Store the resolved test, lint, and build commands for use in every later step.
    
    **Resolve standards and decisions.** Resolve the coding-standards directory and ADR directory the same way: read
    CLAUDE.md's `## Project Discovery` section; fall back to `project-discovery.md`; fall back to Glob defaults (`docs/`,
    `docs/adr/`, `docs/coding-standards/`, `docs/decisions/`). Also check `CLAUDE.md` and `AGENTS.md` for inline standards.
    **Read the standards and ADRs whose titles, paths, or one-line summaries indicate they govern the area being built. Cap
    at five documents; if more than five look relevant, list them and read only the five with the strongest apparent
    relevance — defer the rest until refactor surfaces a need.** These govern the green and refactor steps. If none exist,
    state that plainly and plan to infer conventions from the surrounding code instead.
    
    **Resolve the scope boundary.** Name, in files and directories, what this work is allowed to change, because the scope
    gate tests every candidate edit against it. Inside the boundary: the files, directories, or module the request names,
    plus the tests that cover them. Outside it: everything the named code merely reaches, meaning shared libraries, engines,
    packages, and any code a second application or consumer also uses, plus code another team owns per `CODEOWNERS`. When
    the request names no files, take the application or package the requested behavior lives in as the boundary and treat
    its dependencies as outside it. Record the boundary; you will test list items and production edits against it.
    
    **Report scope, then proceed (no gate).** This skill runs autonomously after the initial request: it does not stop for
    confirmation. State to the user, in a few lines: the behavior or feature to be built, **whether this is net-new behavior
    or a fix to existing broken behavior** (a reported bug, a failing case, a fix being driven back in after `/investigate`,
    or code that already exhibits the error — recognize the fix case from those signals, not only from the word "bug"), the
    scope boundary you just recorded, the resolved test/lint/build commands, the standards and ADRs found (or that none
    were), the current branch, and that the skill will now write code in a red-green-refactor loop. If `current branch` from
    Project Context is the repository's default branch (`main` or `master`), recommend working on a branch, but do not wait
    for an answer. This is a report the user reads while the work runs, not a gate. Continue immediately to Step 2 without
    waiting for a response.
    
    **The one exception.** If the initial request or the provided context explicitly states the human wants to review,
    verify, or approve the plan or test list before implementation, then this becomes a gate: build the test list in Step 2,
    present it together with this scope report, and wait for approval before starting the Step 3 loop. Absent an explicit
    request like that, the skill runs to completion without further human input.
    
    Two things can still block a run, both hard dependencies rather than discretionary checkpoints. A missing test command
    is the first: if it could not be resolved from CLAUDE.md, `project-discovery.md`, the discovery script, or manifest
    inference, ask the user for it, because TDD is impossible without a way to run tests. Exhaust inference before asking.
    The second is the top rung of the scope gate's resolution ladder in Step 2, reached only when the requested behavior
    cannot be delivered without an out-of-scope change.
    
    ## Step 2: Build the BDD Test List
    
    Turn the requested feature or behavior into a test list (Kent Beck's "test list" pattern). Each item is one observable
    behavior, phrased as a behavior sentence, not as an implementation note. "Returns the unrounded fee for a sub-dollar
    charge" is a list item; "use a BigDecimal" is not. Follow [references/bdd-framing.md](./references/bdd-framing.md) for
    how to phrase and name behaviors, and which test-naming convention to adopt (the project's existing convention and any
    discovered coding standard win over a literal "should" default).
    
    **Fixing existing broken behavior is a regression test, not a bug-asserting test.** When the work fixes broken behavior,
    the list item names the _desired correct_ behavior, not the current broken one: "returns the rounded total for a refund"
    (red now because the bug is present, green once the fix lands), never "raises ArgumentError on a refund" — a test that
    asserts the error the bug produces passes while the bug is present and breaks when you fix it, locking the bug in. The
    regression test asserts what the code _should_ do. The boundary: asserting that the code raises is the _correct_ test
    when raising is the specified desired behavior (raise on invalid input); it is wrong only when the raised error _is the
    bug being fixed_.
    
    Order the list outside-in by user value: the next item is the most important thing the system does not yet do. When one
    behavior expands into several candidate tests (the empty case, the single case, the many case, the boundaries), order
    those tests simplest-first — Zero → One → Many — so each test forces the smallest generalization of the code. The
    ranking behind that order is in [references/test-selection.md](./references/test-selection.md); pull it when the order
    is not obvious. For an item that is **user-observable behavior at a system boundary**, write the outer acceptance test
    for it _first_ (it will be red until its inner behaviors exist) and record it as the outer loop for that item. For
    internal or utility behavior with no meaningful system boundary, the outer acceptance test is optional; the inner loop
    alone is correct.
    
    Apply YAGNI to the list itself. A scenario earns a place only with evidence it is needed now (a user-described need, a
    named dependency, an existing code path that breaks, a regulation, a real incident). Scenarios that fail the evidence
    test go to a deferred list with the trigger that would reopen them. Do not pad the list for symmetry or completeness.
    
    **Then apply the scope gate to the list.** YAGNI asks whether a behavior has evidence it is needed. The scope gate asks
    a question no amount of evidence answers: would making this test pass require changing a file outside the Step 1
    boundary? Ask it of every item, and ask it hardest of items that arrived from a test plan, an analysis report, or an
    agent finding carrying a severity label. A CRIT or HIGH label is evidence the finding is real. It is never evidence the
    fix belongs to this ticket, and an item whose own text names a production change as a prerequisite ("this requires
    first adding an explicit `order`") is that production change wearing a test's clothes.
    
    **The resolution ladder.** Work it in order and stop at the first rung that resolves the item. Never skip to
    implementing the out-of-scope change.
    
    1. **Redesign the test.** Most items that trip the gate are asking production code to supply something the test could
       arrange for itself. Rebuild the setup so the assertion holds without the out-of-scope behavior, and the item stays on
       the list in its rewritten form. A test that needs the out-of-scope behavior only to make a fixture deterministic
       always resolves here: that is a test-design problem, and leaning on a production change to make it disappear is the
       wrong direction of dependence.
    2. **Defer the item as its own work.** When the test cannot be redesigned, move it off the list and write it up as a
       ticket: the behavior, the file that would have to change, who else consumes that file, and the change it needs.
       Report it in Step 5 as work this build did not own. The finding stays alive; it just stops being this build's job.
    3. **Escalate.** Only when the requested behavior cannot be delivered at all without the out-of-scope change, stop and
       ask the user. Name the file, its other consumers, the change it needs, and the two ways forward: widen this build's
       scope to include it, or split it into separate work and drop the dependent behavior from this build. This is the one
       rung that pauses an otherwise autonomous run, and it is a hard dependency, not a review checkpoint.
    
    Report the test list to the user, along with any item the scope gate moved and which rung resolved it. Unless the
    verify-plan exception from Step 1 applies, continue to Step 3 immediately without waiting for approval. When that
    exception applies, present the test list together with the Step 1 scope report and wait for approval before entering the
    loop.
    
    ## Step 3: The Red-Green-Refactor Loop
    
    Pick exactly one item from the list. Choose one that teaches you something and that you are confident you can implement
    in one cycle (Beck's "one step test"). When several items qualify, prefer the one whose passing requires the simplest
    transformation of the code: a test needing only a constant return comes before one forcing a conditional, and a
    conditional before a loop — the Transformation Priority Premise, made concrete by the ZOMBIES ordering, both in
    [references/test-selection.md](./references/test-selection.md); pull that reference when the choice is not obvious. If
    every remaining item forces a big leap (a loop or recursion with no smaller test in between), a simpler test is missing:
    add it to the list and pick it. Then run these three phases in order. Do not collapse them.
    
    **Read once, don't reread.** Within a single loop iteration, do not reread a file you have already read in this
    iteration unless you have edited it. When `grep` returns a line number, use `Read` with `offset` and `limit` to read
    20-40 lines around the target — not the entire file. Rereading whole source files between Red and Green of the same
    behavior is overhead, not discipline.
    
    ### Red
    
    Write exactly one test for the chosen behavior. Name it for the behavior in the project's convention. Assert an
    observable outcome through the public interface (Given = arrange the state before; When = the one action under test;
    Then = assert the observable result). Write no more of the test than is sufficient to fail; a compilation failure is a
    failure.
    
    **Before you run it, check the assertion direction for a fix to broken behavior.** The assertion must target the desired
    correct result, so the red you are about to observe is "correct behavior not yet produced" — not "the error the bug
    raises was raised successfully." A test that asserts the buggy behavior either passes immediately or goes red for the
    wrong reason; both look like a satisfied gate and both lock the bug in. (Asserting a raise is still correct when raising
    is the specified desired behavior; the trap is asserting the error that _is_ the bug.)
    
    Run the resolved test command directly with Bash. **Paste the failing assertion plus enough surrounding output (5-10
    lines) to confirm the failure reason** — the assertion text or the missing symbol you expect, not an unrelated error. If
    the test passed on its first run, paste only the runner's summary line and stop to diagnose: the observed-failure gate
    has tripped.
    
    If the test passes on its first run, the observed-failure gate has tripped. Stop. Diagnose one of three causes: the test
    is not exercising the behavior; the behavior already exists; or — for a fix to broken behavior — the test is asserting
    the current broken behavior (the error the bug raises), which passes precisely because the bug is still present. If the
    behavior already exists, cross the item off and pick the next one. If the test is asserting the bug, **do not cross the
    item off** — rewrite it to assert the desired correct behavior, so it goes red until the fix lands. Do not write
    production code off an unobserved red.
    
    **With the red observed, check where green would land.** Name the files you would edit to make this test pass, before
    you edit any of them. If one sits outside the Step 1 boundary, the scope gate has tripped: do not edit it, and work the
    resolution ladder from Step 2 instead. A genuine red says the behavior is missing. It does not say this build owns
    producing it, and this is the only check that asks. Shared or cross-application code is where the gate matters most,
    because the blast radius of an edit there reaches consumers nobody in this build is testing.
    
    ### Green
    
    Write the minimum production code that makes this one test pass. Use the smallest gear that works: Obvious
    Implementation when you are certain, Fake It (return a constant, generalize later) when you are not, Triangulate (force
    the abstraction with a second example) only when you are really unsure. Gears are described in
    [references/tdd-loop.md](./references/tdd-loop.md).
    
    While going green, respect the coding standards and ADRs that govern _correctness and architectural placement_: where
    this code is allowed to live, which boundary or client it must go through, which contract it must honor. Violating an
    ADR boundary is not a sin you clean up later — it is the wrong code. Do **not** apply stylistic or structural polish
    here (naming sweeps, extraction, formatting passes). That is the refactor hat, and wearing it now violates "no more code
    than is sufficient to pass the test."
    
    Run the full test suite with Bash. **Paste the runner's summary line (pass and fail counts).** Paste full output only if
    a previously passing test broke or something unexpected appears. The gate to leave green is: the new test passes and
    every previously passing test still passes. If a prior test broke, you are not green — fix it before refactoring.
    
    ### Refactor (non-skippable)
    
    Only with every test green. Neglecting this step is the most common way to ruin TDD, so it is not optional: either you
    change something, or you state explicitly "no duplication, structure, or standards issue this cycle" and move on.
    
    Eliminate the duplication you just created. Bring the code into full conformance with the resolved coding standards and
    ADRs — this is the home for the stylistic and structural standards you deliberately skipped in green.
    
    Apply YAGNI per [../../references/yagni-rule.md](../../references/yagni-rule.md): remove duplication, do not add
    speculative abstraction. Defer speculative structure with the trigger that would reopen it; never add silently, never
    drop silently.
    
    Change no behavior. Re-run the full suite after the refactor. **Paste the runner's summary line** — paste full output
    only if something unexpected appears. The suite must stay green. If a refactor reddened a test, revert it — a refactor
    that changes behavior is a defect, not a refactor.
    
    ### Close the cycle
    
    Cross the completed item off the list. Append any scenarios you discovered while implementing (deferred, with their
    reopen trigger if speculative), but do not implement them now. If the open list has grown past roughly ten items, do not
    stop for input: flag it prominently as a scope warning, keep going, and record in the final summary that the work
    exceeded the recommended size and should be split next time. A runaway list is a scope signal, not a reason to pause for
    a human.
    
    **Running collaboratively.** When the request asks to review each behavior as it lands, which is what `pairing` does
    when it hands work here, stop at this point and hand control back instead of continuing. Present the stop in the shape
    [collaborative-stop-rule.md](../../references/collaborative-stop-rule.md) specifies. Absent such a request, continue as
    below; an ordinary invocation is unchanged.
    
    Return to the top of Step 3 with the next item. Continue until the list is empty.
    
    ## Step 4: Close the Outer Loop
    
    For any item that had an outer acceptance test (Step 2), run that test now. It should pass only because its inner
    behaviors are all implemented with real code (not mocks). If it is still red, the gap is a missing inner behavior: add
    the missing scenario to the test list and return to Step 3. The acceptance test going green is the signal the
    user-facing behavior is actually delivered.
    
    ## Step 5: Final Verification and Summary
    
    Run the full test suite, then the lint command, then the build command, using the resolved commands from Step 1. **Paste
    the summary line from each.** Paste full output only when one of them fails. If lint or build fails, that is in scope —
    fix it (a lint or build break is not a "pre-existing error" to wave off) and re-run.
    
    Summarize for the user:
    
    - Behaviors implemented, and the state of the test list (done, and any deferred items with their reopen triggers).
    - Any item the scope gate moved, with the rung that resolved it: the redesign that kept it, or the ticket write-up for
      the out-of-scope change this build declined to make.
    - Which coding standards and ADRs were applied, and where they shaped the code.
    - Any YAGNI deferrals from refactor, each with its reopen trigger.
    - A scope warning if the test list exceeded roughly ten open items, with a recommendation to split future work.
    - The final test, lint, and build status, with output shown, not asserted.
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related