AC
Chapter 02Start With the Codebase

Conventions agents understand

Consistent naming, clean interfaces, logical file grouping. What makes a codebase agent-friendly.

30 minLesson 4 of 4

The agent doesn't just read your code — it parses it for patterns. Every file it opens teaches it how your project works. Consistent conventions make that pattern recognition instant. Inconsistent conventions force guessing.

This lesson covers the specific conventions that matter most for agent performance. Not because these are universally "best practices" — but because these are the patterns agents parse best.

File naming: pick one, use it everywhere

This seems trivial. It's not.

If your components directory has UserProfile.tsx, admin-dashboard.tsx, and settings_page.tsx, the agent has to deal with three different conventions. When it creates a new file, which pattern does it follow? It picks whichever file it read most recently. That's not a strategy.

Here's what I use across my projects:

Naming conventions
Components:    kebab-case.tsx    (admin-layout.tsx, course-sidebar.tsx)
Hooks:         kebab-case.ts     (use-auth.ts, use-debounce.ts)
Utilities:     kebab-case.ts     (format-date.ts, calculate-price.ts)
Types:         kebab-case.ts     (database.types.ts, course.types.ts)
Pages:         page.tsx           (Next.js App Router convention)

The specific pattern matters less than consistency. If you prefer PascalCase for components, do that. Just do it everywhere.

Good convention
Bad convention
src/components/Button.tsx
src/stuff/Btn.tsx
getUserById(id: string)
getUser(x: any)
CLAUDE.md with boundary rules
No documentation at all
Consistent folder structure
Files scattered randomly

Export patterns

Named exports or default exports? Both work. Mixing both in the same category of file doesn't.

My convention: named exports for everything except page components.

Export conventions
// Utilities — named exports
export function formatDate(date: Date): string { ... }
export function calculateReadingTime(content: string): number { ... }
 
// Components — named exports
export function CoursesSidebar({ chapters }: Props) { ... }
export function ExerciseBlock({ exercise }: Props) { ... }
 
// Pages — default export (Next.js convention)
export default function CoursePage() { ... }

When the agent sees named exports consistently, it follows the pattern. When it sees a mix, it improvises. Improvisation is the enemy of consistency.

Directory grouping: feature-based or type-based

Two valid approaches. Pick one.

Feature-based

Group by what the code does. /admin/layout.tsx, /admin/sidebar.tsx, /admin/hooks.ts. Everything about admin lives together.

Type-based

Group by what the code is. /components/, /hooks/, /utils/. All components live together regardless of feature.

I use a hybrid in my monorepo — type-based at the package level (packages/ui/, packages/api/), feature-based within the app (components/admin/, components/course/, components/blog/). But the key is that each level is internally consistent.

What kills agent performance is mixing both at the same level. If components/admin/ is feature-based but then hooks/ is a flat dump of every hook in the project, the agent can't predict where a new hook for the admin feature should go.

Import paths: aliases over relative imports

Agents follow import aliases perfectly. They parse your tsconfig.json, find the path mappings, and use them. Deep relative imports create ambiguity.

tsconfig.json paths
{
  "compilerOptions": {
    "paths": {
      "@/*": ["./apps/web/*"],
      "@jd/ui": ["./packages/ui/src/index.ts"],
      "@jd/shared": ["./packages/shared/src/index.ts"],
      "@jd/db": ["./packages/db/src/index.ts"],
      "@jd/api": ["./packages/api/src/index.ts"]
    }
  }
}

Compare these two imports:

Import clarity
// Clear — the agent knows exactly what package this comes from
import { Button } from '@jd/ui'
import { cn } from '@jd/shared'
 
// Ambiguous — is this the right depth? Is this even the right file?
import { Button } from '../../../packages/ui/src/components/button'
import { cn } from '../../../../packages/shared/src/utils'

Aliases are unambiguous. The agent sees @jd/ui and knows: that's the UI package, use the public API. Relative imports with ../../../ force the agent to count directory levels — and it sometimes counts wrong.

Language-specific conventions in CLAUDE.md

The ECC methodology recommends layered rules — common rules as a baseline, with language and framework-specific overrides. For TypeScript projects, these conventions make agent output dramatically more consistent:

CLAUDE.md — TypeScript conventions
## TypeScript Conventions
- Explicit types on all exported functions
- `interface` for object shapes, `type` for unions/intersections
- Zod for runtime validation, `z.infer<>` for derived types
- No `any` — use `unknown` with type guards
- No enums — use `as const` objects

These aren't opinions. They're disambiguation rules. When the agent knows "use interface for objects," it never has to decide between interface and type for a simple props definition. One less decision means one less chance to be inconsistent with what you already have.

Define these in CLAUDE.md so every session starts with the same conventions loaded.

Many small files beat few large files

This is an ECC principle that directly impacts agent performance. The agent reads entire files — it can't read just a function from a 2000-line file. Every file it opens consumes context window.

Target: 200-400 lines

Most files should land here. High cohesion — one responsibility, one file. The agent reads it fast and understands it completely.

Maximum: 800 lines

If a file exceeds this, it's doing too much. Split by responsibility. The agent will thank you with better output.

The anti-pattern I see constantly: the 2000-line "god file." A utils.ts that contains every helper the project has ever needed. A types.ts with every interface across every feature. The agent reads the whole thing, wastes context budget on 1800 lines it doesn't need, and still might not find the 3 functions that matter.

Split it. format-date.ts, calculate-price.ts, parse-url.ts. Each file is 20-50 lines. The agent reads only what it needs. Context usage drops. Output quality goes up.

ECC codifies this as a rule with specific thresholds: target 200-400 lines per file, hard cap at 800, and keep functions under 50 lines. Those numbers are not arbitrary — they come from measuring how context consumption and output quality change as file size increases. The rule lives in their coding-style.md, loaded into every session as a permanent guideline. You do not have to adopt their exact numbers, but having explicit limits in your CLAUDE.md means the agent never has to guess when a file is "too big."

The "Where new code goes" table

Of every convention you can set in CLAUDE.md, this single table prevents the most mistakes:

CLAUDE.md — Where new code goes
## Where Does New Code Go?
 
| Type              | Package           | Example                    |
|-------------------|-------------------|----------------------------|
| UI primitive      | @jd/ui            | Button, Card, Dialog       |
| Zod schema        | @jd/shared        | blogArticleSchema          |
| Utility function  | @jd/shared        | cn(), calculateReadingTime |
| TRPC router       | @jd/api           | New data endpoint          |
| Supabase migration| apps/web/supabase/| New table/column           |
| Page/layout       | apps/web/app/     | New route                  |
| Feature component | apps/web/components/| Admin panel, contact chat |
| E2E test          | apps/web/e2e/     | Playwright test            |

Two columns. No ambiguity. The agent reads this table and never puts a Zod schema in the app directory or a feature component in the UI package. I've had this table in my CLAUDE.md from day one, and it has prevented more misplaced files than every other rule combined.

If you take one thing from this lesson: write this table. Adapt the rows to your project. Put it in your CLAUDE.md. It takes five minutes and saves hours.

Conventions compound

No single convention transforms your agent workflow. But collectively, they compound. Consistent naming means the agent predicts file locations. Clean exports mean it generates matching patterns. Small files mean it reads efficiently. The routing table means it never guesses where code goes.

Each convention removes one decision the agent has to make. Fewer decisions, fewer mistakes, more consistent output. That's the entire game.