# Project structure optimized for AI coding agents

URL: https://developertoolkit.ai/en/workflows/context/file-organization/
Description: Lay out files, names, tests and scoped instructions so Claude Code, Codex and Cursor load only what a task needs, then prove the layout holds in CI.

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

- 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](/en/workflows/context/codebase-indexing/) first.

## 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](/en/workflows/context/context-windows/)). The layout decides how many of those reads are wasted.

| Layout property | What the agent does with it | What goes wrong without it |
| --- | --- | --- |
| Feature folders | Lists one directory and sees the whole feature | Opens `controllers/`, `services/`, `models/` and `types/` to assemble one change |
| Small files | Reads a file whole and understands it | Reads a partial view of a large file, or reads 1,000 lines to use 20 |
| Descriptive names | Finds the file from a filename search alone | Opens `helpers.ts`, `misc.ts` and `common.ts` speculatively |
| Co-located tests | Derives the test path from the source path | Writes tests in the wrong tree, or skips them |
| Scoped instructions | Loads the billing rules only when it touches billing | Carries 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](https://code.claude.com/docs/en/tools-reference.md), 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?

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

### Group code by feature, not by file type

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

```text
# 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.

### Keep files small enough to read whole

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:

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

### Name files for what they contain

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`.

| Vague | Specific |
| --- | --- |
| `utils.ts`, `helpers.ts`, `misc.ts` | `password-hashing.ts`, `currency-format.ts`, `csv-parse.ts` |
| `service.ts` inside `notifications/` | `notification-sender.ts` |
| `types.ts` with 500 lines | `refund-types.ts`, `invoice-types.ts` |
| `index.ts` that re-exports 60 symbols | Direct imports, or a barrel under 20 lines |

### Put each test beside the code it checks

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.

### Keep barrels thin and imports acyclic

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](/en/workflows/harness/agent-ready-codebase/) rather than a second tool.

