← All posts
AI-Assisted Software Development

Context Engineering for Codebases: Repo Maps, Symbol Graphs, and What the Agent Actually Reads

The quality of an AI coding agent's output depends on what it reads before it writes. Here's how to engineer the context — repo maps, symbol graphs, file selection — so the agent starts with the right information.

Craig Hoffmeyer8 min read

When a coding agent produces bad output, the instinct is to blame the model. The model is not usually the problem. The problem is what the model read before it started writing. A coding agent that reads the right files, understands the relevant patterns, and sees the constraints produces good code. The same agent, on the same task, reading the wrong files or missing critical context, produces code that looks plausible and breaks in production.

Context engineering — the practice of deliberately controlling what information goes into the model's context window — is the most important and least discussed skill in AI-assisted development. I wrote about this at the conceptual level in prompt engineering is dead, long live context engineering. This article is the developer version: the specific techniques for engineering context in a codebase so that coding agents produce better output.

What the agent actually reads

Understanding what a coding agent reads is the first step. When you give Claude Code a task, it does not read the entire codebase. The context window, even on the largest models, cannot hold a full production repository. Instead, the agent reads selectively:

The files you explicitly reference. If you say "look at src/billing/processor.ts and fix the validation bug," the agent reads that file first.

Files it discovers through search. The agent searches for relevant symbols, function names, imports, and patterns. The quality of these searches depends on the initial context — if the agent knows the right terms to search for, it finds the right files.

Files linked through imports. The agent traces imports from files it has already read, building a picture of the dependency graph around the task.

The project instructions file. CLAUDE.md or equivalent. This is read at the start of every session and sets the baseline context.

Recent conversation history. Everything you have told the agent in the current session.

The agent does not read files it does not know to look for. This is the fundamental problem. If the critical context for a task lives in a file the agent never opens, the agent will produce code that ignores that context. The code will compile, the code will often pass existing tests, and the code will be wrong in a way that only someone who knows about the missing context would catch.

Repo maps

A repo map is a structured summary of the repository that gives the agent a birds-eye view before it starts working. Instead of the agent having to discover the codebase by searching blindly, the repo map tells it where things are and how they relate.

A useful repo map includes:

Directory structure with annotations. Not just the file tree — the tree plus a one-line description of what each directory contains. "src/billing — all billing logic, Stripe integration, invoice generation." "src/auth — authentication and authorization, JWT handling, middleware."

Key files. The files that any change to a given area needs to consider. "Before changing anything in billing, read src/billing/README.md and src/billing/types.ts." These are the files the agent should always open before writing.

Conventions index. Where the team's coding conventions are demonstrated, by example. "For new API routes, follow the pattern in src/api/users/route.ts. For new database queries, follow src/db/queries/billing.ts."

Boundary rules. Which parts of the codebase should not be modified without explicit permission. "The files in src/core/ are stability-critical and should not be edited without senior review."

The repo map lives in the project instructions file (CLAUDE.md) or in a separate file that the agent reads at session start. The investment is a few hours of writing, and the return is a permanent improvement in the quality of every agent interaction.

Symbol graphs

A symbol graph is a more detailed version of a repo map — a mapping of key symbols (functions, classes, types, constants) to their locations and relationships. Where a repo map tells the agent "billing logic is in src/billing/," a symbol graph tells the agent "the processPayment function is in src/billing/processor.ts, it calls validateCard from src/billing/validation.ts and createInvoice from src/billing/invoices.ts, and it is called from src/api/checkout/route.ts."

You can build a symbol graph manually for the critical paths in your codebase, or you can generate one automatically. Several tools produce symbol graphs from TypeScript, Python, and other typed codebases. The generated graph is usually too large to put in the context window directly, but a filtered version — showing only the symbols relevant to a specific area — is highly effective.

The workflow: before handing a task to the agent, generate or select the symbol graph for the relevant area and include it in the task description. The agent reads the graph, understands the relationships, and navigates the codebase more accurately.

File selection strategies

The most direct form of context engineering is telling the agent which files to read. Three strategies I use:

Explicit file lists. For well-scoped tasks, list the files the agent should read and the files it should modify. "Read src/billing/processor.ts, src/billing/types.ts, and src/api/checkout/route.ts. Modify only src/billing/processor.ts and the test file tests/billing/processor.test.ts." This eliminates ambiguity about scope and prevents the agent from wandering into unrelated files.

