Skip to content

A project structure optimized for AI coding agents groups code by feature, keeps files small and named for what they contain, places each test beside the code it checks, and puts scoped instructions in the directory they govern. Claude Code, Codex and Cursor then find the right files with fewer searches and load only the rules that apply.

You ask the agent to add a refund endpoint. It searches for validate, opens a 1,400-line utils.ts, reads 40 unrelated helpers, picks the wrong one, and writes the new test into a __tests__ tree that CI does not run. The model did what the repository allowed. This page is for developers who restructure code with agents and for tech leads who set the layout conventions every agent on the team inherits.

What you’ll get from restructuring for agents

Section titled “What you’ll get from restructuring for agents”
  • Five layout rules, each tied to a specific way agents find and read code.
  • Directory-scoped instructions for Claude Code, Codex and Cursor, with the loading rule each tool actually follows.
  • Four copy-paste prompts: a read-only structure audit, a behaviour-preserving file split, scoped instructions for your busiest directories, and a findability probe.
  • CI checks that keep the structure from drifting back: a file-size budget, a circular-import check, and a rename-only diff.

This page assumes you know how agents search a repository. If not, read code search and indexing for AI agents first.

Why does file layout change what the agent reads?

Section titled “Why does file layout change what the agent reads?”

An agent learns your codebase the way a new hire does on day one: it lists directories, searches for names, and opens files. Every file it opens lands in the context window, where it competes with your instructions for attention (see how context windows fill up). The layout decides how many of those reads are wasted.

Layout propertyWhat the agent does with itWhat goes wrong without it
Feature foldersLists one directory and sees the whole featureOpens controllers/, services/, models/ and types/ to assemble one change
Small filesReads a file whole and understands itReads a partial view of a large file, or reads 1,000 lines to use 20
Descriptive namesFinds the file from a filename search aloneOpens helpers.ts, misc.ts and common.ts speculatively
Co-located testsDerives the test path from the source pathWrites tests in the wrong tree, or skips them
Scoped instructionsLoads the billing rules only when it touches billingCarries every team’s rules in every session, or none

Large files have one more cost in Claude Code. When a whole-file read exceeds the Read tool’s token limit, the tool returns only the first page with a PARTIAL view notice, and Claude has to page through the rest (Claude Code tools reference, checked 2026-09-26). Claude can miss a helper defined on line 1,300 of such a file.

Which layout rules matter most for agents?

Section titled “Which layout rules matter most for agents?”

Apply these in order. The first two give most of the benefit, and the last three depend on them.

A type-first layout scatters one feature across the tree. A feature-first layout keeps it in one directory that a single listing reveals:

# Type-first: one refund change touches four directories
src/controllers/refund-controller.ts
src/services/refund-service.ts
src/models/refund.ts
tests/services/refund-service.test.ts
# Feature-first: one directory, one listing
src/features/refunds/
refund-handler.ts
refund-handler.test.ts
refund-policy.ts
refund-policy.test.ts
refund-types.ts
AGENTS.md # rules for this feature only
CLAUDE.md # one line, @AGENTS.md, for Claude Code

Keep code that really is shared (database client, logging, error types) in src/shared/, split into small files with specific names. shared/ is not a new utils.ts: if a function has one caller, it belongs in that caller’s feature folder.

No published study sets the right line count, so choose a budget, write it down and let a linter hold it. Start at 400 lines, the value in the config below, and adjust it once you have seen the audit. What matters more than the number is that the split follows responsibility: refund-policy.ts and refund-handler.ts, never refund-1.ts and refund-2.ts.

Enforce it for TypeScript and JavaScript with ESLint’s core max-lines rule, as a warning first and an error once the backlog is cleared:

eslint.config.js
export default [
{
files: ['src/**/*.{ts,tsx}'],
rules: {
'max-lines': ['warn', { max: 400, skipBlankLines: true, skipComments: true }],
},
},
];

