| name | code-architecture |
| description | Apply when writing, structuring, or reviewing application code — covers SOLID, YAGNI, DRY, composition over inheritance, Gang of Four patterns (used sparingly), Command/Query Separation, the banned Repository pattern, /lib application logic, and /tests BDD coverage. Load whenever designing classes, services, modules, or deciding where code lives. |
🏛️ Code Architecture
The hard rules live in AGENTS.md, CLAUDE.md, and SPEC.md. This skill adds the why and concrete examples.
⚖️ Principles (in priority order)
- YAGNI first. Don't build abstraction for a future that may never come. The simplest thing that satisfies the user story wins. Add structure when a second real use appears, not before.
- SOLID — but in service of clarity, not ceremony:
- S — a class has one reason to change. An
OrderService orders; it does not also format currency or send email.
- O — extend via new types/strategies, not by editing stable code.
- L — subtypes must honor the base contract. If
IPaintable.paint() throws "not supported", the hierarchy is wrong.
- I — interfaces describe abilities, not implementations. A
Car implements IDriveable, IPaintable, IVehicle — small, capability-named interfaces, not one fat ICar.
- D — depend on abstractions. Inject collaborators; wire them at a Composite Root, never with
new deep in business logic, and never a Singleton.
- DRY, but not prematurely. Two similar lines aren't duplication; two lines that must change together are. Don't fold accidental similarity into a shared helper.
- Composition over inheritance. Reach for inheritance only for genuine is-a + shared contract. Otherwise compose behaviors.
- Command/Query Separation. A method either changes state (command, returns void/ack) or answers a question (query, no side effects) — never both.
getNextId() that increments is a trap.
🧩 Gang of Four — sparingly
Use a pattern when it names a problem you actually have: Strategy for swappable algorithms, Factory for construction logic, Adapter at boundaries, Decorator for layered behavior, Composite for trees. Never Singleton — inject instead. If you can't name the problem the pattern solves here, you don't need it yet.
🚫 No Repository pattern — ever
Rob loathes it. Do not introduce a *Repository class, interface, or folder. Data access goes through Drizzle directly inside the domain's service layer (see the data-access skill). A service method talks to the DB; there is no repository seam in between.
📁 Folder structure
All app logic lives in /lib, split by domain entity. Tests live in /tests and mirror the domain/behavior under test:
lib/
orders/
orders.service.ts # logic — talks to Drizzle directly
orders.model.ts # types, domain model, toString()/toJSON()
tests/
orders/
orders.test.ts # bun:test BDD specs (see testing-standards)
Keep a domain self-contained. Cross-domain orchestration happens at the Composite Root, not by one service reaching into another's internals.
🧱 Class conventions
Every class gets:
toString() — a human-readable representation (e.g. Order #1042 — 3 items, $59.97).
toJSON() — explicit serialization, not accidental field leakage.
Interfaces named for abilities (IDriveable), injected via constructor, composed at the root.