Agent instructions, skills, SSH host aliases and expansion triggers are worth having identical on every machine one person owns, and belong in none of the shared configuration. They live in user/ now, with a manifest saying where each piece goes and a link-user stage that puts it there. That stage does nothing unless the machine said yes. Somebody who clones Panama to try the desktop keeps their own ~/.claude/CLAUDE.md exactly where it was; the question names the destinations and defaults to no. Anything displaced goes to config/old rather than being deleted. ~/.claude/CLAUDE.md and ~/.codex/AGENTS.md were byte-identical copies of one file, which is the drift this exists to prevent. Also adds the vitals toggles for the battery and Claude usage readouts, which had preferences and no way to reach them.
137 lines
6.9 KiB
Markdown
137 lines
6.9 KiB
Markdown
---
|
|
name: tdd
|
|
description: Use when implementing any feature or bugfix, before writing implementation code, and when deciding what to test, where a seam goes, or whether a mock is warranted.
|
|
---
|
|
|
|
# Test-Driven Development
|
|
|
|
Write the test first. Watch it fail. Write the minimal code that passes it.
|
|
|
|
**If you did not watch the test fail, you do not know that it tests the right thing.** A test written
|
|
after the code passes immediately, which proves nothing: it may be testing the implementation, or the
|
|
wrong behaviour, or nothing at all.
|
|
|
|
**Violating the letter of the rule is violating its spirit.**
|
|
|
|
## The Iron Law
|
|
|
|
```
|
|
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
|
|
```
|
|
|
|
Wrote the code first? Delete it and start over. Not kept as reference, not adapted while you write
|
|
the test, not consulted. Delete means delete, and implement fresh from the test.
|
|
|
|
Exceptions worth asking about: throwaway prototypes, generated code, config files. Thinking "skip it
|
|
just this once"? That thought is the rationalization, not the exception.
|
|
|
|
## Agree the seams before writing anything
|
|
|
|
A **seam** is the public boundary you test at: the interface where behaviour is observable without
|
|
reaching inside. Tests live at seams, never against internals.
|
|
|
|
**Test only at pre-agreed seams.** Before writing any test, name the seams under test and confirm
|
|
them:
|
|
|
|
> "The public interface here is `X`. I plan to test at `X` and `Y`, and not at `Z` because it is
|
|
> internal. Does that match what you expect?"
|
|
|
|
No test gets written at an unconfirmed seam. This is the whole mechanism that keeps a suite focused:
|
|
you cannot test everything, so agreeing the seams up front is what puts the effort on the critical
|
|
paths and the complex logic instead of spreading it thin across every edge case.
|
|
|
|
When the shape of that interface is itself the open question, how deep the module should be, where
|
|
the seam belongs, what the interface exposes, call the Skill tool with "codebase-design" and use that
|
|
vocabulary. It is a reference to consult, not a session to run.
|
|
|
|
## The loop
|
|
|
|
Run this once per behaviour. One seam, one test, one minimal implementation per cycle.
|
|
|
|
1. **RED: write one failing test.** One behaviour, a name that describes it, real code rather than
|
|
mocks. See [TESTS.md](TESTS.md) for what a good one looks like.
|
|
2. **Verify RED. Mandatory, never skipped.** Run the project's test command for that single file.
|
|
Confirm it *fails* rather than errors, that the message is the one you expected, and that it fails
|
|
because the behaviour is missing rather than because of a typo. A test that passes immediately is
|
|
testing behaviour that already exists: fix the test. A test that errors is broken: fix it and
|
|
re-run until it fails correctly.
|
|
3. **GREEN: write the simplest code that passes.** Nothing speculative, no options objects for
|
|
futures nobody asked for, no improving adjacent code.
|
|
4. **Verify GREEN. Mandatory.** The test passes, the other tests still pass, and the output is
|
|
pristine with no stray errors or warnings. Test still failing? Fix the code, never the test.
|
|
5. **Tidy, then stop.** Remove duplication you just introduced and improve names, keeping every test
|
|
green. **Design refactoring is not part of this loop**: reshaping a module, moving a seam, or
|
|
extracting an abstraction belongs to review, not to the red-green cycle. Confusing the two is how
|
|
a cycle turns into an afternoon.
|
|
6. **Repeat** with the next failing test.
|
|
|
|
Look up the test command rather than assuming one: `package.json` scripts are the source of truth,
|
|
and these repos are usually `bun test <path>` or `bunx vitest run <path>`.
|
|
|
|
## Work in vertical slices
|
|
|
|
**Horizontal slicing is the anti-pattern**: writing all the tests first, then all the implementation.
|
|
Bulk tests verify *imagined* behaviour. You end up testing the shape of things rather than what a
|
|
caller experiences, the tests go insensitive to real changes, and you commit to a test structure
|
|
before you understand the implementation.
|
|
|
|
Work vertically instead: one test, one implementation, repeat. Each test is a **tracer bullet** that
|
|
responds to what the last cycle taught you.
|
|
|
|
## Mocking
|
|
|
|
Mock at system boundaries only, never your own modules. See [MOCKING.md](MOCKING.md) for the boundary
|
|
list and how to design an interface that is easy to substitute at.
|
|
|
|
## Rationalizations
|
|
|
|
| Excuse | Reality |
|
|
|---|---|
|
|
| "Too simple to test" | Simple code breaks. The test takes 30 seconds. |
|
|
| "I'll test after" | Tests written after pass immediately, which proves nothing. You never watched it fail, so you never proved it can catch anything. |
|
|
| "Tests after achieve the same thing, spirit not ritual" | Tests-after answer "what does this do?"; tests-first answer "what should this do?" Writing them after biases you toward the cases you already remembered. |
|
|
| "I already tested it manually" | Manual testing leaves no record, cannot be re-run, and is the first thing dropped under pressure. |
|
|
| "Deleting hours of work is wasteful" | Sunk cost. That time is spent either way. Keeping code you cannot trust is the waste. |
|
|
| "I'll keep it as reference and write tests first" | You will adapt it, which is testing after. Delete means delete. |
|
|
| "I need to explore first" | Fine. Throw the exploration away and start with TDD. |
|
|
| "This is hard to test" | Listen to the test. Hard to test means hard to use: the design is talking to you. |
|
|
| "TDD will slow me down" | The shortcut means debugging in production, which is slower. |
|
|
| "The existing code has no tests" | You are improving it. Add them. |
|
|
|
|
## Red flags: stop and start over
|
|
|
|
- Code written before the test
|
|
- The test passed the first time you ran it
|
|
- You cannot explain why the test failed
|
|
- "I'll add tests later"
|
|
- "It's about the spirit, not the ritual"
|
|
- "Keep it as reference"
|
|
- "This one is different because..."
|
|
|
|
All of these mean the same thing: delete the code, start again with the test.
|
|
|
|
## When stuck
|
|
|
|
| Problem | What it means |
|
|
|---|---|
|
|
| Do not know how to test it | Write the API you wish existed, then the assertion. Ask if still stuck. |
|
|
| The test is too complicated | The design is too complicated. Simplify the interface. |
|
|
| You have to mock everything | The code is too coupled. Inject dependencies instead. |
|
|
| The setup is enormous | Extract helpers. Still enormous? The design needs work. |
|
|
|
|
## Fixing a bug
|
|
|
|
Never fix a bug without a test. Write the failing test that reproduces it, watch it fail, then fix.
|
|
The test proves the fix and prevents the regression. Where the bug is hard to reproduce at all, the
|
|
loop-building discipline is its own skill: call the Skill tool with "diagnosing-bugs" first, then come
|
|
back here once you have a red-capable repro.
|
|
|
|
## Done when
|
|
|
|
- Every new behaviour has a test that was watched failing, for the expected reason, before the code
|
|
existed.
|
|
- Every test sits at a seam that was agreed before it was written.
|
|
- The full suite passes and the output is pristine.
|
|
- No test asserts on a mock of something you own, and no expected value was computed the way the
|
|
code computes it.
|