Keep the personal half of the desktop in one place, and ask before installing it
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.
This commit is contained in:
@@ -0,0 +1,59 @@
|
||||
# When to mock
|
||||
|
||||
Mock at **system boundaries** only:
|
||||
|
||||
- External APIs you do not control (Jira, Stripe, email, Infisical)
|
||||
- Time and randomness
|
||||
- Databases, sometimes, though a real local test instance is better where one exists
|
||||
- The filesystem, sometimes
|
||||
|
||||
Do **not** mock your own modules, internal collaborators, or anything you control. A test that mocks
|
||||
what you own is asserting on your own wiring rather than on behaviour, and it will break on the first
|
||||
refactor that changes nothing a caller can see.
|
||||
|
||||
Before mocking any dependency, understand what it actually does. A mock built on a guess about a
|
||||
dependency's side effects encodes the guess into the suite.
|
||||
|
||||
## Designing for substitutability
|
||||
|
||||
### Accept dependencies, do not construct them
|
||||
|
||||
```typescript
|
||||
// Easy to substitute at the seam
|
||||
function processPayment(order: Order, paymentClient: PaymentClient) {
|
||||
return paymentClient.charge(order.total);
|
||||
}
|
||||
|
||||
// Welded in: nothing can stand in for the client
|
||||
function processPayment(order: Order) {
|
||||
const client = new StripeClient(process.env.STRIPE_KEY);
|
||||
return client.charge(order.total);
|
||||
}
|
||||
```
|
||||
|
||||
### Prefer an SDK-shaped interface over a generic fetcher
|
||||
|
||||
One function per external operation, rather than a single generic call with conditional logic:
|
||||
|
||||
```typescript
|
||||
// GOOD: each operation is independently substitutable
|
||||
const api = {
|
||||
getUser: (id: string) => fetch(`/users/${id}`),
|
||||
getOrders: (userId: string) => fetch(`/users/${userId}/orders`),
|
||||
createOrder: (data: NewOrder) => fetch("/orders", { method: "POST", body: data }),
|
||||
};
|
||||
|
||||
// BAD: the stand-in needs conditional logic to know what it is being asked for
|
||||
const api = {
|
||||
fetch: (endpoint: string, options: RequestInit) => fetch(endpoint, options),
|
||||
};
|
||||
```
|
||||
|
||||
The SDK shape means each stand-in returns one specific type, no branching in test setup, an obvious
|
||||
read of which endpoints a test exercises, and type safety per operation.
|
||||
|
||||
## One adapter is not a seam
|
||||
|
||||
Introducing a port because a single test wants to substitute at it is indirection, not design. One
|
||||
adapter means a hypothetical seam; two means a real one, and production-plus-test counts as two. Call
|
||||
the Skill tool with "codebase-design" when the seam placement itself is in question.
|
||||
@@ -0,0 +1,136 @@
|
||||
---
|
||||
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.
|
||||
@@ -0,0 +1,97 @@
|
||||
# What a good test is
|
||||
|
||||
A test verifies behaviour through a public interface. The code underneath can change entirely; the
|
||||
test should not. A good one reads like a specification: `user can checkout with valid cart` tells you
|
||||
exactly what capability exists, and survives refactors because it does not care about internal
|
||||
structure.
|
||||
|
||||
## Good
|
||||
|
||||
```typescript
|
||||
test("user can checkout with valid cart", async () => {
|
||||
const cart = createCart();
|
||||
cart.add(product);
|
||||
|
||||
const result = await checkout(cart, paymentMethod);
|
||||
|
||||
expect(result.status).toBe("confirmed");
|
||||
});
|
||||
```
|
||||
|
||||
- Tests what a caller cares about
|
||||
- Uses the public interface only
|
||||
- Survives internal refactors
|
||||
- Describes *what*, not *how*
|
||||
- One logical assertion
|
||||
|
||||
## The three anti-patterns
|
||||
|
||||
### Implementation-coupled
|
||||
|
||||
Mocks internal collaborators, reaches for private methods, or asserts on call counts and ordering.
|
||||
**The tell: the test breaks when you refactor, even though the behaviour did not change.**
|
||||
|
||||
```typescript
|
||||
// BAD: asserts on the mock, not the behaviour
|
||||
test("checkout calls paymentService.process", async () => {
|
||||
const mockPayment = vi.mock(paymentService);
|
||||
await checkout(cart, payment);
|
||||
expect(mockPayment.process).toHaveBeenCalledWith(cart.total);
|
||||
});
|
||||
```
|
||||
|
||||
A second form is verifying through a side channel instead of the interface:
|
||||
|
||||
```typescript
|
||||
// BAD: bypasses the interface to check the database directly
|
||||
test("createUser saves to database", async () => {
|
||||
await createUser({ name: "Alice" });
|
||||
const row = await db.query("SELECT * FROM users WHERE name = ?", ["Alice"]);
|
||||
expect(row).toBeDefined();
|
||||
});
|
||||
|
||||
// GOOD: verifies through the interface a caller would use
|
||||
test("createUser makes the user retrievable", async () => {
|
||||
const user = await createUser({ name: "Alice" });
|
||||
const retrieved = await getUser(user.id);
|
||||
expect(retrieved.name).toBe("Alice");
|
||||
});
|
||||
```
|
||||
|
||||
### Tautological
|
||||
|
||||
The expected value is recomputed the way the code computes it, so the test passes by construction and
|
||||
can never disagree with the implementation.
|
||||
|
||||
```typescript
|
||||
// BAD: the assertion reimplements the function
|
||||
test("calculateTotal sums line items", () => {
|
||||
const items = [{ price: 10 }, { price: 5 }];
|
||||
const expected = items.reduce((sum, i) => sum + i.price, 0);
|
||||
expect(calculateTotal(items)).toBe(expected);
|
||||
});
|
||||
|
||||
// GOOD: an independent, known literal
|
||||
test("calculateTotal sums line items", () => {
|
||||
expect(calculateTotal([{ price: 10 }, { price: 5 }])).toBe(15);
|
||||
});
|
||||
```
|
||||
|
||||
Expected values come from an independent source of truth: a known-good literal, a worked example, or
|
||||
the spec. Never from the code.
|
||||
|
||||
### Horizontal slicing
|
||||
|
||||
Writing all the tests first, then all the implementation. Covered in `SKILL.md`; the fix is vertical
|
||||
slices, one test and one implementation at a time.
|
||||
|
||||
## Before writing any test
|
||||
|
||||
Name the production change that would make this test fail. If you cannot name one, the test asserts
|
||||
nothing and should not be written.
|
||||
|
||||
## Keep test-only code out of production
|
||||
|
||||
A method that exists so a test can reach inside is a hole in the interface. Put helpers in test
|
||||
utilities. If the test cannot reach what it needs through the interface, that is a design finding,
|
||||
not a reason to widen the interface.
|
||||
@@ -0,0 +1,3 @@
|
||||
interface:
|
||||
display_name: TDD
|
||||
short_description: Red-green loop, pre-agreed seams, and what makes a test worth keeping.
|
||||
Reference in New Issue
Block a user