Module-level design for code that is getting harder to change. A deep module
gives callers a lot of behaviour behind a small interface; a shallow one makes
them learn almost as much as it does. This skill measures where a codebase is
shallow, ranks the places where deepening would pay back, writes the review,
and then designs the replacement interface at least twice before anyone writes
it. The goal: callers get a lot done through very little surface, a change
stays inside one module, and tests exercise the same interface callers use.
git log), to weight hotspotsWhat most shapes the output is whether this is a review of existing code or the design of one module, and how much change the codebase can absorb. Settle the inputs below before producing anything. When one is missing or vague and would change the result, ask about it; when it wouldn't, use a sensible default and say which one you used.
Ask about the two or three inputs that matter most for this request, not the whole list. If the user wants a result straight away, produce it and state your assumptions at the top.
# Rank shallow, pass-through and leaky modules in the sample order-management codebase
python3 engineering/deep-module-design/scripts/module_depth.py \
--input engineering/deep-module-design/assets/sample_codebase.json
# Compare three candidate interfaces for the merged order-intake module
python3 engineering/deep-module-design/scripts/interface_compare.py \
--input engineering/deep-module-design/assets/sample_designs.json
The agent finds deepening opportunities, ranks them, and writes a report; it does not propose signatures until a candidate is chosen.
references/architecture-review-playbook.md).```bash
python3 engineering/deep-module-design/scripts/module_depth.py \
--path . --churn churn.json --format json > depth.json
```
assets/architecture-review-template.md: friction with evidence, proposed deepening in plain words, leverage and locality gains, test impact, before/after sketch, strength label.Validation checkpoint: every candidate cites evidence (churn, a bug, a duplicated rule), uses glossary names, and states which tests move or get deleted.
references/depth-principles.md).assets/interface-design-brief-template.md).assets/sample_designs.json and run the comparison:```bash
python3 engineering/deep-module-design/scripts/interface_compare.py \
--input engineering/deep-module-design/assets/sample_designs.json --format json
```
module_depth.py on the area and verify that the candidate no longer appears in the flagged list.| Signal | Deepen or merge | Keep as is |
|---|---|---|
| Deletion test | Complexity vanishes when inlined | Complexity spreads to N callers |
| Pass-through share | ≥ 50% of functions only forward | Each function adds a rule, translation or check |
| Seam | One adapter, nothing varies | Two adapters, behaviour varies across them |
| Callers' preparation | Every caller repeats the same setup | Callers call it directly |
| Tests | Reach past the interface | Go through the interface only |
module_depth.py [RECOMMENDED]| Flag | Default | Meaning |
|---|---|---|
--shallow-below | 4.0 | Under 4 code lines per interface unit, with 3+ entry points, is shallow |
--deep-above | 8.0 | 8+ lines per interface unit is reported as deep |
--pass-ratio | 0.5 | Half or more functions forwarding (and at least two) marks a pass-through layer |
--max-params | 5 | More parameters than this marks a leaky entry point |
Interface cost counts each entry point as 1 plus its parameters. The ratio is a pointer, not a verdict: read the code before reporting a module.
Put a seam only where you can name two adapters. Storage, messaging and third-party services earn ports; steps inside one calculation do not. Keep internal seams private to the module's own tests.
Mistake: Splitting a function into many tiny pure helpers so each can be unit-tested, while the bugs live in how they are combined.
Why it happens: Small functions are easy to test and the coverage number rises.
Instead: Test through the interface that callers use. Keep helpers private; the deep module's interface is the test surface.
Mistake: Controller → service → manager → repository, where three of the four layers forward their arguments unchanged.
Why it happens: A layered template was applied by rule, not by need, and each layer "might need logic later".
Instead: Run the deletion test on each layer. Merge pass-throughs into the module that owns the behaviour; add a layer back when it has a second adapter or a real rule.
Mistake: Introducing an interface plus factory for a dependency that has exactly one implementation and no test fake.
Why it happens: It looks like good design and costs little at first.
Instead: Add a seam when a second adapter exists — usually the in-memory fake the tests need. Until then it is indirection that callers must read through.
Mistake: Shipping the first interface that came to mind for a module many callers will depend on.
Why it happens: The first design works, and alternatives feel like extra work.
Instead: Write at least two designs under different constraints and compare them with interface_compare.py. The second design usually reveals a responsibility the first leaked to callers.
Mistake: Adding tests at the new deep interface but keeping all the old tests on the absorbed internals.
Why it happens: Deleting tests feels like losing safety.
Instead: Once the new interface's tests cover the behaviour, delete the old ones. Tests that pin internals make the next refactor as expensive as this one.
| Script | Purpose | Exit codes |
|---|---|---|
scripts/module_depth.py | Per-module entry points, interface cost, code lines, depth, pass-through functions, long parameter lists and private-name imports for Python (AST) and JS/TS (pattern based); ranks opportunities, weighted by --churn | 0 nothing flagged, 1 shallow / pass-through / leaky modules found, 2 input error |
scripts/interface_compare.py | Scores candidate interfaces on interface cost, depth, caller burden; enforces placed responsibilities, two-adapter seams and distinct designs; recommends one | 0 all designs valid, 1 rule violations, 2 input error |
Both take --format text|json. module_depth.py accepts --path for real code or --input for a JSON bundle of {"files": {name: source}, "churn": {name: count}}; test files are skipped unless --include-tests.
| File | Purpose |
|---|---|
references/depth-principles.md | Vocabulary, five module tests, leak catalogue, seams and adapters, dependency categories and test strategy, design moves, acceptable shallow shapes |
references/architecture-review-playbook.md | Seven-phase review: hotspots, scan, rank rubric, report, interrogation, design twice, migration; question bank |
assets/architecture-review-template.md | Report with candidate cards, ranking and before/after sketches |
assets/interface-design-brief-template.md | One brief per candidate design, feeding the comparison JSON |
assets/sample_codebase.json | Seven-module order-management bundle — analyser exits 1 with six flagged modules |
assets/sample_designs.json | Three interface designs for order intake — comparison exits 1, recommends design A |
In Scope:
Out of Scope:
Important Caveats:
ast module and is exact on syntax.interface_compare.py scores what you describe. A design described optimistically scores optimistically; the usage example is the honesty check.