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.
60 lines
2.2 KiB
Markdown
60 lines
2.2 KiB
Markdown
# 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.
|