agentleFS
siarhei-belavussiarhei-belavus/agent-smitt/skills/engineering/codebase-design/SKILL.md

codebase-design

Skill4 starsChanged 15 days agoMITView on GitHub

Design deep modules proportionally. Use when the user wants to design or improve a module's interface, decide where a seam goes, deepen or privately decompose a module without widening its interface, make it testable through that interface, or judge whether a design is overengineered.

194 other projects use a version of this, 169 of them edited.

Adopted by

Most recently taken up by

Used with

Projects with it also use setup-ts-deep-modules, decision-mapping and 7 more.

Other files people use alongside this one.

Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
  • liufeng1976Skill

    setup-ts-deep-modules

    Wire dependency-cruiser into a TypeScript repo so each package is a deep module — implementation hidden in subfolders, reachable only through its entry-point files. User-invoked.

    Also in 43 projects that use this

  • FabienDehopreSkill

    decision-mapping

    Turn a loose idea into a sequenced map of investigation tickets, then drive them to resolution one at a time.

    Also in 8 projects that use this

  • RoentekSkill

    loop-me

    Grill me about specs for the workflows I want to build, within this workspace.

    Also in 55 projects that use this

  • LesaniSkill

    writing-beats

    Shape an article as a journey of beats, choose-your-own-adventure style. The user picks a starting beat from the raw material, you write only that beat, then offer options for where to pivot next, beat by beat, until the article reaches a natural end. Use when the user has raw material and wants to assemble it as a narrative rather than an argument.

    Also in 58 projects that use this

  • LesaniSkill

    writing-shape

    Take a markdown file of raw material and shape it into an article through a conversational session — drafting candidate openings, growing the piece paragraph by paragraph, arguing about format (lists, tables, callouts, quotes) at each step. Use when the user has a pile of notes, fragments, or a rough draft and wants help turning it into something publishable.

    Also in 58 projects that use this

  • ryotaroh180105Skill

    claude-handoff

    Hand the current conversation off to a fresh background agent that picks up the work immediately.

    Also in 41 projects that use this

  • +1 more from Lesani

The file

We split this skill into the rules it gives an agent

2 rules. 1 is common, 1 is distinctive, and 0 are very specific to this project.

  • 1

    Common

    Most of the 181 edited versions we read keep these.

  • 1

    Distinctive

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Common:

    When designing an interface, ask:

    line 48
  2. Distinctive:

    Keep current-truth documentation proportional to its purpose and audience.

    line 78

“Common” is measured: kept by at least 6 in 10 of the 181 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Codebase Design

Design deep modules: a lot of behaviour behind a small interface, placed at a clean seam, testable through that interface. Use this language and these principles wherever code is being designed or restructured. The aim is leverage for callers, locality for maintainers, and testability for everyone.

Load module vocabulary and TEST-SURFACE.md before applying the design rules.

Glossary

Implementation — what's inside a module, its body of code. Distinct from Adapter: a thing can be a small adapter with a large implementation (a Postgres repo) or a large adapter with a small implementation (an in-memory fake). Reach for "adapter" when the seam is the topic; "implementation" otherwise.

Depth — leverage at the interface: the amount of behaviour a caller (or test) can exercise per unit of interface they have to learn. A module is deep when a large amount of behaviour sits behind a small interface, shallow when the interface is nearly as complex as the implementation.

