improve-codebase-architecture and codebase-design: deep modules so your code isn't a ball of mud
Agents speed up coding, and with it software entropy: AI-built projects become complex and hard to change at an unprecedented rate. That's the fourth failure the mattpocock/skills repository goes after, and its author's answer is "a radical new approach": caring about the design of the code. codebase-design supplies the vocabulary and principles; improve-codebase-architecture walks your code looking for where to apply them.
This is part eight of the Matt Pocock skills guide. Both skills are MIT licensed.
The core idea: deep modules
It all starts from a quote in John Ousterhout's *A Philosophy of Software Design*: "The best modules are deep. They allow a lot of functionality to be accessed through a simple interface".
- Deep module: a small interface with a lot of implementation behind it. Callers learn little and get a lot.
- Shallow module: the interface is nearly as complex as the implementation. It just passes things through.
For an agent this matters twice over. A codebase full of shallow modules forces it to bounce between many files to understand one concept, and every bounce spends context. A deep module is understood through its interface and tested through it.

codebase-design: the vocabulary
codebase-design is a reference skill the agent loads when it designs or changes a module's interface, and that tdd and improve-codebase-architecture both use. Its first rule is to always use the same terms and never swap in "component", "service", "API" or "boundary":
| Term | Definition |
|---|---|
| **Module** | Anything with an interface and an implementation: a function, a class, a package or a vertical slice |
| **Interface** | Everything a caller must know: the signature, but also invariants, call ordering, error modes, configuration and performance |
| **Depth** | Behaviour you get per unit of interface you learn |
| **Seam** | Where the interface lives: a place you can alter behaviour without editing in that place (Michael Feathers) |
| **Adapter** | A concrete thing that satisfies an interface at a seam |
| **Leverage** | What callers get: more capability per thing they learn |
| **Locality** | What maintainers get: changes, bugs and verification concentrate in one place |
The four principles
- Depth is a property of the interface, not the implementation. A deep module can be built from small, swappable parts inside; they just aren't part of its interface.
- The deletion test. Imagine deleting the module. If complexity vanishes, it was a pass-through. If it reappears across its callers, it was earning its keep.
- The interface is the test surface. Callers and tests cross the same seam. If you need to test *past* the interface, the module is the wrong shape.
- One adapter means a hypothetical seam; two adapters mean a real one. Don't add a seam unless something varies across it (usually production and test).
The skill adds three testability guidelines with TypeScript examples: accept dependencies instead of creating them, return results instead of producing side effects, and keep the surface small.
Deepening by dependency type
The DEEPENING.md file sorts a module's dependencies into four categories, because each is tested differently:
- In-process (pure computation, in-memory state): always deepenable; tested directly.
- Local-substitutable (PGLite for Postgres, in-memory filesystem): tested with the stand-in.
- Remote but owned (your own microservices): a port at the seam, an HTTP adapter in production and an in-memory adapter in tests.
- True external (Stripe, Twilio): an injected port and a mock adapter in tests.
And one testing rule: replace, don't layer. Once tests exist at the deep module's interface, the old tests on the shallow modules are waste and get deleted.
improve-codebase-architecture: finding the opportunities
/improve-codebase-architecture is the user-invoked skill to run "whenever you have a spare moment". Its author recommends running it every few days and warns: it's a survey, not a rescue. On an old codebase it will find real candidates, but it won't untangle the mud for you.
1. Explore with intent
Before looking, decide where to look. If you give it a direction (a module, a pain point), it takes it. If not, it walks the git log history for hot spots: the files that change most, because deepening a module pays off when that module will keep being touched (YAGNI).
Then it spawns a subagent with a fresh context that walks the code and notes friction: concepts that require bouncing between many small modules, shallow modules, pure functions extracted only for testability while bugs hide in how they're called, coupled modules leaking across their seams, and areas that are untested or hard to test.
2. An HTML report with before and after
Candidates are presented in a self-contained HTML file in the OS temp directory (nothing lands in the repository), using Tailwind and Mermaid from a CDN, and it opens automatically. Each candidate is a card with:
- Files involved.
- Problem and solution in plain English.
- Benefits in terms of locality, leverage and tests.
- A custom-drawn before/after diagram.
- A strength badge:
Strong,Worth exploringorSpeculative.
The report ends with a top recommendation. If a candidate contradicts an ADR, it only appears when the friction justifies reopening the decision, and it's flagged with a warning.
3. Decide through grilling
Once you pick a candidate, the skill runs the grilling interview on constraints, dependencies, the shape of the deep module and which tests survive. Meanwhile, domain-modeling keeps the glossary current: if the new module is named after a concept missing from GLOSSARY.md, it adds it. If you reject the candidate for a load-bearing reason, it offers an ADR so future reviews don't suggest it again.
To compare alternative interfaces, Design It Twice (DESIGN-IT-TWICE.md) comes in: three or more parallel subagents design the interface in radically different ways (minimal, flexible, optimised for the common caller, ports and adapters), and the agent compares them on depth, locality and seam placement before giving its recommendation.
How they combine
improve-codebase-architecture finds the where; codebase-design is the workbench for the how. The outcome isn't code: it's an idea that enters the main flow at grill-with-docs, continues through to-spec and to-tickets, and gets built with implement.
They also close a loop with diagnosing-bugs: when a bug has no seam to lock a regression test onto, that finding is exactly the kind of friction this skill turns into a candidate.
Common mistakes
- Asking it to fix everything at once. The skill presents candidates and waits for you to pick one.
- Introducing ports with a single adapter. It's indirection with no benefit; the skill itself advises against it.
- Mixing vocabularies. "Service", "component" and "module" for the same thing brings back the problem from part 4.
- Keeping the old tests of shallow modules after deepening.
FAQ
Is it only for TypeScript? No. The examples are in TypeScript, but the vocabulary applies to any language.
Does it write to the repository? The report goes to the temp directory. It only writes GLOSSARY.md or ADRs if terms or decisions get resolved during grilling.
How often should I run it? The author suggests every few days, focused on what has changed most recently.
Next step
Run /improve-codebase-architecture on the area you've touched most this month and apply the deletion test to its first candidate. In part 9 we look at prototype and research, the pair for answering design questions before building.
// source code
// implementation
More on AI agents

- Skills for your agents
- Claude Code

- Skills for your agents
- Claude Code

- Skills for your agents
- Claude Code