Skip to main content

How to Structure an AI-Friendly Codebase

Config-driven architecture, Zod schemas, and CLAUDE.md conventions that let Cursor, Copilot, and Claude generate production-quality code from the first prompt.

DirectoryLaunch Team7 min read
How to Structure an AI-Friendly Codebase

"AI-friendly" is one of those phrases that sounds like marketing until you actually compare the prompting experience in a well-structured codebase versus a typical one. The difference is dramatic — not "a little faster," but "10 correct suggestions in a row" versus "constantly fixing hallucinated imports."

This post is the concrete checklist we use.

What "AI-friendly" actually means

It is not about using more AI. It's about structuring your code so that an AI agent, given a typical context window, has enough signal to produce code that compiles, runs, and matches your existing style on the first try.

Three characteristics define it:

  1. Single source of truth for each concept — one file that owns the schema, the types, and the defaults.
  2. Predictable file locations — the agent can guess where something lives and be right.
  3. Explicit conventions in writing — CLAUDE.md, .cursorrules, or AGENTS.md that encode the non-obvious rules.

Config-driven design

The highest-leverage pattern is pushing all customization into config files that are plain data, not code.

// config/features.config.ts
export const featuresConfig = {
  ratings: { enabled: true, max: 5 },
  comments: { enabled: true, moderation: "auto" },
  ai: { enabled: false },
} as const

This flips the common case. Instead of "find every component that renders ratings and add a conditional," you toggle one flag and the agent knows, from the shape of the config, what to check.

Rule of thumb

If an AI agent asks you "where do I toggle X," your codebase isn't config-driven enough. X should live in a single, obvious file.

Zod schemas as the single source of truth

Every config, every API input, every form should start with a Zod schema. The schema gives you:

  • Runtime validation (cheap insurance against bad data)
  • TypeScript types via z.infer
  • Documentation — the schema is the spec
  • A target for AI-generated code. "Add a field X of type Y" is unambiguous.
import { z } from "zod"
 
export const plansConfigSchema = z.object({
  free: z.object({ listings: z.number(), featured: z.boolean() }),
  pro: z.object({ listings: z.number(), featured: z.boolean() }),
})
 
export type PlansConfig = z.infer<typeof plansConfigSchema>

A codebase with Zod at the boundary lets AI agents propose changes that are statically checkable — they either compile or they don't, no runtime surprises.

Predictable file structure

Here's the shape that works well:

app/              # routes only — thin wrappers
components/       # presentation
  ui/             # primitives (shadcn)
  landing/        # domain grouping
  admin/
config/           # all customization, Zod-validated
lib/              # business logic
  [feature]/
    content.ts
    schema.ts
types/            # shared types (imported elsewhere)

The rule: given a feature name, there is exactly one place to look. No "utils" folder where half the code actually lives. No duplication between lib/ and helpers/.

CLAUDE.md: the contract

Every repo that takes AI collaboration seriously has a top-level CLAUDE.md (or AGENTS.md) with:

  • The commands for build, test, lint (with the correct package manager).
  • The architectural rules in plain English — "never import from app/ into lib/".
  • The conventions that aren't obvious from the code — "we use Server Components by default; opt into Client only when you need useState."
  • Pointers to the config files.

Keep it under 200 lines. AI agents load it into context on every turn — bloat is expensive.

Naming consistency

AI agents infer patterns. If half your route handlers are handleX and half are xHandler, the agent will mix both and introduce subtle bugs. Pick one and enforce it.

The same applies to file names, component names, hook names, and API route shapes. Internal consistency is worth more than external "best practices."

Tests as specification

Tests are the most efficient form of AI context. A good test says:

  • What the input looks like
  • What the output should be
  • What edge cases matter

When you ask the agent to modify parseSubmission, it'll read parseSubmission.test.ts and know exactly what not to break. Code without tests forces the agent to guess intent.

The measurable outcome

After restructuring a directory-site codebase along these lines:

MetricBeforeAfter
First-prompt correctness~40%~85%
Avg. back-and-forth turns per feature6-82-3
Hallucinated imports per 1k LOC3-4<1

These aren't research numbers. They're what happens when an agent has a codebase where guessing works, because the structure is consistent enough that the guess is the right answer.

A worked example: adding a feature in a well-structured codebase

To make this concrete, suppose you ask an AI agent to "add a 'verified' badge to listings, configurable per-tier in features.config.ts."

In a typical codebase, this prompt produces 4–8 messages of clarification: where does the config live, where is the badge component, what's the styling pattern, where do listings render. Each turn introduces small inconsistencies.

In an AI-friendly codebase, the agent's response on turn one looks like this: open config/features.config.ts, add a verified: { enabled: boolean, perTier: ... } block alongside the existing config; create components/listings/verified-badge.tsx mirroring the existing badge style; update components/listings/listing-card.tsx to render it conditionally; add a Zod field to lib/validations/schemas.ts for the verified_at column; update supabase/schema.sql with a column migration. Five files, no clarifying questions — because the structure makes "where does this live?" answerable from naming alone.

This isn't a hypothetical. It's the predictable outcome of three boring decisions: config-driven design, Zod-as-truth, and predictable file placement. The agent finishes in one turn what used to take six. Multiply that across a year of feature work and the codebase becomes 5× faster to evolve, with the same engineer headcount.

Common pitfalls when retrofitting an existing codebase

  • Trying to migrate everything at once. Pick one feature area (e.g., "submissions") and migrate it end-to-end: config → schemas → components → tests. Ship that PR. Move on. Big-bang refactors stall.
  • Adding CLAUDE.md without enforcing it. A document the agent reads but the codebase contradicts is worse than no document — the agent gets confused by the gap. Either align the code or update the doc.
  • Stuffing CLAUDE.md with everything. Anything over 200 lines burns context budget on every turn. Keep it tight: the build commands, three or four architectural rules, pointers to deeper docs.
  • Skipping tests "because AI can't run them anyway." Tests are the cheapest documentation an AI agent can read. Even one test per feature gives the agent a concrete contract to preserve.
  • Using "fix this" as a prompt. Vague prompts produce vague edits. Reference specific files, expected behaviour, and the failure case. Even five extra words of context improve correctness more than a model upgrade.

What to do tomorrow morning

If you maintain a directory or any small product codebase and want AI-edit quality to climb in a single day:

  1. Open every file in your existing config/ (or create one if it doesn't exist). Make sure each is plain data, exported as const. No business logic mixed in.
  2. Pick the three most-edited files in your repo (git log --pretty=format: --name-only | sort | uniq -c | sort -rn | head). Apply the file-structure rules to each: are types imported from a single source, are conventions consistent, is the naming predictable?
  3. Write or update CLAUDE.md with: build/test commands, your one-sentence architecture summary, the three non-obvious rules. Test it by asking the agent to add a small feature and seeing if it follows the rules.
  4. Add one test for the most-changed function in step 2. Watch the agent reference the test on the next change request.

The investment is half a day. The compounding is months.

Takeaways

  • Push customization into typed config files.
  • Use Zod as your source of truth.
  • Pick a file structure and stick to it ruthlessly.
  • Ship a CLAUDE.md that encodes the non-obvious rules.

An AI-friendly codebase is just a readable codebase, taken seriously. The payoff shows up whether the reader is an AI or your new teammate.