| name | codebase-design |
| description | Design and evaluate deep modules: cohesive responsibility behind a small, stable caller-facing contract, with information hiding, justified seams, explicit effects, dependency strategy, and behavior-focused tests. Use when designing or changing an in-process module or package contract, consolidating shallow pass-through modules, deciding what to hide, comparing alternative interfaces, or asking whether code should be combined or split for leverage and locality. For physical layout use structure-codebase; for public compatibility use api-design; for a repository-wide scan use improve-codebase-architecture. For an already-selected whole-path requirement to support a calibrated net-mechanism-reduction claim, use reduce-system-complexity. |
Codebase Design
Design coherent deep modules: substantial, related behavior and design decisions hidden behind a small, stable caller-facing contract. Optimize for leverage for callers and locality for maintainers without creating a god module.
Use this skill for logical responsibility and contract shape. Use structure-codebase for physical paths, packages, exports, dependency direction, enforcement, and folder migration. Use reduce-system-complexity when the selected objective is an evidence-backed net-reduction claim over whole-path mechanism rather than a deeper contract. Use evaluate-existing-solutions for a consequential unresolved library, tool, application, service, framework, or platform choice.
When improve-codebase-architecture loads this skill during an unselected audit, use only its vocabulary, principles, evidence tests, and thin-edge safeguards. Do not run the contract-design workflow or propose an exact interface until the user selects a candidate.
Read the relevant reference before proposing a consequential design:
Vocabulary
| Term | Meaning |
|---|
| Module | A cohesive unit with an implementation and one or more role-shaped caller contracts: a function, object, package, or capability. Scale alone does not make it a module. |
| Interface / public contract | Everything a caller must know to use the module correctly: operations, types, invariants, errors, ordering, configuration, lifecycle, effects, and relevant performance characteristics. This is broader than a TypeScript interface or a type signature. |
| Implementation | The decisions and behavior hidden behind the caller-facing contract. Private functions may be small and numerous without becoming public modules. |
| Depth | How much coherent capability and decision-making a caller gains for the contract burden it must learn. Do not measure depth by lines of code. |
| Leverage | The caller benefit of depth: one learned contract applies useful behavior consistently across many scenarios. |
| Locality | The maintainer benefit of depth: related knowledge, changes, bugs, and verification concentrate in one owner. |
| Seam | Per Michael Feathers, a place where behavior can be changed without editing at that place; every seam has an enabling point. Not every module contract is a seam. |
| Adapter | A concrete translator or implementation selected at a seam. In hexagonal architecture, retain that skill's driving, driven, and test-interactor distinctions. |
Use these terms to disambiguate, not to erase useful established vocabulary. API, component, service, signature, boundary, port, and bounded context remain valid when they name those specific concepts.
Design Principles
Hide decisions, not merely code
Place a responsibility behind a module when callers should not each know its policy, sequencing, representation, error recovery, or provider mechanics. A private helper extraction does not deepen a module if the same knowledge still leaks through its parameters and call order.
Keep depth cohesive
A tiny contract over an incoherent implementation is a god module, not a good deep module. Combine behavior only when it shares meaning, invariants, ownership, lifecycle, or a real axis of change. Preserve separate modules when they evolve, fail, deploy, or authorize independently.
Apply the behavior-preserving inlining test
Imagine inlining the module into every caller while preserving behavior:
- If policy, sequencing, error handling, or representation knowledge spreads across callers, the module earns its place.
- If callers become simpler because a pass-through disappears and no knowledge is duplicated, the module is shallow or misplaced.
This is the useful form of the deletion test. Do not imagine deleting the behavior itself.
Pull complexity downward deliberately
Make the common call simple. Accept complexity inside the implementation when doing so removes configuration, ordering, special cases, or provider knowledge from callers. Keep effects, failure modes, resource ownership, and performance costs explicit enough that callers can use the module safely.
Justify seams with evidence
Create a seam for a concrete need: substitution, independent testing, volatility isolation, ownership, trust, runtime failure, or deployment. Adapter count is evidence, not a rule. A production adapter plus a faithful test interactor may justify a seam; two accidental wrappers do not.
Test at the stable contract
Make caller-observable behavior the primary test surface. Do not export private helpers or expose internal seams solely to test them. A private subsystem may have focused tests when it is itself a coherent module or when an algorithm needs precise failure localization; those tests must not freeze incidental orchestration.
Preserve intentionally thin edges
Do not diagnose a route leaf, CLI command, adapter, generated client, or composition root as shallow merely because it is thin. Translation and wiring should often be thin. Judge whether policy is hidden in the correct inside module and whether the edge leaks provider or transport knowledge across its contract.
Workflow
1. Define the job and callers
State the behavior the module owns, its current and expected callers, the common case, and what must remain outside. Read project instructions, architecture decisions, glossary conventions, and relevant tests before naming anything new.
2. Inventory the full contract burden
List what each caller must know today:
- operations and data shapes;
- invariants and preconditions;
- call ordering and lifecycle;
- configuration and dependency construction;
- errors, retries, partial failure, and effects;
- latency, throughput, consistency, and transaction expectations.
Do not confuse a short type signature with a small interface.
3. Map hidden and leaked knowledge
Trace callers and collaborators. Identify duplicated decisions, pass-through chains, provider types, repeated orchestration, co-changing files, and tests that must reconstruct internals. Record counterevidence: independent ownership, different failure domains, or callers that genuinely need separate policy.
4. Choose responsibility before the seam
Write one sentence defining the module's coherent responsibility. Decide what knowledge belongs behind it. Only then place seams and select dependency strategies. Read references/deepening.md for existing clusters.
When a material generic dependency or subsystem is not already prescribed, feed this responsibility, caller scenarios, effects, and constraints into evaluate-existing-solutions before finalizing a dependency-shaped contract. Keep the chosen provider or library behind local application language when doing so preserves a useful change boundary; do not wrap every stable primitive by reflex or copy a vendor API into the module contract.
5. Design the contract from use scenarios
Design from representative caller examples, including invalid input, partial failure, cancellation, retries, and lifecycle where relevant. Specify types, invariants, ordering, errors, effects, and performance expectations—not methods alone.
For consequential choices, read references/design-it-twice.md and compare genuinely different designs before recommending one.
6. Test the depth claim
Ask:
- Does the common caller learn less and coordinate less?
- Did implementation knowledge move behind the contract, or merely change names?
- Would a policy change concentrate here rather than fan out?
- Are effects and failures still honest?
- Can behavior be tested through the contract with faithful dependencies?
- Did the design avoid speculative flexibility and a generic command-shaped god interface?
7. Route delivery to the owning skills
- Use
api-design for public HTTP and contracts published or versioned across
an ownership boundary. Ordinary in-process component props remain with this
skill and the applicable framework/design-system guidance.
- Use
structure-codebase for file/package placement and mechanical dependency enforcement.
- Use
render-code-shape first when the current composition is not yet visible: it returns cited boundaries, signatures, and a call graph, which is the evidence this skill's judgements need.
- Use
reduce-system-complexity when the accepted outcome must remove total branches, states, dependencies, layers, or operational moving parts rather than only improve caller leverage.
- Use
evaluate-existing-solutions when a material generic implementation choice remains unresolved after the responsibility and constraints are known.
- Use
hexagonal-architecture only for an opted-in ports-and-adapters system with purposeful actor conversations.
- Use
finding-seams when existing hard-coded dependencies block a test harness.
- Use
characterisation-tests before restructuring untested behavior.
- Use
tdd, testing, and refactoring during implementation according to whether behavior changes and whether the safety net is trustworthy. At PR readiness, follow the repository's mutation policy; where mutation is not meaningful, record proportionate alternate evidence.
- Use
ubiquitous-language when a domain term must be proposed or changed; never coin it silently.
Design Output
Produce:
- The module's one-sentence responsibility and explicit exclusions.
- Callers and representative usage scenarios.
- The complete proposed contract, including non-type obligations.
- Knowledge and behavior hidden behind it.
- Dependencies, seams, adapters or test interactors, and their fidelity strategy.
- Alternatives considered and why the recommendation wins.
- Compatibility and incremental migration constraints.
- Behavior tests that should survive implementation changes.
- Assumptions, risks, and evidence still needed.
Completion Check
- Is the contract simpler than the coherent behavior it provides?
- Does it hide decisions rather than expose orchestration knobs?
- Is the common case obvious without making uncommon cases impossible?
- Are cohesion, ownership, failure, and runtime boundaries still honest?
- Are seams justified by real variation or isolation needs?
- Can callers and behavior tests use the same stable contract?
- Are thin edges still thin and policy-free?
- Does every compatibility or migration claim have a verification path?
Attribution
Adapted from Matt Pocock's MIT-licensed codebase-design skill and linked resources, with deep-module and Design It Twice concepts credited to John Ousterhout and seam terminology credited to Michael Feathers. See references/source-notes.md and LICENSE for pinned provenance and license terms.