Adapter — a concrete thing that satisfies an interface at a seam. Describes role (what slot it fills), not substance (what's inside).

Leverage — what callers get from depth: more capability per unit of interface they learn. One implementation pays back across N call sites and M tests.

Locality — what maintainers get from depth: change, bugs, knowledge, and verification concentrate in one place rather than spreading across callers. Fix once, fixed everywhere.

Deep vs shallow

Deep module = small interface + lots of implementation:

┌─────────────────────┐
│   Small Interface   │  ← Few methods, simple params
├─────────────────────┤
│                     │
│  Deep Implementation│  ← Complex logic hidden
│                     │
└─────────────────────┘

Shallow module = large interface + little implementation (avoid):

┌─────────────────────────────────┐
│       Large Interface           │  ← Many methods, complex params
├─────────────────────────────────┤
│  Thin Implementation            │  ← Just passes through
└─────────────────────────────────┘

When designing an interface, ask:

  • Can I reduce the number of methods?
  • Can I simplify the parameters?
  • Can I hide more complexity inside?

Principles

  • Depth is a property of the interface, not the implementation. A deep module can be internally composed of cohesive private parts — they just aren't part of the interface. A module can have internal seams where its implementation genuinely varies, but physical file boundaries do not create seams.
  • The deletion test. Imagine deleting the module. If complexity vanishes, it was a pass-through. If complexity reappears across N callers, it was earning its keep.
  • One adapter means a hypothetical seam. Two adapters means a real one. Don't introduce a seam unless something actually varies across it.

Proportional design

Choose the least-complex coherent design that protects current acceptance criteria, established repository invariants and ADRs, and credible failures while preserving depth and locality. Fewer constructs are not simpler when ownership, invariant knowledge, or boundary policy leaks through the interface into callers.

A failure is credible only when its state is reachable through a named external boundary, supported persisted data, an allowed lifecycle transition, or a realistic concurrency or failure interleaving. Untyped or external data earns the trusted domain type only after validation or translation at its boundary; a cast or unchecked deserialization does not establish that trust. Handle invalid input at the boundary, then make invalid internal states unrepresentable so internal modules can rely on their declared typed contracts.

Every added mechanism must name the acceptance criterion or established invariant it protects, the credible failure and material impact, and why a simpler coherent design is insufficient. A simplification must leave those protections and the intended module shape intact. A deferral names the accepted residual risk and its next owner.

Final state

Leave every affected current-truth artifact in final-state form: code, tests, configuration, schemas, examples, plans, skills, and durable design docs describe the intended system directly, using its current names, owners, statuses, contracts, layout, validation expectations, and vocabulary. Prefer one clear model over artifacts that teach superseded and current shapes together.

Repave the affected area: reshape the affected logic into one coherent current implementation, scoped to the smallest coherent area, so the repair leaves no change-shaped layer. The resulting code should be explained entirely by the domain model and current invariants, as if the correct behavior had been designed from the start.

After a change, run a recency audit: identify every current-truth artifact affected by the changed behavior or contract, including artifacts outside the diff, then reread that full set as a whole without the diff. Integrate new behavior as if it had always existed. Add tests, invariants, files, types, helpers, interface methods, headings, examples, or test groupings wherever the domain model calls for them, but name and organize them by current ownership and behavior rather than by the change that introduced them. Each construct must independently pass proportional design and logical ownership. If the change order explains the grouping, naming, or prominence better than the domain model, reshape the result. Complete the audit when every construct has a present-state domain reason and the result reads naturally to someone who does not know the change history.

Treat the smallest coherent area as the full peer set within the same logical owner and abstraction, not merely the item named in the issue. Identify those peers by present-state domain role and invariant significance, then give comparable peers proportionate structural treatment and emphasis. Make treatment identical when their present-state domain role, invariant significance, and credible risk are equivalent. When one peer gains focused validation, tests, documentation, examples, or proof, audit the whole set at that abstraction level. Prefer one shared mechanism and an exhaustive table or matrix when the peers follow the same invariant; otherwise justify every difference with a present-state domain reason. Reshape uneven coverage when the difference lacks such a reason. Do not equalize treatment merely because one peer changed, and do not give the changed peer additional prominence merely because it triggered the work. Complete the peer audit when coverage, naming, grouping, and emphasis no longer reveal which peer triggered the change.

Keep current-truth documentation proportional to its purpose and audience. Select its document role as well as its repository owner:

  • CONTEXT.md describes the domain model through CONTEXT-FORMAT.md.
  • README.md gives a repository overview, essential usage, and navigation to detail.
  • AGENTS.md gives working rules and pointers that say when to consult their targets.
  • Architectural rationale follows domain-modeling's ADR selection.
  • Necessary feature and mechanism details belong in module or API documentation when code and tests do not already explain them adequately.

Read each affected overview as a whole. Its emphasis must reflect the significance of the system's responsibilities, not which feature changed most recently. Complete the documentation audit when each document serves its role and each overview remains representative of its full scope. Keep detailed documents discoverable through existing navigation without copying their contents into the overview. Update affected knowledge without requiring a new section or document for every delivery; keep delivery narration and one-off validation evidence in delivery records.

Artifact role decides whether chronology belongs. A current-truth artifact answers what is intended now; an intentional history artifact answers how decisions or understanding evolved. Change logs, review findings, issue history, commits, Git history, and explicitly superseded ADRs or learning records may preserve chronology. A file does not become a history artifact merely because notes were appended to it.

Clean breaks

Backward compatibility is a rare, exceptional, deliberately approved mechanism, not a default design requirement or a precaution to add just in case. Only explicit human approval makes it an acceptance criterion.

An internal contract change is a clean break: establish one current contract, update every producer and consumer under the same change authority, and remove the superseded form in the completed change. Aliases, shims, dual reads or writes, legacy payload handling, compatibility wrappers, and deprecated fallbacks are added mechanisms; internal migration convenience is not an acceptance criterion.

A contract crosses a real external boundary when existing consumers outside the service's change authority depend on it, including public HTTP, RPC, webhook, CLI, UI, event, file, protocol, integration, persisted, or published contracts. At that boundary, stop and ask whether backward compatibility is required. Only explicit human approval makes compatibility an acceptance criterion; otherwise make the clean break. When approved, define the supported transition and the condition that removes the old contract.

When uninterrupted operation requires explicitly approved temporary coexistence, treat it as traffic management during repaving, not as final architecture. Isolate the transition at the external seam, translate immediately into one current internal contract, give the transition an owner and observable removal trigger, and remove it when the trigger is met.

Logical ownership and physical decomposition

A module is a logical owner, not a file. Its implementation may span cohesive private files hidden behind one public interface at one seam. Physical decomposition splits independently changing internal responsibilities while preserving that interface, the module's invariants and vocabulary, and every boundary translation. Treat each new file as a private implementation detail. If it needs its own caller-facing interface or direct tests, first establish through proportional design that it is a separate logical owner rather than an internal part of the existing module.

Before adding behavior to a module whose implementation contains independently changing responsibilities, either decompose those responsibilities privately behind the existing seam or state the concrete locality reason they are clearer and safer together. A behavior-preserving private split is tidy-first refactoring within the current implementation authority. Every split should improve locality by concentrating understanding and change.

Designing for testability

Good interfaces make testing natural:

  1. Accept dependencies, don't create them.

    // Testable
    function processOrder(order, paymentGateway) {}
    
    // Hard to test
    function processOrder(order) {
      const gateway = new StripeGateway();
    }
  2. Make observable behavior explicit. Return results when the module's purpose is computation. When its domain purpose is an effect, make that effect explicit in the interface contract and observable at its declared external boundary.

    // Testable
    function calculateDiscount(cart): Discount {}
    
    // Hard to test
    function applyDiscount(cart): void {
      cart.total -= discount;
    }
  3. Small surface area. Fewer interface elements reduce the number of surface combinations and simplify test setup.

Relationships

  • A Module has exactly one Interface (the surface it presents to callers and tests).
  • Depth is a property of a Module, measured against its Interface.
  • An Adapter sits at a Seam and satisfies the Interface.
  • Depth produces Leverage for callers and Locality for maintainers.

Rejected framings

  • Depth as ratio of implementation-lines to interface-lines (Ousterhout): rewards padding the implementation. We use depth-as-leverage instead.

Going deeper

  • Deepening a cluster given its dependencies — execute DEEPENING.md.
  • Exploring alternative interfaces — execute DESIGN-IT-TWICE.md.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.