Home AI Coding Assistants Making an AI Pair Work on the Codebase Everyone Hates

Making an AI Pair Work on the Codebase Everyone Hates

Concrete techniques for getting reliable help from AI assistants inside big, inconsistent, poorly documented legacy systems.

By Sam Ortega, a developer-productivity lead · Published 18 June 2026 · 10 min read · Reviewed against our editorial standards

ADVERTISEMENT

AI assistants are seductive on empty repos and disappointing on the codebase that actually pays your salary: 600 files, three coding styles from three eras, a test suite that takes eleven minutes and fails two tests for reasons nobody remembers. The tools do not fail here because the models are weak. They fail because the codebase does not fit in the context window and half of it contradicts the other half. Everything below is about closing that gap.

The core constraint: the model only knows what it retrieves

An assistant working your monolith sees a slice. Cursor and Copilot index and retrieve chunks; Claude Code and agentic tools read files on demand by grepping and opening them. In both cases the failure mode is the same — the model confidently uses a pattern it found in one corner while your team abandoned that pattern two years ago in favor of another. Your job is to control what it retrieves and to make the good patterns easy to find.

Point it at the right file before you ask

The single highest-leverage habit on a big repo is giving the assistant an anchor. Do not say "add caching to the user service." Say "look at how src/services/billing.ts uses the CacheClient wrapper, then add the same pattern to user.ts." You have replaced a search over 600 files with a search over one, and you have told it which of your three competing conventions is the current one.

# Weak prompt on a messy repo:
"Add retit logic to the API calls"

# Strong prompt: anchor + boundary + the gotcha
"src/net/http.ts already has withRetry(). Wrap the three
calls in src/sync/pull.ts with it. Do NOT add a new retry
helper. The old src/legacy/api.js has its own retry, ignore it."

Make the repo legible to a machine

Most large codebases were documented for humans who already have the context, which is to say barely. A few artifacts pay for themselves immediately because the assistant reads them on every task.

# A CLAUDE.md that actually helps on a messy repo
## Commands
build: make build   test: make test PKG=./internal/...
lint: make lint (must pass, CI blocks on it)

## Landmines
- internal/legacy/* is frozen. Read for behavior, never extend.
- Two HTTP clients exist. Use internal/httpx. The one in
  pkg/oldclient is being deleted.
- Migrations are hand-written in db/migrations, NOT generated.
- Anything under gen/ is protobuf output. Don't edit.

Shrink the task before you hand it over

Agents degrade as the surface area grows. A request that touches twelve files across two subsystems will produce a plausible-looking diff that is wrong in three places you will not notice until CI. The fix is to do the decomposition yourself, which is work you were going to do anyway.

Split a big change into a sequence where each step is independently verifiable: first "add the new function and a unit test, change no callers," then "migrate callers in module A," then module B. After each step you run the tests. When the assistant goes off the rails, you are one small diff from green instead of untangling a sprawling one. This also keeps each step inside a context budget the model can actually hold.

Give it a fast feedback loop or it is guessing

An agent that can run your tests is a fundamentally different collaborator from one that cannot. If it can execute make test and read the failure, it will iterate to green on its own. If it cannot, it hands you code it never ran. On a large repo the test suite is often too slow for this loop, so invest in making a targeted subset runnable:

# Give the agent a scoped, fast command it can loop on
$ pytest tests/orders/ -x -q        # not the whole 11-min suite
$ go test ./internal/orders/...       # just the package touched
$ pnpm test --filter=@app/web -- --changed

Tell it which command corresponds to the code it is editing. A tight edit-run-read loop over one package beats a brilliant model with no way to check its work.

Use the assistant to understand before you use it to change

The most reliable win on an unfamiliar messy codebase is not code generation, it is orientation. Before touching anything, ask the assistant to trace a path: "walk me through what happens from the POST /checkout handler to where the payment is captured, listing each file." On a large repo an agentic tool will grep and open its way through the call chain in a minute, work that would cost you an hour of jumping through definitions. You review the trace, correct its misreadings, and now both of you have the map. Read-only exploration is where these tools are least likely to hurt you and most likely to save real time.

Guard the parts that are load-bearing and undertested

Messy codebases have regions where a subtle change causes a production incident and no test catches it: money math, auth, data migrations, anything touching a queue. Treat AI output in these zones as a junior's first draft that gets extra scrutiny, and say so in your rules. A blunt instruction works:

## High-risk zones (extra review, propose before editing)
- billing/ : never change rounding or currency handling
  without flagging it explicitly in your summary
- auth/    : do not touch token validation logic
- migrations/ : write forward-only, reversible; show me the SQL

The point is not distrust for its own sake. It is that the model has no idea which of your files will page someone at 3am, and only you do.

What still will not work, and that is fine

Some things stay manual. Sweeping architectural changes where the right answer depends on tribal knowledge and a roadmap in someone's head. Reconciling two subsystems that disagree on a domain concept. Anything where the hard part is deciding what to build, not typing it. The assistant is a fast, tireless, slightly unreliable collaborator who has read every file but understands none of your history. Used that way — anchored, scoped, given a way to check itself, and kept away from the parts that bite — it turns the codebase everyone hates into one that is merely large. That is a real improvement, and it is available today without pretending the tool is smarter than it is.

legacy-codecontextworkflowrefactoring

A note on shelf life. AI products change fast. This guide deliberately focuses on the parts that stay true — how to judge a tool, what the trade-offs are — rather than ranking products that will have changed by the time you read it. Prices and feature claims should always be checked against the provider before you rely on them.