The filename is the search index. An agent that searches for password should get password-hashing.ts, not a hit on line 612 of auth-helpers.ts.

VagueSpecific
utils.ts, helpers.ts, misc.tspassword-hashing.ts, currency-format.ts, csv-parse.ts
service.ts inside notifications/notification-sender.ts
types.ts with 500 linesrefund-types.ts, invoice-types.ts
index.ts that re-exports 60 symbolsDirect imports, or a barrel under 20 lines

A fixed rule, x.ts → x.test.ts in the same directory, lets the agent find the test for any file without a search and put new tests where your test runner looks. If your framework needs a separate test tree (for example, Python projects with tests/ mirroring the package), mirror the source path exactly and write the rule into your instruction file.

A barrel file (index.ts) that re-exports a whole package makes the agent read one file to find the next. Circular imports make module boundaries ambiguous, so the agent cannot tell which module owns a function. Both show up in the CI checks later on this page. For layer rules (UI must not import the database client), use the boundary checks in making a codebase agent-ready rather than a second tool.

Put scoped instructions where the code lives

Section titled “Put scoped instructions where the code lives”

The root instruction file should hold what applies everywhere. Rules that apply to one directory belong in that directory, so they load when the agent works there and cost nothing when it does not. The three tools load scoped instructions differently, and the difference decides where you start the session.

Claude Code loads CLAUDE.md files in your working directory and every directory above it at launch. A CLAUDE.md in a subdirectory loads on demand, when Claude reads files in that subdirectory. For rules that follow a file pattern rather than a directory, use a path-scoped rule in .claude/rules/:

---
paths:
- "src/features/billing/**"
---
# Billing rules
- Money is integer cents: use the `Cents` type from src/features/billing/money.ts. Never use floats.
- Every handler in this folder has a sibling `*.test.ts`. Add one with every new handler.

Save it as .claude/rules/billing.md. Path-scoped rules trigger when Claude reads a matching file, not on every tool use (Claude Code memory docs, checked 2026-09-26). Claude Code also reads AGENTS.md in a project with no CLAUDE.md, including AGENTS.md files in subdirectories, as long as there is no CLAUDE.md in the working directory or any directory above it; that needs v2.1.277 or later (v2.1.281 on Bedrock, Google Cloud, Foundry, LLM gateways or with telemetry off), on both release channels since stable reached v2.1.287 (checked 2026-10-12).

Two gotchas. The built-in Explore and Plan subagents skip CLAUDE.md files, so a directory map kept only there does not reach them. In a monorepo, ancestor CLAUDE.md files from other teams still load; exclude them with claudeMdExcludes in .claude/settings.local.json.

A team that uses both tools should write each directory’s rules once, in that directory’s AGENTS.md. Two copies of the same rules drift apart. Make Claude Code see the file in one of two ways (Claude Code memory docs, checked 2026-09-26):

  • Add a CLAUDE.md beside it that contains only the import line @AGENTS.md. Imports are expanded when the CLAUDE.md loads.
  • Set Project instructions to claude-md-and-agents-md in /config, so Claude Code reads CLAUDE.md and AGENTS.md files together.

Do not rely on the default. With the default setting, claude-md-or-agents-md, Claude Code reads AGENTS.md files only when there is no CLAUDE.md, .claude/CLAUDE.md or CLAUDE.local.md in the working directory or any directory above it. A repository with a root CLAUDE.md therefore makes a bare src/features/billing/AGENTS.md invisible to Claude Code, while Codex still loads it.

Keep each scoped file to rules the agent cannot infer from the code: invariants, the one approved helper, where tests go. Documentation as context covers what else belongs in instruction files, and pruning CLAUDE.md and AGENTS.md covers removing what no longer earns its tokens.

Restructure an existing codebase with an agent

Section titled “Restructure an existing codebase with an agent”

