How one codebase holds its shape
Software drifts (toward duplication, toward tangle, toward lost threads) unless something holds it. We build with an AI coding agent, so nothing holds unless it's written down where the agent reads it. Here's what keeps ours on track: one set of rules loads first and governs every decision, a core holds the live state and the plan, each module extends a shared registry instead of redefining it, and a few named contracts, pinned by tests, connect only the modules that genuinely share something. The graph is just a way to picture it.
This is the code half. For how the work stays coherent across sessions, see the system → /the-system
What the picture is saying
Open a layer to walk it, or start at the top
The walkthrough, as text
Every stage of the interactive walkthrough above, as plain readable text. Each stage has its own link.
Stage 0The rules that load firstLoaded every turn, before any code
The always-on rules
CLAUDE.md and the memory/ files load before any work starts and stay loaded every turn. Every later decision happens inside those rules, not against them. A lesson only becomes a rule after it survives three audits, generalizes, and reduces to one line.
The laws that hold everywhere. They load on every turn, you don't visit them, and they decide what's even allowed to become a law.
What happens
Two files load before the first line of work and stay loaded every turn. CLAUDE.md is the laws: how we communicate, what scope a change is allowed, the build-and-test gate, the security posture. The memory/ files are the measured constants, the lessons already proven that now bind without being asked. This is stage zero, not stage one, because you don't travel here. On the map, it's the field the whole graph sits in.
The rules govern their own growth, so the always-on prompt stays small and true. A lesson doesn't become law because it sounded right once. It earns in through the promotion gate: it survives three audits without being walked back, it generalizes beyond this one codebase, and it reduces to a single line. Then /end-session promotes it into memory/. An always-loaded index points at the bodies, which load only when the work matches one. Measure a constant enough times and it stops being an observation.
Who acts
- CLAUDE.md, rule: The laws. Communication, scope, the build/test gate, security posture. Loaded every turn, before any module is touched.
- memory/, constant: The measured constants. The index (MEMORY.md) loads every turn; each proven lesson body loads only when the work matches it.
- the promotion gate, gate: The test a lesson passes to become law: survives three audits, generalizes beyond this codebase, reduces to one line. Run by /end-session.
The rules
- Survives three audits. A lesson that gets walked back under pressure was never a law. Three clean passes, or it stays an observation in STACK.
- Generalizes past its first case. If it only explains the one bug that spawned it, it's a fix, not a law. A law generalizes beyond this project.
- Reduces to one line. If it can't be stated in a single line a tired person can follow at 2am, it won't get followed.
- Then /end-session promotes it. Pass all three and it moves into memory/. That promotion is the only door in.
In: Nothing. The rules load before anything else runs. Out: The laws every later stage is bound by, plus the gate that decides what becomes one.
Stage 1The StackWhere you are, where you're going
The core
Starting an app, the first thing that exists is the Stack: two files at the center holding the live state and the plan: where the work is right now, and the arc it's moving along. They only mean something together. Anything a module needs to share routes through this center, never sideways to another module.
The center you start from: the live state and the plan, paired. Plus the one hard rule: all shared data routes through here, never module to module.
What happens
Before any feature exists, the Stack does. STACK.md is the live state: where the work is right now, including the handoff that /start-session reads and /end-session rewrites. The plan is the journey: where we've been and where we're going. Neither stands alone, so they pair at the center as the thing the whole app turns around. On the graph, that's the Stack node at the heart of it.
One rule lives here too, and it shapes everything built later: when a module needs something another module owns, it doesn't reach sideways. It routes through the center. Real shared data runs through the Stack and never straight from one module to another, which is why, on the graph, the only direct line between two modules is a named contract.
Who acts
- STACK.md, state: The live state and the session handoff: where the app is right now, read at the start of a session and rewritten at the end.
- the plan, journey: The journey: where the build has been and where it's going, the arc the live state moves along.
- the shared type, shared: A definition many modules need is written once, here at the center; every module reads it through the Stack, never copies it from a sibling.
The rules
- State and plan are read together. STACK.md and the plan are read and written as a pair. Neither is canonical alone; together they're the source of truth for where the app is.
- Everything shared routes through the center. A module that needs shared data reads it through the Stack, never from a sibling module. The missing module-to-module line is the rule made visible.
- Defined once. The center defines each shared thing a single time. Modules extend it; they don't redefine it.
In: The always-on rules, which bind how the Stack can change. Out: The shared definitions every module reads through, plus the path they route along.
Stage 2Build modules outwardA registry, plus pages that extend it
Modules
With the Stack in place, you build the app as modules, innermost first. Each module is one unit built around a registry: every mechanic that spans its pages is defined once, with a stable ID, a producer, and a consumer list. Its pages reference those IDs instead of re-describing them. This is the unit that keeps a growing app from drifting into ten copies of the same rule.
One pattern, repeated outward. A module is a registry plus the pages that extend it: the unit that stops an app drifting into duplicate, disagreeing copies of one rule.
What happens
A module is one unit of the app, and its hub is a registry: every mechanic that spans its pages is defined once, with a stable ID, a producer, and a list of consumers. Pages open with a short contract (what they produce and consume by ID), and they only extend the registry, never redefine a mechanic. On the graph a module is a small cluster: a hub (the registry) with its pages around it. The plain point is the registry: one place names each shared mechanic, and everything else points at it. That single-definition rule is the whole defense against drift. The moment a rule gets a second copy, the two start to disagree.
The registry changes form as the work moves, and that's deliberate. At design time it's the index of shared behavior: the thing you reference precisely because there's no code to grep yet. At build time the code itself becomes the registry: a shared mechanic is now a typed field, and the type system enumerates every consumer for you, so the spec can freeze and code becomes the source of truth. You build modules from the Stack outward, innermost first, so each new one rests on a center that already exists.
Who acts
- the registry, module: The module's hub. Defines each cross-page mechanic once, with a stable ID, a producer, and a consumer list.
- a page, page: Opens with a contract, produces and consumes by ID, extends the registry, and reads shared data through the Stack.
The rules
- Each mechanic is defined once, by ID. The registry names every cross-page mechanic a single time, with a stable ID, a producer, and its consumers. Pages reference the ID; they don't re-describe it.
- Pages extend, never redefine. A page opens with a contract and may only add to the registry. It can't fork a mechanic into a second definition, and it reads shared data through the Stack.
- At build time, the type system is the registry. Once code exists, a shared mechanic is a typed field the compiler enumerates over every consumer, the spec freezes, and code becomes truth.
- Build innermost first. Modules grow Stack-outward, so each new one rests on a center that already exists rather than reaching for something that isn't there yet.
In: Shared definitions read through the Stack, never from a sibling module. Out: A module whose every shared mechanic has exactly one definition, ready for a contract if it ever genuinely shares one.
Stage 3Contracts & StandingNamed agreements + cross-cutting threads
Contracts & Standing
A registry defines what one module's pages share, inside that module; a contract is the rare line drawn between two. Route real shared data through the Stack, and draw a contract only for the leftover a type can't hold (the one rule both sides must agree on), pinned by a test. A shared word is not a shared mechanic. Standing follow-ups are the cross-cutting threads that don't belong to one module; they live in Standing-FUs.md, trigger-gated and kept light.
The named lines between modules, pinned by a test, plus the Standing follow-ups that don't belong to any one module. A shared noun is not a shared mechanic.
What happens
Several modules touching the same word does not make a shared mechanic. Instead of inventing a cross-module row that fabricates a producer and a consumer, each module keeps its own in-module mechanic and they all read the shared type through the Stack, so the data routes through the center. Only the leftover a type can't hold (the one rule both sides must agree on) gets drawn as a contract: a named agreement across just those two modules, pinned by a characterization test so it can't drift. On the graph it's the one direct line between two modules.
Standing follow-ups are the other thing out here: cross-cutting threads that can't finish inside one module or one phase. Each waits on a trigger, fires when it arrives, then drops back into the backlog, not bound to any one module. They live in Standing-FUs.md, separate from the in-flight follow-ups in STACK.md, so the cross-cutting ones don't get lost when a phase closes. Kept deliberately light: plain bullets, no heavy taxonomy, the discipline without the weight.
Who acts
- a contract, contract: Two modules agree on the one leftover a type can't hold: the rule both sides must honor. Pinned by a characterization test; the data still routes through the Stack.
- Standing, comet: Cross-cutting follow-ups, trigger-gated, homed in Standing-FUs.md and kept light. The in-flight, phase-local follow-ups stay in STACK.md.
- the shared type, shared: The real shared data routes here, read through the Stack at the center, never module to module.
The rules
- A shared noun is not a shared mechanic. A word several modules touch through unrelated verbs is not a cross-module row. Drawing one fabricates an edge that doesn't exist.
- Route the data, draw the leftover. Decompose into in-module mechanics that read the shared type through the Stack. Draw a contract only for what a type can't hold: the one rule both sides must agree on.
- Pinned by a test, not a promise. A contract is real when a characterization test witnesses it. Without the test it's two modules hoping the agreement still holds.
- Standing is homed and trigger-gated. Standing follow-ups live in Standing-FUs.md, trigger-gated and kept light. The in-flight, phase-local follow-ups stay in STACK.md.
In: Two modules and the one leftover a type can't hold between them. Out: A named, test-pinned contract, plus the Standing follow-ups waiting on their triggers.
Stage ✓Where we're least sureThe open questions, named
// where we're least sure
The honest edge: the parts of the model genuinely unsettled, named plainly so you can push on them. Reply on LinkedIn or open a GitHub issue. The work gets sharper when it gets challenged.
The honest edge of the model. Three open questions, named plainly, with two ways to push back: reply on LinkedIn or open a GitHub issue.
What happens
Here's where the model is soft. Each of these is a place it could be wrong, and it gets sharper when someone who does the work pushes on it. The read-vs-build seam below is the one least settled.
Two ways in. Reply on LinkedIn for a conversation, or open a GitHub issue to track it against the artifact. Both land on a named question, not a contact form.
Who acts
- the read-vs-build seam, question: You read the rules first, but you build within them, not the rules themselves. Is that step 0 or step 1? Reply on LinkedIn.
- is the picture falsifiable, question: Strip the graph to boxes and arrows. If the substance doesn't stand without it, the picture was carrying the meaning instead of the software terms. Tell us on LinkedIn.
- the first contract, question: A generic contract (one rule two modules must both honor) is the safe first line to draw. Is it the right first one, or too abstract to teach the rule? Reply on LinkedIn.
The rules
- Read first, build within. You read the rules before anything, but you build inside them, not the rules themselves. That seam is surfaced here, not hidden.
- The picture must be falsifiable. Strip the graph to boxes and arrows. If the substance doesn't survive that, the picture was carrying the meaning instead of the software terms.
- Generic first, on purpose. The first contract drawn is a generic one, concrete enough to teach the rule, generic enough to publish.
- Two reply paths, no sales surface. Reply on LinkedIn or open a GitHub issue. Each goes to a named question. Neither is a contact form or a pitch.
In: The full walkthrough: the rules, the Stack, the modules, the contracts. Out: Your pushback, on LinkedIn or as a GitHub issue, and a sharper model.