If you have ever asked an AI agent for a whole screen and it came back working, you have done vibe coding. The practice caught on because the first hour is impressive: you describe the intent in plain language, the agent writes, you run it, it works.
The trouble shows up in week three, when the agent proposes the fourth variation of the same bottom sheet, imports the database client inside a UI component, and reintroduces a library you removed two months ago because it stalled scrolling on Android. In none of those cases could the model have known. The information existed, just not anywhere it could reach.
This article is about how to structure a project’s knowledge so the agent decides correctly without you re-teaching the same rules every session. The numbers come from a real app, Gaveta, with five months in production, 726 commits, 38 versions published to the stores, and one developer.

What vibe coding is
Vibe coding means writing software by describing the outcome in plain language and letting the AI agent produce the code, reviewing by behavior rather than line by line. The term caught on in 2025 and became an umbrella for everything from a throwaway prototype to a product in production.
Vibe coding is a working mode, and it has two versions with very different fates.
In the throwaway version, you accept the code with no contract. The prototype gets discarded next week and none of this matters.
In the production version, the code stays. And the cost shifts: writing stops being the expensive step, because the agent writes fast. What becomes expensive is that every session starts from zero.

Why the agent forgets the context
The agent does not know why that screen hook carries a sub-view-model for the sheet. It does not know the blur library was swapped out because it recomposed every frame and stalled the list. It does not know Apple already rejected a release over a single word in the store note.
You know all of that, but you forget it too, and three months from now you are a different person reading your own code.
Without somewhere that answers those questions at the moment of the decision, two outcomes follow, and both cost:
- Architecture rots by accretion. Every new screen invents its own variant of the pattern. At Gaveta, that process produced 31 hand-written bottom sheets before anyone counted.
- You become the agent’s human compiler. Every PR turns into a session re-teaching the same five rules. The speed gain evaporates in review.
Treating context as an architectural artifact solves both, with the same discipline you apply to code: explicit boundaries, one source of truth per subject, and automatic enforcement wherever it fits.