A restructure is a mechanical, behaviour-preserving change, which is the kind of change an agent does well and a gate can check fully. Run it in small pull requests, one directory or one catch-all file at a time, so parallel branches do not collide with a repository-wide move.

  1. Audit before you move anything. Run the audit prompt below in a fresh session, read-only. Ask for measured line counts, not estimates, and rank the findings by how often each file changes, because a large file nobody touches costs little.

  2. Approve the target layout, not the diff. The agent proposes the new tree and the name of every new file. You or the tech lead review that list: it is short, and names are where a bad split shows first.

  3. Move one unit per pull request. Use the split prompt. It forbids body changes, so the diff is moves and import updates only.

  4. Prove nothing changed. Run the gates in the next section. A pure move needs no line-by-line review when the gates pass.

  5. Write the scoped instructions. Once a feature folder exists, add its CLAUDE.md, AGENTS.md or .mdc rule with the scoped-instructions prompt.

  6. Turn the budget into an error. When the audit’s backlog is empty, switch max-lines from warn to error so the structure cannot regress.

How do you prove a restructure changed nothing?

Section titled “How do you prove a restructure changed nothing?”

You do not read a 60-file move line by line. You make the move checkable and let the checks sign off:

  • Rename-only diff. git diff -M --summary main...HEAD lists each moved file as a rename with a similarity percentage. A pure move shows high similarity on every file; a low percentage marks a file where the agent changed more than imports. A split such as utils.ts into modules shows up as create mode and delete mode instead of renames, so for a split run git diff --color-moved=zebra --color-moved-ws=allow-indentation-change main...HEAD: moved blocks render in the moved colours, and any line in the normal add or delete colour is a real change to review.
  • Same tests, same results. The type checker, linter and full test suite pass, and the test count is unchanged. A falling count means tests were lost in the move.
  • No new cycles. npx madge --circular --extensions ts,tsx src/ exits with code 1 when it finds a circular import (madge 8.0.0), so it works as a CI step as written.
  • The budget holds. max-lines runs in the same lint job as everything else.
  • Findability improved. Run the probe below in a fresh session before and after, and compare how many files the agent opened and how much context it used (/context in Claude Code, /status in Codex).

Run the probe with three questions your team asks often. If the after-run opens as many files as the before-run, the new names are not doing their job. The tech lead owns the layout conventions and approves the target tree in step 2; CI owns everything after that, and a pull request that fails a gate does not merge.

What breaks when you restructure for agents?

Section titled “What breaks when you restructure for agents?”

A scoped instruction file never loads. Codex was started at the repository root, so the package AGENTS.md below it was never read; or a Claude Code path rule did not fire because Claude wrote a new file without reading a matching one. Recovery: add a canary line to the scoped file (“When you use a rule from this file, say BILLING-RULES”), start a session the way your team does, and ask a billing question. Start Codex in the package with -C, or ask Claude to read a sibling file first.

Framework conventions break silently. File-based routers, migration folders, plugin discovery and dynamic import() with string paths depend on names and locations the type checker does not see. Recovery: list those directories as off-limits in the split prompt, search for the old path as a string after the move, and run the end-to-end suite, not only unit tests.

The split follows the line budget instead of responsibility. The agent cuts a 900-line file into three files by position, and every change now touches all three. Recovery: reject the file list in step 2 when names are numbered or generic, and ask for a split by responsibility with one sentence per file on what it owns.

A barrel quietly replaces the old catch-all. shared/index.ts re-exports everything, and the agent now reads the barrel plus every module. Recovery: delete the barrel, switch to direct imports, and rerun the audit prompt, whose step 3 flags any index file over 20 lines.

A repository-wide move collides with open branches. Every open pull request conflicts at once. Recovery: move one directory per pull request, merge each within a day, and announce moves before they land.

Generated code sits inside feature folders. The agent searches it, edits it, and the next codegen run erases the change. Recovery: move generated output to a clearly named directory and exclude it from agent search, as in code search and indexing for AI agents.