Why Your Repo Conventions Matter for AI Coding Agents
A file-system coding agent is the fastest junior you've ever hired. It reads 200 files before breakfast, holds the whole tree in context, and writes code that compiles on the first try.
It also picks its own design system.
Hand it "add a settings page" in a clean repo with no conventions written down, and you'll get a page that uses bg-slate-900 while the rest of your app uses bg-[var(--surface-2)], a Modal that ignores the one you've shipped, and a form field that's a <div> with onClick instead of a <button>. It compiles. It passes the screenshot review. Three weeks later you grep for "Modal" and find eleven of them.
The model is clever. The prompt is a guess about your codebase. The codebase itself is the contract.

1. The agent has taste, and the taste is random
Run the same Claude Code or Cursor prompt twice on a green-field repo. The first run picks clsx for class merging. The second picks cn. The third hand-rolls a template literal. All three are reasonable. None of them are yours, because you don't have a "yours" yet.
This isn't a model problem. It's a documentation problem. The model is doing the only thing it can with no signal: pick a plausible default. Plausible defaults diverge. Divergent defaults are technical debt by Tuesday.
2. Three failure modes I keep seeing
Drift. You have a Button in components/ui. The agent decides it would be cleaner to make a <PrimaryAction> in app/(marketing)/components. Now you have two buttons. By Friday, eleven.
Reinvention. Your repo has a working Modal with focus trapping, escape handling, and portal mounting. The agent, scanning the prompt and not the component library, builds a fresh one from a <dialog> element. It even handles Escape. It doesn't handle the design tokens. It doesn't match the card padding. It ships.
Layer violation. A React component lands in lib/. A server-only query lands in a client component. A token reference lands in a route handler. TypeScript was permissive. The bundle doubled. You find out in prod.
I've watched all three happen in a single 40-minute session on a real codebase. The model wasn't broken. The repo was empty of instructions.
11 production screens. Login, database, payments — all wired.
The SaaS Dashboard Kit ships everything already connected. Nothing to set up. Live demo at saas.otf-kit.dev.
3. What the agent actually reads
A file-system agent reads files. That's it. It doesn't read Slack, the design review from last quarter, or the senior engineer's head. It reads what's on disk.
Which means the things that shape its output are:
- The files that already exist — patterns get copied.
- The naming conventions that recur — suffixes get mimicked.
- The dependencies in
package.json— new code uses the same libraries. - The errors from the type checker and linter — hard signals win.
- Any markdown at the root, especially one called
CLAUDE.md,AGENTS.md, or.cursorrules.
Four of those are emergent from a clean codebase. The fifth is the one you control directly. That fifth one is where the use is.
4. What a conventions-aware repo actually looks like
Five things, in priority order.
A root-level CLAUDE.md — 150 to 300 lines, plain English, structured. Mine looks like this:
# Project conventions
## Stack
- TypeScript strict, no `any` without a comment
- Single component library at components/ui/* — never inline a button
- Token system at styles/tokens.css — never hard-code colors or spacing
## File layout
- Routes: app/<route>/page.tsx
- Server logic: lib/<domain>/<action>.ts
- UI primitives: components/ui/<name>.tsx
- All client components start with "use client"
## Forbidden
- Inline class strings for colors (red-500 is banned, use the token)
- New dependencies without a note in DECISIONS.md
- Modal/dialog without <Modal> from components/ui/modal.cursorrules — same content, Cursor-flavoured. Cursor looks for this file by name.
AGENTS.md — same content, the cross-tool convention gaining adoption across Codex CLI and others.
An ai/prompts/ directory with 20 or so tested prompts for recurring tasks. Each one is a markdown file the agent can @-reference:
# prompts/add-button-variant.md
When the user asks for a new button variant:
1. Open components/ui/button.tsx
2. Add the variant to the existing cva(...) call — do NOT create a new file
3. Add a story to app/(design-system)/buttons/page.tsx
4. Add a row to components/ui/__tests__/button.test.tsx
5. Run pnpm test button — do not move on if it failsThe actual primitives. components/ui/button.tsx has to exist for step 1 to mean anything. The tokens have to exist for "never hard-code colors" to be enforceable. Conventions without primitives are wishes.
5. Worked example — adding a "destructive outline" button variant
Without conventions, prompt to the agent: "add a destructive outline button variant." What comes back:
// app/(marketing)/_components/DestructiveOutlineBtn.tsx
export function DestructiveOutlineBtn({ children, onClick }) {
return (
<button
onClick={onClick}
className="border border-red-500 text-red-500 px-3 py-1.5 rounded"
>
{children}
</button>
);
}Three problems. It's a new file when the existing Button would have taken five lines. It's hard-coded red-500 instead of your destructive token. It's a raw <button> instead of your polymorphic component, so it loses the loading state, the icon slot, the asChild prop.
With conventions, same prompt:
// components/ui/button.tsx — diff only
const buttonVariants = cva(base, {
variants: {
variant: {
// ...existing
"destructive-outline":
"border border-destructive text-destructive bg-transparent hover:bg-destructive/10",
},
},
});Same intent. Five lines. Extends the system instead of forking it. The agent could not have produced the second version without reading components/ui/button.tsx first — which it did, because CLAUDE.md told it that's where buttons live.

6. What this gets you
The same agent, run twice on the same prompt, produces the same diff. That's not nothing — that's the difference between a tool and a teammate.
Three concrete wins:
- Determinism. Re-running the agent on a PR gives the same suggestions. Code review becomes "did the agent follow the conventions?" instead of "what did the agent pick this time?"
- Onboarding. A new dev runs
claudein the repo and gets the same answer a senior would. TheCLAUDE.mdis also their onboarding doc. - Survival across model churn. When the model behind the agent changes in three months, the conventions don't. Your design system survives the swap.
7. Where OTF sits in this
Every paid OTF kit ships with all five of those things, pre-written and tested against the kit itself. CLAUDE.md, .cursorrules, and AGENTS.md describing the kit's component layout, token system, auth pattern, and forbidden shortcuts. An ai/prompts/ directory with 20+ prompts for "add a page", "add a billing flow", "add a screen to the mobile app" — each one verified by running it through Claude Code and Cursor against the actual kit and checking the diff.
The point isn't to lock you into OTF. It's that the convention contract is the part that doesn't change when the model does. Use whichever agent you want — Claude Code, Cursor, Codex CLI — and the kit's CLAUDE.md is the durable layer underneath the churn. The agent extends the kit instead of regenerating it. The kit's tokens stay the tokens. The kit's Button stays the only Button.
That's the part worth owning.
Ship the product, not the setup.
- 11 production screens — auth, billing, team, analytics, settings
- Real database, payments, and login — all wired on day 1
- AI configs pre-tuned so your agent extends instead of regenerates