## 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**

    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/`:

    ```markdown
    ---
    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](https://code.claude.com/docs/en/memory.md), 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`.

**Codex**

    Codex finds the project root by walking up from the working directory to the first `.git` (the default `project_root_markers`), then concatenates every `AGENTS.md` from the root down to the working directory. In each directory, an `AGENTS.override.md` is used instead of `AGENTS.md`. The total is capped by `project_doc_max_bytes`, 32,768 bytes by default (`codex-rs/core/src/agents_md.rs` and `config.schema.json` at `rust-v0.157.1`).

    That means an `AGENTS.md` below your working directory is not loaded at start. To get a package's rules loaded, start Codex in that package:

    ```bash
    # terminal: loads the root AGENTS.md and src/features/billing/AGENTS.md
    codex -C src/features/billing
    ```

    Codex's bundled model instructions tell the model to look for `AGENTS.md` files when it works below the working directory, but that is the model following an instruction, not the loader guaranteeing it. Since 0.150.0, untrusted projects supply no project-level `AGENTS.md` at all, so trust the project first.

    Codex reads a plain `AGENTS.md` with no `paths` frontmatter. If your team also uses Claude Code, keep one copy of the billing rules in this `AGENTS.md` rather than a second copy in `.claude/rules/`; the next section shows how.

**Cursor**

    Cursor calls these project rules. The agent runtime Cursor ships in its `@cursor/sdk` 1.0.32 package loads `.cursor/rules/**/*.mdc` files and reads `alwaysApply`, `globs` and `description` from their frontmatter; in Cursor's rule model these select always-on, file-matched and agent-decided rules respectively (confirm in the editor, see below). [Cursor project rules](/en/cursor-ide/quick-start/project-rules/) covers the current rule types.

    ```markdown
    ---
    description: "Billing rules: money types and test placement"
    globs: "src/features/billing/**"
    alwaysApply: false
    ---

    - 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 `.cursor/rules/billing.mdc`. The frontmatter fields `alwaysApply`, `globs` and `description` decide when a rule applies (Cursor docs, checked 2026-10-12); confirm it in the editor against [project rules in Cursor](/en/cursor-ide/quick-start/project-rules/), which also has a canary check that proves a rule loaded.

### One file for Claude Code and Codex

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](https://code.claude.com/docs/en/memory.md), 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](/en/workflows/context/documentation-as-context/) covers what else belongs in instruction files, and [pruning CLAUDE.md and AGENTS.md](/en/workflows/context/pruning-context-files/) covers removing what no longer earns its tokens.

## 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.

**Copy-paste prompt: audit the project structure (read-only)**

```text
Audit this repository's file structure for how easily a coding agent can navigate it.
Do not edit any file.

Measure, do not estimate:
1. Run `git ls-files '*.ts' '*.tsx' '*.js' '*.py' | xargs wc -l | sort -rn | head -30`
   (adjust the extensions to this repo) and list every source file over 400 lines.
2. For each of those files, run `git log --oneline --since="6 months ago" -- <file> | wc -l`
   and report how many commits touched it.
3. List files whose names say nothing about their content: utils, helpers, misc, common,
   service, manager, index files with more than 20 lines.
4. List source files with no test beside them, following the pattern x.ts -> x.test.ts.

Output one table: path, lines, commits in 6 months, problem, proposed fix (the new file
names, by responsibility). Sort by commits, highest first. End with the three changes
that would help most, and stop.
```

**Copy-paste prompt: split a catch-all file without changing behaviour**

```text
Split src/utils.ts into focused modules. This is a pure move refactor.

Rules:
1. Group functions by responsibility and name each new file for what it contains
   (src/shared/currency-format.ts, not src/shared/utils-2.ts). If a function has exactly
   one caller, move it into that caller's feature folder instead of src/shared/.
2. Do not change any function body, signature, export name or behaviour.
3. Update every import to the new path. Do not leave a barrel in src/utils.ts.
4. Move each function's tests into a test file beside its new module.

Before you edit, show me the list of new files and which functions go where, and wait.
After the move, run the type checker, the linter and the full test suite, and report
the test count before and after. If any check fails or the count differs, stop and show me.
```

**Copy-paste prompt: write scoped instructions for the busiest directories**

```text
Find the three directories under src/ with the most commits in the last 90 days
(use git log --name-only). For each one, read its code and write a scoped instruction
file of at most 15 lines containing only rules an agent could not infer from the code:
invariants, the one approved helper for a common task, naming, and where tests go.

Match the instruction files this repository already uses: a CLAUDE.md in each directory
if the root has CLAUDE.md, an AGENTS.md in each directory if the root has AGENTS.md, and
.cursor/rules/<directory-name>.mdc with a globs pattern for that directory if .cursor/rules/
exists.

Every rule must cite the file that shows it (for example, "see money.ts"). Do not repeat
anything already in the root instruction file. Show me all three files before saving.
```

## 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).

**Copy-paste prompt: findability probe**

```text
Without editing anything, find where refunds are validated before they are sent to the
payment provider. Answer with the file path and function name.

Then list every file you opened or searched, in order, with one line on why. Do not
open any file you do not need for the answer.
```

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?

**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](/en/workflows/context/codebase-indexing/).

## Where to go next with project structure

- [Making a codebase agent-ready](/en/workflows/harness/agent-ready-codebase/) — Score the repository properties that let checks replace reading, including enforced module boundaries.
- [Code search and indexing](/en/workflows/context/codebase-indexing/) — How each tool finds code, and how to keep generated and vendored files out of its searches.
- [Monorepo workflows with agents](/en/workflows/build/monorepo-management/) — Project graphs, impact analysis and per-package rules when one repository holds many packages.
- [Documentation as context](/en/workflows/context/documentation-as-context/) — What belongs in CLAUDE.md, AGENTS.md and Cursor rules, and what does not.
