Refactoring at scale
Mechanical change across many files is what agents are best at — provided you never let them decide what "equivalent" means.
A large mechanical refactor — rename a concept, change a call signature, migrate a pattern across ninety files — is tedious for you and well suited to an agent. It is also the change most likely to produce a diff too large to review, so the process matters more than usual.
Establish the safety net first
Non-negotiable before touching anything: a green check command, and tests that actually cover the behaviour being preserved. If the coverage is not there, that is a prior session (Lesson 4.6, characterisation tests). Refactoring without a net is not refactoring; it is rewriting and hoping.
Do one exemplar by hand
Convert one file yourself, or pair closely on it. Then that file becomes the specification:
src/api/users.ts has been migrated from the old client to the new one. Read it and the git diff for that file. That diff is the pattern. Apply exactly the same transformation to src/api/orders.ts. Do not improve anything else. Do not rename anything not required by the migration. Run 'npm run check' when done.
"That diff is the pattern" is much stronger than describing the transformation in prose, and it eliminates the drift you get when each file is converted from a slightly different reading of your instructions.
Batch by review capacity, not by agent capacity
The agent will happily do all ninety files in one session. Do not let it — the resulting diff is unreviewable and the last twenty files were written deep in the dumb zone. Batch into groups of five to ten files, one session and one commit each.
git commit -m "refactor(api): migrate users, orders, invoices to new client (1/9)"
Nine reviewable commits beat one enormous one, and if batch six turns out to be wrong you revert one commit rather than the whole migration.
Prefer the deterministic tool when one exists
If the change is expressible as a codemod, an IDE rename, or a sed across a known pattern, use that instead and let the agent write the codemod. A deterministic transformation applied ninety times is verifiable in a way that ninety independent generations are not.
Write a codemod (jscodeshift) for this transformation instead of editing files one by one. Run it on src/api/users.ts only and show me the diff. If it matches the exemplar diff exactly, we will run it across the rest.
Verify the invariant, not just the checks
Type checks and tests catch a lot, but the refactor-specific question is whether behaviour is unchanged. Where you can, get a stronger signal: compare build output, run both implementations against the same inputs, or diff recorded responses before and after.
Watch out
The seductive failure is the agent "improving things while it is in there". A migration diff containing unrelated cleanups is unreviewable, because you cannot separate the mechanical change from the judgement calls. Say "no other changes" in every batch, and reject batches that ignore it.
Try it
Pick a real mechanical change in your repo. Do the exemplar yourself, then run two batches. Compare the second batch’s diff against the exemplar diff line by line — drift usually shows up by the second or third file, and catching it early is the whole game.
Takeaways
- Get to a green safety net before any large refactor; build one if it is missing.
- Convert one file by hand and use that diff as the specification.
- Batch by what you can review, commit per batch, and forbid unrelated improvements.
- Prefer a codemod the agent writes over ninety independent generations.
Why is an exemplar diff a better instruction than a written description of the transformation?
Because it is a primary source and it is unambiguous. Prose descriptions leave the edges to interpretation, and each file gets converted from a slightly different reading, producing drift. A diff shows exactly what changed and what deliberately did not.
Module 04 of this course ends here
The Next Steps goes under it: specification as the actual product — grilling at depth, acceptance criteria that bite, interface-first design, and specifying data and migrations.
See what is in it →A course by Pieter Zandbergen