agentleFS
svyatovsvyatov/supermatt/skills/tdd/SKILL.md

tdd

Skill20 starsChanged 51 days agoMITView on GitHub

Test-driven development. Use when the user wants to build features or fix bugs test-first, mentions "red-green-refactor", or wants integration tests.

194 other projects use a version of this, 66 of them edited.

Adopted by

Most recently taken up by

Used with

Projects with it also use capture-learning-tools, design-frontend-dev and 10 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • The-Vibe-CompanySkill

    capture-learning-tools

    Turn technical conversations, bugs, CI failures, review comments,

    Also in 5 projects that use this

  • The-Vibe-CompanySkill

    design-frontend-dev

    Clean Impeccable frontend design skill. Use when the user wants to

    Also in 5 projects that use this

  • tristanphvnSkill

    ketchup-video

    Catch the user up like /keel:ketchup, with a short silent video of slides for what happened and the waiting calls as text under it. Use for /keel:ketchup-video.

    Also in 14 projects that use this

  • jaydubya818Skill

    figure-it-out

    Use when novel or multi-phase work needs an auditable workflow and no narrower playbook fits.

    Also in 54 projects that use this

  • jaydubya818Skill

    principle-laziness-protocol

    Apply when a refactor is adding wrappers or abstractions instead of simplifying.

    Also in 45 projects that use this

  • +2 more from The-Vibe-Company
  • +5 more from jaydubya818

The file

We split this skill into the rules it gives an agent