Pattern-based inclusion. "Read all files matching src/billing/**/*.ts before starting." This gives the agent the full area context without listing every file. Useful when the task might touch multiple files in a directory and you want the agent to understand the full picture.

Negative constraints. "Do not read or modify any files in src/auth/ or src/core/." Negative constraints prevent the agent from pulling in context that would distract or mislead, and they protect sensitive areas of the codebase from accidental changes.

The general principle: the agent should read enough context to understand the task and not so much that it loses focus. Too little context produces code that ignores important constraints. Too much context produces code that is influenced by irrelevant patterns.

The CLAUDE.md as context anchor

The project instructions file (CLAUDE.md) is the persistent context layer. It is read at the start of every session and establishes the baseline knowledge the agent has about the codebase. I wrote about this in detail in CLAUDE.md is the new README, but the context engineering angle is worth repeating.

A strong CLAUDE.md should include:

The architecture in one paragraph. What the app does, how it is structured, what the major subsystems are.

Build and run commands. How to start the dev server, how to run tests, how to run the linter. These are the tools the agent uses and it should not have to guess.

Naming conventions. How files are named, how functions are named, how types are named. This prevents the agent from introducing inconsistent naming.

Key patterns to follow. Pointers to canonical examples of the team's preferred patterns. "For API routes, follow src/api/users/route.ts. For database queries, follow src/db/queries/billing.ts."

Things not to do. Anti-patterns, deprecated approaches, things the team is migrating away from. "Do not use the old Redux store. New state should use Zustand. See src/stores/ for examples."

Every hour spent on the CLAUDE.md saves multiples in review time and agent rework.

Dynamic context injection

For complex tasks, static context (the CLAUDE.md, a repo map) is not enough. The task needs dynamic context that changes based on what the agent discovers during execution.

This is where subagents shine. The pattern: the main agent reads the static context and starts the task. When it encounters something it does not understand, it spawns a subagent to go explore. "Subagent: read the billing module and explain how payment retries work." The subagent reads the relevant files, produces a summary, and the main agent incorporates the summary into its working context.

This is context engineering at runtime — the agent actively manages its own context window by delegating exploration to subagents and pulling back summaries rather than raw files.

Measuring context quality

How do you know whether the agent's context is good enough? Two signals:

First-pass success rate. Track how often the agent's first attempt at a task produces code that passes review without structural changes. If the rate is below 60–70%, the context is probably insufficient. Improve the repo map, add to the CLAUDE.md, or include more explicit file references in the task description.

Type of review comments. If review comments are about style and naming, the context is decent but the conventions are not documented well enough. If review comments are about architectural violations or missed patterns, the agent is not reading the right files.

Over time, the first-pass success rate climbs as the project instructions, repo map, and team's spec-writing practices improve. The payoff is cumulative.

Common mistakes

No project instructions at all. The agent starts every session with zero context about the codebase. This is the single most common mistake and the easiest to fix.

A CLAUDE.md that is out of date. A stale project instructions file is worse than none because it gives the agent incorrect information. Review and update it regularly — I recommend a monthly pass.

Over-stuffing the context. Including a 50-file repo map in the context window leaves no room for the actual task. Keep the persistent context concise and inject additional context dynamically as needed.

Assuming the agent will find what it needs. The agent is good at searching but not perfect. If you know the agent needs to read a specific file, tell it. Do not hope it discovers it on its own.

Counterpoint: the tools are getting better at this

A warning against over-engineering. The AI coding tools are improving their context management rapidly. Features like automatic codebase indexing, smarter file selection, and better search are closing the gap between "good context engineering" and "default behavior." The practices described here are high-leverage today; some of them will be automated tomorrow. Invest in the ones that are cheap (CLAUDE.md, repo maps) and be ready to let the tools handle the more complex ones as they mature.

Your next step

This week, add a repo map to your CLAUDE.md. Annotate the top-level directories with one-line descriptions. Add three "key files" entries for the areas of the codebase the team touches most often. Run your next agent task with the updated context and compare the output to what you got before. The difference is usually visible on the first task.

Where I come in

Context engineering for codebases — writing the CLAUDE.md, building the repo map, establishing the spec practices that feed good context to agents — is a standard part of my AI dev stack engagements. Usually a 2–3 day effort that permanently improves every subsequent agent interaction. Book a call if your team's agent output is inconsistent and you want a structured approach to fixing it.


Related reading: Prompt Engineering Is Dead, Long Live Context Engineering · CLAUDE.md Is the New README · Subagents, Skills, and Hooks

Want better output from your coding agents? Book a call.

Get in touch →