← All articles
A Codebase Map for AI Coding Agents (Real Numbers)
Every fresh AI agent session re-greps your repo from zero. A generated codebase map plus a patterns file cut tool calls 58% — real numbers and the design.

Watch an AI coding agent open your repo for the first time and you'll see the same ritual every session: glob the tree, grep for a component name, read three files, grep again. It re-derives the same orientation — where the routes live, which folder owns the hero, what the styling convention is — that it (or its sibling) already derived yesterday. Spawn subagents and it's worse: each one starts with zero context, so the exploration cost repeats per agent, not per project. This post is how I fixed that with a codebase map for AI coding agents — a generated CODEMAP.md plus a recipe file called PATTERNS.md — and an honest A/B benchmark of what it actually buys: 58% fewer tool calls, with some sharp caveats.
Why agents re-explore your repo every session
An agent's context window starts empty. Everything it knows about your repo, it buys with tool calls — list a directory, grep a symbol, read a file — and each call is a full network round-trip plus the tokens of whatever came back. For a one-off question that's fine. But orientation knowledge ("where is X?", "what handles Y?") is stable between sessions, and agents re-purchase it every time because nothing persists it.
The insight is boring and old: that's an index. Databases don't table-scan twice; repos shouldn't be re-explored twice. So I built the index as two committed files the agent reads first.
Part 1: CODEMAP.md — a generated index, never hand-written
CODEMAP.md is generated by a ~700-line Node script with zero dependencies — fs, path, child_process, nothing else, no LLM in the loop. It runs in under a second and is wired as a pre-commit hook, so the map regenerates on every commit and never drifts from the code. It covers: routes, feature modules, shared components, hooks/lib, the animation runtime, WebGL, styling lanes, package.json scripts, and env var names only (never values).
Four design constraints turned out to matter more than the section list:
1. A hard budget of ≤18,000 characters. Agent hosts commonly cap an inline tool result around ~25K characters; a map that overflows gets truncated by the platform, silently, and the agent doesn't know what it lost. So the generator enforces its own budget and truncates honestly — each section can end with a line like … +7 more (not shown), so the agent knows there's more and can fall back to grep for the tail. (I stole the budget-tiering idea from studying CodeGraph, which caps retrieval responses the same way.)
2. Every line is written in the query's register. Agents ask "where is X?" and "what handles X?" — so every line answers exactly that, one line per thing:
## Feature modules
- src/modules/hero/ — full-viewport intro, scroll-scrubbed timeline? (Hero.tsx + Hero.module.scss)
- src/modules/services/ — CMS-driven services grid (ServicesSection.tsx)
… +7 more (not shown)
3. Provenance markers. A description the script inferred (from naming, imports, folder position) carries a trailing ?; a fact it read verbatim (a script name, an export) doesn't. The agent can trust unmarked lines outright and verify ? lines before betting on them.
4. A freshness banner. The first line states the generation date, the commit, and the regenerate command — so an agent (or human) can tell at a glance whether the map is current and how to refresh it.
Part 2: PATTERNS.md — recipes the agent wrote, the script polices
The map answers "where is X." It does not answer "how do I add an X" — that's procedural knowledge. PATTERNS.md holds agent-authored recipes for the repo's recurring tasks (add a CMS-editable section, add an API route, …), with strict rules: a recipe is extracted from real code, never invented — each one must be verified against at least two real instances in the repo, names its exemplar, and states what it deliberately leaves alone:
### Add a CMS-editable section
Verified against: src/modules/services/, src/modules/faq/ (2 instances)
Mirror: src/modules/services/ — copy this module's structure exactly
1. Create src/modules/<name>/<Name>Section.tsx + <Name>.module.scss
2. Register the section type in the CMS schema and the section-renderer switch
3. Add the reader query alongside the existing section readers
Not touched: src/lib/cms/client.ts — the reader layer is generic; new sections need no changes
Recipes rot, so the codemap generator polices them: on every regen it validates every backticked path in every recipe against the tree, and stamps any recipe whose structure moved:
### Add a CMS-editable section ⚠ STALE (missing: src/modules/faq/FaqSection.tsx)
A stale stamp doesn't delete the recipe — it tells the next agent "verify before you mirror." Self-flagging beats silently wrong. (The recipe-library idea is a direct lift from Voyager's skill library: store verified procedures, retrieve them by natural-language key, instead of re-deriving them.)
The benchmark: same repo, same model, map on/off
I A/B'd this on my Next.js site template: identical questions to fresh agent sessions, same model throughout, with and without the files. Full disclosure of the limits up front — n=3 questions, a single run per cell, and I authored the questions — so read this as a strong signal, not a paper.
| Question | Setup | Tool calls | Wall time | Tokens |
|---|---|---|---|---|
| Q1 — orientation (hero file, its SCSS module, animation deps, smooth-scroll runtime, verify commands) | no map | 6 | 31.2s | 35.6K |
| CODEMAP | 1 | 8.7s | 37.5K | |
| Q2 — design ("every file to touch to add a new CMS-editable section") | no map | 14 | 95.4s | 57.4K |
| CODEMAP only | 9 | 85.5s | 61.5K | |
| CODEMAP + PATTERNS | 2 | 21.6s | 40.8K | |
| Q3 — pipeline (which scripts form the blog-cover pipeline + env names) | no map | 13 | 43.3s | 51.3K |
| CODEMAP | 4 | 40.0s | 39.7K |
Every answer was correct in every cell (Q2's answers matched file-for-file; on Q3 the map run nailed the core pipeline while the baseline dug up a few peripheral extras). Aggregate: tool calls −58%, wall time −21%, tokens roughly flat. Q2 with the patterns layer is the standout: calls −86%, time −77%, tokens −29%.
Three honest readings, because the flat token line deserves one:
- Tokens ≈ flat per single question because reading a 17.7K-char map costs tokens too — you're trading grep results for map text. The real wins are round-trips (each tool call is seconds of latency) and orchestrator flows, where you paste one relevant map section into a subagent's brief instead of letting every subagent re-explore.
- Location questions win immediately; deep design questions only win once the recipe layer exists. CODEMAP alone barely moved Q2 (14→9 calls) — it took PATTERNS to collapse it (→2).
- The map replaces exploration, not comprehension. An agent should still open the actual source for judgment work — the map tells it which files, and the
?/⚠ STALEmarkers tell it when to double-check the map itself.
Porting it: three repos, zero code changes
The test of a generator like this is the second repo. I copied the script verbatim into three repos of very different shapes — my Next.js site template, my self-hosted CMS package, and a prototype — and it needed zero code changes. The trick is existence-gated section detection: every section probes for its own evidence and reports what it finds, including nothing. The repo with no dedicated WebGL directory correctly reported none — and its PATTERNS recipes covered React Three Fiber scenes instead, because that's what its real instances were.
That's also my pitch for generating rather than hand-writing: hand-written context files (a CLAUDE.md, a wiki page) are great for rules, but their facts go stale the week you refactor. A sub-second script on a pre-commit hook can't forget to update itself.
FAQ
How is this different from a CLAUDE.md / agent instructions file?
They're complements, not competitors. An instructions file carries rules and conventions ("use SCSS modules", "never commit X") — stable, hand-written, opinionated. The codemap carries facts about the tree — volatile, generated, provenance-marked. Facts in a hand-written file rot silently; facts in a generated file regenerate on every commit.
Why not embeddings / RAG over the repo?
For this problem, a 700-line script beats a vector store on every axis I care about: zero infrastructure, zero dependencies, deterministic output, diffable in code review, and free. Semantic search earns its keep on huge polyglot monorepos; on a project-sized repo, a well-shaped 18K-char index answers the same questions with none of the moving parts.
Why not just let the agent explore — isn't that what it's for?
It costs real money and wall time, per session and per subagent, to re-derive facts that haven't changed. Q1 above is the everyday case: six tool calls and 31 seconds to learn what one map read answers in 8.7. Exploration is for questions the index can't answer — that's why the map marks its own inferences and staleness instead of pretending to be complete.
Does a codemap help human developers too?
Yes, as a side effect — it's a decent onboarding README that can't go stale. But humans weren't the design target: the char budget, the query-register lines, and the provenance markers exist because of how agents consume tool results.
If you're evaluating how a developer actually works with AI tooling in 2026, this is the kind of infrastructure I mean — the same care that goes into a well-cached Next.js data layer applied to the agent workflow itself. It's part of what you're really buying when you hire a Next.js developer: not keystrokes, but systems that stop paying the same cost twice — and it's half of what the "engineering" in creative developer means to me. The full setup — generator, hooks, recipes — travels with every project I ship through my services.


