add-seam
Put an integration that will change at scale behind one interface (a new provider, store or transport), so a later swap is a new implementation and not new callers. Use when adding a third-party integ
SKILL.md.claude/skills/add-seam/SKILL.md
GitHubAdd a seam
The README's "Scaling path" lists every seam: what's there now, what it becomes, where the interface is, and the skill that swaps it. A seam is worth it only for something that will change at scale; everything else is plain code.
- The interface, in the one place every caller reaches it: a shared concern in
packages/nest-common/src/<concern>.ts(likestorage.ts,crypto.ts), one service's in that service (likeapps/notifications/src/channels/sms/sms-transport.ts), Python inapps/ai/app/. The smallest set of methods the callers need, in the repo's terms, not the provider's. - The first implementation in its own file, the provider's SDK imported only there. Chosen from env in one factory (the module that provides it), so callers never name the provider.
- Its header comment says what the interface is for, what implements it today, and how to swap it: the files to add, the env to set, the tests to extend. The existing seams' headers are the model.
- A local stand-in that speaks the real protocol, for tests and local development
(Mailpit,
packages/fake-stripe, the fake push server), refused in production at boot. - Tests against the interface: a contract test the next implementation reuses, and integration tests through the stand-in.
- Docs. A row in the README's "Scaling path" table, and a
.claude/skills/swap-<concern>/SKILL.mdlike the others (interface, today's implementation, how to add one, what to test,disable-model-invocation: true), listed indocs/README.md's runbooks. - Boundaries. If the provider's SDK must stay inside the adapter, a
dependency-cruiser rule in
.dependency-cruiser.cjsenforces it.
Done when
bun run lintandbun run check-typespass, and the tests pass against the stand-in (bun run test:integration).- Nothing outside the adapter imports the provider (
Grepfor its package name). - The
revieweragent reports nothing blocking.