Forty thousand lines. No tests. Original author gone. Product still queuing features.
The obvious move is to start adding tests. I did something else first, and I'd do it that way again.
The problem with testing first
You can't write a meaningful test until you know what the current behavior is — not what it should be, what it actually does.
Write tests from assumption and you encode your assumptions. Then a refactor "passes" while breaking something real, because your test asserted the behavior you imagined rather than the one users depend on.
Legacy systems are full of behavior that looks like a bug and is load-bearing. Someone downstream is relying on it.
What I did instead: a behavior snapshot
Not tests. Prose.
I had the agent read the critical paths and write out what the code currently does — including the parts that look wrong but might be intentional.
Prompt shape:
Read the order processing path. Document actual current behavior,
including edge cases and anything that looks like a bug but may
be deliberate. Don't suggest changes. Describe only.
That last instruction matters. Without it you get a refactoring proposal instead of a description, and you can't test against a proposal.
Doing it without blowing the context window
Forty thousand lines doesn't fit in one session, and even if it did, accuracy degrades as the window fills.
Six batches, one module each:
- Subagent reads the module, returns a summary
- Summary goes into the snapshot document
- Clear context
- Next module
The subagent step is what makes this affordable — those files land in its window, not mine. My main thread accumulated six summaries, not forty thousand lines.
Took about a week. No features shipped. It felt unproductive and it was the highest-leverage week of the project.
Then the tests
With a snapshot, test-writing is fast: work from the document, case by case.
More importantly I could now classify behaviors. This one is intended, keep it. This one is a bug but something depends on it, keep it and file an issue. This one is a bug nobody uses, fix it.
That classification is impossible without the characterization step. It's the actual product of that week.
I covered critical paths only. Not chasing coverage — building enough of a net that subsequent changes could be verified automatically rather than by me reading diffs.
Why the net matters more than the tests
Once critical paths are covered, the agent can close its own loop: write, run, read the failure, fix, repeat.
Without that, the agent stops at "looks done" and you become the verification step. On an unfamiliar 40k-line codebase, that's a terrible position to be in — you're the least equipped person in the room to spot a regression.
Cost shape
That characterization week ran three to four times my normal consumption, essentially all of it in the reading phase.
Which is the general pattern: comprehension costs more than generation. When I audited a year of my logs, about half my usage turned out to be understanding code rather than writing it. Taking over a legacy system is that ratio, concentrated.
Spiky in exactly the way monthly tiers handle worst — one intensive week, then back to normal. I keep a low baseline and top up for stretches like this through Asale, a market where unused subscription capacity gets routed to people who need it, priced per million tokens.
Caveat: requests relay through another user's client, so the payload is visible at that hop. No end-to-end encryption, stated on their front page. That project was internal, so I used the company account for it — the personal setup is for open source and side projects. Worth deciding that boundary before you're mid-migration.
Has anyone found a better characterization approach than prose snapshots? Approval testing seems adjacent but I haven't tried it on something this size.
Top comments (0)