One sovereign document per boundary
One giant document that tries to cover everything gets read by neither humans nor agents. It blows past the attention window and becomes noise.
The alternative is applying information architecture to the repository itself. At Gaveta, the root document runs 212 lines and opens by declaring what it is not: it describes how the packages connect, never how to code inside one of them. That belongs to the package document, and the rule is declared inviolable.
The root becomes a routing table:
| Area you are touching | Sovereign document |
|---|---|
mobile/** | mobile/CLAUDE.md (1,259 lines) |
web/** | web/CLAUDE.md (1,853 lines) |
supabase/** | docs/SUPABASE.md |
| Product and design | PRODUCT.md and DESIGN.md |
Document scope follows problem scope. The smallest one runs 116 lines and lives inside the blur component folder, because the decision it holds only matters to whoever is writing blur. The root knows nothing about blur, and that ignorance is deliberate.
It closes with a direct instruction: if your change does not fit any recipe in the sovereign document, stop and ask, do not improvise architecture.

The split between contract and cookbook
Each package document splits into two halves with different natures.
The contract answers "what am I not allowed to do". Principles, a layer table, naming conventions, and the list of lint rules. It fits in a few short sections, because a document that runs long stops being consulted. The Gaveta layer table has five rows and answers ninety percent of "where does this go" questions:
| Layer | May | May not |
|---|---|---|
| Service | Pure async, database, RPC, fetch | React hooks, JSX |
| Data hook | Cache key from the central registry, invalidations | Page JSX |
| Screen hook | Compose hooks, navigate, derive the view model | Import the database client directly |
| View | Typed props, JSX | Queries, mutations, data access |
| Route | const vm = useXScreen() and render the View | Business logic, inline queries |
The cookbook answers something else: "how do I build the thing I need right now". Numbered steps for a feature from scratch, and a set of patterns that already repeated, each linking to the real example in the code.
The cookbook grows by repetition, not anticipation: a pattern only enters after showing up in two places. An anticipated cookbook is decoration.
The detail that saves the most time is a table of "look at this feature as a reference", tying each kind of work to an existing file. Documentation that points at real code ages better than documentation that describes real code.

Turning conventions into lint rules
A normative document usually survives the first two PRs. On the first feature you follow the rule, on the second you do too, on the third you are in a hurry and nothing stops you from ignoring it. A lint rule stops you. At Gaveta, four things break the build at error level: the database client and the caching library imported in the route layer or in any View, a cache key written as a literal outside the central registry, and a modal written by hand instead of the shared primitive.
The error message teaches the way out alongside the ban: "a bottom sheet is not written by hand, use the primitive, which already brings drag-to-dismiss, safe area, keyboard and tokens".
The same logic applies to the design system. A token documented in prose depends on someone remembering it. A lint rule forbidding literal hex in JSX does not, and the difference shows up in month three, with forty screens in place of four.

Declaring the debt inside the document
Gaveta’s web document opens by saying it describes the target architecture rather than the current state of the code: part of the dashboard still has data access inside the page, hardcoded strings, and hand-rolled caching. And it publishes the phase plan rather than pretending the rules already hold, with phase 0 being what is actually active in lint today.
That changes reader behavior, human or agent. A document claiming a hundred percent compliance makes the agent copy legacy code thinking it is the standard, while one that names the debt makes it ask first.

Each rule records the incident behind it
Tracing Gaveta’s history, each normative change lands right after an incident:
- July 28. Apple rejected version 1.5.2 for inaccurate metadata. The cause was one line of the release note starting with "Android:", because the same file feeds both stores. The rule that followed is a verifiable command: before committing, a
grepfor platform names has to come back empty. And it carries the reason: it cost a full review cycle over one word. - August 31. A test link on the splash shipped visible to production. It became a release checklist item, with the explicit condition of confirming it sits inside the development-only block before building.
- July 22. The 31 bottom sheets became a single primitive, in a commit spanning 41 files. The lint rule forbidding hand-written modals was born the same day. Without it, sheet number 32 would appear within two weeks.
In all three the rule got written after the pain, and the text carries the pain with it. A rule without a story becomes bureaucracy and disappears at the first rushed moment.

How Otther applies this
When Otther steps into a product already running with a team and an agent, the first thing we measure is how long it takes someone to make the right decision without asking another person. That number predicts velocity better than test coverage or a debt count.
What we apply, in order:
- Map the boundaries before the patterns. Who decides what, and where that decision lives. Without this, the document becomes a dumping ground for everything nobody knew where to file.
- Keep the contract short and the cookbook long. The contract has to fit in your head. The cookbook can be extensive, because it is reference material.
- Tie every normative rule to a lint entry. The ones without it get marked aspirational, with the phase plan published and the current phase named.
- Tie maintenance to the workflow, not to goodwill. "A new event enters this document in the same PR that instruments it" works. "Keep the document updated" does not.
The same reasoning applies to the handoff between design and engineering, and it explains why so many handoffs fail: when the spec document repeats design system values instead of citing it, it is born out of date. A handoff that points at the source survives the first token change.

What the method costs
Of Gaveta’s 726 commits, 146 touch a markdown file, and 31 of those touch nothing else. That is one in five commits spent on text that does not compile.
The documents accumulate debt too. A diagram at the root still says "33 migrations" when the folder holds 105, because the number landed in May inside a block nobody revisits. And the lines about "CI blocks merge" describe intent, because lint runs on the developer machine, not on a runner. Both cases sit at points left without enforcement.
In exchange, you open the repository after two weeks away, in a package you never touched, and ship a feature without rebuilding the mental model. The agent starts from the same base. Five months, 38 releases, and the architecture layer still has centralized cache keys, six-line routes, and Views with no data access, because lint does not let it through.

How to apply this to your project
The most common mistake when copying this structure is writing all of it on day one. A cookbook with no code to point at is decoration, and a layer table invented before the first feature describes a product that does not exist yet. The order below is the one that survived at Gaveta.
| When | What goes in | What stays out for now |
|---|---|---|
| Before the first feature | A boundary document, one page: which packages exist, who decides what, and the sovereignty rule | Layer table, cookbook, FAQ |
| After the first feature | The layer table, extracted from the code you just wrote | Recipes for cases that have not shown up |
| On a pattern's second occurrence | The pattern becomes a cookbook entry, linking to the real example in the repository | Any pattern that appeared only once |
| Whenever you write a rule | A matching line in the lint configuration | A rule with no enforcement, unless declared aspirational |
| After each incident | A rule line carrying the date and what it cost | Abstract justification with no incident attached |
Two of those rows carry almost all the weight.
The two-occurrence rule. A pattern only enters the cookbook after showing up in two places in the code. Before that you are documenting a coincidence and creating maintenance work for a rule that may never hold.
The lint-or-phase rule. Every normative rule needs a matching line in lint. The ones without it declare themselves aspirational and publish the phase plan, with the current phase named. A rule with neither is the one that disappears on the third PR.
The root document skeleton
# <project> — architecture between packages
This document describes how the packages connect.
It does not teach how to code inside any of them.
**Sovereignty rule (inviolable):** how to code inside a package is
dictated by that package's document. This file never overrides those
rules. It covers boundaries, contracts and flow between packages.
## Routing
| Where you are working | Sovereign document |
|-----------------------|------------------------|
| `<package-a>/**` | `<package-a>/CLAUDE.md`|
| `<package-b>/**` | `<package-b>/CLAUDE.md`|
## Current state
<Name the debt here: where the code does not yet obey the contract,
since when, and what has to happen for it to obey.>
## If it does not fit
If your change does not fit any recipe in the sovereign document,
stop and ask. Do not improvise architecture.
Each package document then splits into two halves: a short contract covering what you may not do, and a long cookbook covering how to build what you need right now, pointing at real examples instead of describing them.
What not to do
Do not write the cookbook before you have code to point at. Do not claim compliance the repository does not have, because a document that pretends to a hundred percent makes the agent treat legacy code as the standard to copy. And do not leave maintenance to goodwill: tie it to the workflow, in the shape of "a new event enters this document in the same PR that instruments it".
Conclusion
Vibe coding breaks when the knowledge justifying each project decision lives in someone’s head, out of reach of whoever has to decide in the moment, agent or person. Markdown, ADRs, a design system or a handbook is a formatting detail. In practice the question is: when the next session needs to decide, will it find the answer where it is going to look?
If your team is gaining speed with AI and losing product consistency at the same rate, that is the problem. Talk to Otther about structuring the context layer of your product.