4 rules. 0 are common, 3 are distinctive, and 1 is very specific to this project.

  • 0

    Common

    Most of the 186 edited versions we read keep these.

  • 3

    Distinctive

    This author's own choices. Worth a look.

  • 1

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    When exploring the codebase, read the glossary (docs/agents/domain.md gives the layout.

    line 11
  2. Distinctive:

    Before you write a test, name the break it catches: the production change that turns it red.

    line 19
  3. Distinctive:

    Read tests.md for good and bad test examples before writing the first test, and read mocking.md before adding any mock or test double.

    line 21
  4. Distinctive:

    Ask: "What's the public interface, and which seams should we test?"

    line 29

“Common” is measured: kept by at least 6 in 10 of the 186 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Test-Driven Development

TDD is the red → green loop. This skill is the reference that makes that loop produce tests worth keeping: what a good test is, where tests go, the anti-patterns, and the rules of the loop. Every section applies on every cycle: consult them before and during the loop, not after.

When exploring the codebase, read the glossary (docs/agents/domain.md gives the layout; by default GLOSSARY.md at the root, if it exists) so test names and every identifier the change introduces match the project's domain language, and respect ADRs in the area you're touching.

For a bug whose cause is not yet known, load and follow the diagnosing-bugs skill through the host's skill mechanism first: it finds the cause and brings the repro back here as the failing test.

What a good test is

Tests verify behavior through public interfaces, not implementation details. Code can change entirely; tests shouldn't. A good test reads like a specification: "user can checkout with valid cart" tells you exactly what capability exists, and it survives refactors because it doesn't care about internal structure.

Before you write a test, name the break it catches: the production change that turns it red. If you cannot name one, the test observes no behavior yet; find the behavior first.

Read tests.md for good and bad test examples before writing the first test, and read mocking.md before adding any mock or test double. Before any behaviour-preserving change, read characterization.md: it pins code that has no tests, and checks that existing tests cover each change.

Seams: where tests go

A seam is where a module's interface lives (the codebase-design term): tests cross it the way callers do and observe behavior without reaching inside. Tests live at seams, never against internals.

Test only at pre-agreed seams. Before writing any test, write down the seams under test and confirm them with the user. Seams named in the spec or issue you are working from are already confirmed. No test is written at an unconfirmed seam. You can't test everything, so agreeing the seams up front is how testing effort lands on the critical paths and complex logic instead of every edge case. The seams bound the code as well as the tests: when the change needs code behind a seam not on the list (a TUI line when only the Session API is confirmed), confirm that seam before writing the code, so every line the run adds has its red case at a confirmed seam.

Ask: "What's the public interface, and which seams should we test?"

When the shape of that interface is itself in question (how deep the module is, where the seam belongs, what the interface should expose), load and follow the codebase-design skill through the host's skill mechanism for the vocabulary. It is the shared source of the module, interface, depth, seam, adapter, leverage and locality terms, and it is a reference to consult, not a session to run.

Anti-patterns

  • Implementation-coupled: mocks internal collaborators, tests private methods, or verifies through a side channel (querying the database instead of using the interface). The tell: the test breaks when you refactor but behavior hasn't changed.
  • Tautological: the assertion recomputes the expected value the way the code does (expect(add(a, b)).toBe(a + b), a snapshot derived by hand the same way, a constant asserted equal to itself), so it passes by construction and can never disagree with the code. Expected values must come from an independent source of truth: a known-good literal, a worked example, the spec.
  • Change detector: the test goes red only on a deliberate decision (a constant's value, the exact wording of a message, the source text of a script or doc) and stays green through real bugs. Assert the behavior that depends on the decision: "a failing call is tried 5 times, then gives up" in place of expect(MAX_RETRIES).toBe(5).
  • Horizontal slicing: writing all tests first, then all implementation. Bulk tests verify imagined behavior: you test the shape of things rather than user-facing behavior, the tests go insensitive to real changes, and you commit to test structure before understanding the implementation. Work in vertical slices instead: one test → one implementation → repeat, each test a tracer bullet that responds to what the last cycle taught you.

Rules of the loop

  • Red before green. Write the test first and run it. It is red when the assertion fails for the break you named. An error, a typo, a failed import, a build failure, or a panic is not red yet: fix it and run again. When the test calls a name that does not exist yet, fix it with a stub: add the name so it compiles and returns the zero value (an empty field, a function returning nil or an empty list), then run again until the assertion fails. A test that passes at once pins behavior that already exists. If the code predates this run, rewrite the test to name a break that is not there yet, unless the spec asks for this case over existing behavior: then it is a characterization test, and it counts once you break the code it covers (characterization.md, "When you have enough") and watch it go red. If this run wrote the code ahead of the test, the test counts only once you break that code (the mutation check below) and watch it go red: do that before counting the cycle, then undo the break. Then write only enough code to pass it: every branch you add is one the failing case runs. A behavior the case does not reach (another input form, another case of the same rule, a later slice's rule) waits for its own red test, even when the natural implementation would cover it in the same edit. Don't anticipate future tests or add speculative features.
  • Green is the affected seam. During a cycle, run the new test and the tests for the affected seam, plus lint on changed files and the narrowest supported typecheck. Use the full command when the runner cannot select that scope or shared configuration makes the effect project-wide. Before a completed behavior is committed or handed off, run the repository's full required gate, including its full suite. A cycle is not a commit boundary: several cycles can complete one behavior. Reuse a passing check only while its relevant files, dependencies, configuration, and environment are unchanged; a later phase or commit alone does not invalidate it. Repository rules and hooks still apply. Report every failure by name, including failures you did not cause.
  • One slice at a time. One seam, one case, one minimal implementation per cycle. A case is one input and the assertions on it, whatever file holds it: in a table-driven test or a script of many cases, add one case per cycle and watch that case go red, since a runner that stops at its first failure shows only the first new case red and hides the rest.
  • Mutate before you finish. Break every file this run changed in small, realistic ways, one at a time: a wrong constant or argument, the wrong branch, a missing side effect, an empty return, a missing check for empty, zero, null, or bad input. Run the tests for that seam, then undo the change. Each mutation must turn at least one test red as the red rule above defines it, an assertion failing for that break: a mutation that only panics or fails to compile proves nothing, so pick another. Change the logic and leave every import and variable in use (false && cond in place of a deleted cond, x += 0 in place of x = x): a compiler that rejects unused names, as Go's does, or a vet step that rejects self-assignment, reports the mutation broken. One that stays green marks behavior no test protects: add the test that turns it red, or, when no input reaches the mutated code or the mutation changes no observable result (an equivalent mutation), delete that code, since the red rule admits no branch a failing case does not run. An equivalent mutation of code that has to stay (a == that no input tells apart from >=, a value the output never shows) is a survivor: write down the input you tried to tell the two apart and why none can, and list it by name in your report. Rewriting the code so the mutation no longer applies is a behaviour change: it needs its own red case, and the rewritten code is mutated again. The check is done when every mutation is red or is a listed survivor. The mutation runner requires a POSIX shell, Perl, and standard Unix utilities (cat, cp, date, head, mktemp, and rm). Run them with sh <this skill's directory>/scripts/mutate.sh [-b BUILD] [-t SECONDS] TEST MUTATION... (scripts/mutate.sh, bundled with this skill). Each MUTATION is a file: the target path on line 1, then the exact text to find, a line holding only ====, then the replacement. A missing separator is rejected as missing; bytes outside the replacement, including the final newline, are preserved. Write them into a mktemp -d directory, one heredoc per file. Call it through sh, because an installed copy can lose its execute bit. A batch runs BUILD and TEST once per mutation, so a batch of dozens can outlast your shell tool's time limit: run a large batch in the background and read its output when it exits; until then leave the working tree alone, since the batch rewrites each target in place and restores it from a copy, so an edit made meanwhile is lost or skews the results. A mutation that removes a loop or recursion guard can run TEST forever. An explicit -t SECONDS also bounds the unchanged baseline; -t 0 disables TEST timeouts. Without an explicit limit, mutation tests get five times the unchanged run plus a minute. A timeout kills TEST and processes still in its process group. Test commands that detach descendants into another group or session must clean them up themselves. Count a timeout on such a guard as red, since never ending is the break it names. It applies each one alone, restores the file, and reports it test-failed, GREEN, timeout (TEST ran past the limit), broken (the -b build step failed) or missing (its text was not found). A failed backup stops before that mutation. A failed restore exits 2 and retains the backup at the path printed on stderr; recover it before continuing. Failed TEST output is printed on stderr with the mutation name. Inspect that output before counting test-failed as red: identify the assertion and confirm it failed for the intended break. Missing diagnostics require rerunning with a test command that exposes them. A runtime error or failed import is not red. Exit 0 means every TEST failed; it does not certify the failure causes. It first runs BUILD and TEST on the unchanged code and stops with exit 2, printing the failing command's output, when either fails, since every mutation would then read red: fix TEST until it passes there (TEST runs through sh, so quote a | in a -run A|B pattern).
  • Refactoring is not part of the loop. It comes after the slice is green, usually on what review found (the code-review skill), with the tests left unchanged apart from a mechanical rename or move. A refactor runs no red-green cycle: the seam's existing tests are its characterization tests. Each step runs the affected checks and characterization.md's break check; the completed refactor runs the full required gate before commit or handoff.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.