I Let AI Refactor 47 Files in 11 Minutes — Here's What Broke (And How to Avoid It)
I Let AI Refactor 47 Files in 11 Minutes — Here's What Broke (And How to Avoid It)
Last week I refactored 47 files in a legacy codebase. Took me... okay, I'll be honest.
11 minutes.
Not kidding. Cursor's multi-file editing compressed two days of work into a single coffee break. But here's the thing — it wasn't nearly as straightforward as it sounds. I stepped on enough landmines to fill a small handbook. Let me walk you through the mess so you don't repeat my mistakes.
The Afternoon I Almost Threw My Keyboard Out the Window
November last year. Next.js 14 project. The ask sounded dead simple: swap every fetch call across all API routes with our internal safeFetch wrapper, and standardize the error handling while we're at it.
My boss figured two hours. Tops.
I opened the codebase. Forty-two route files. Each one had fetch appearing anywhere from 3 to 15 times. Some nested three layers deep in .then() chains. Others using async/await. And a few... look, I stared at one file for ten minutes before realizing the previous developer had copy-pasted it from StackOverflow and clearly never understood what it did.
That moment reminded me of the Matrix code waterfall. Except I wasn't Neo. I was the guy about to throw up.
Edit Mode vs Find-and-Replace: Not Even Close
Wait — I need to correct something here. A lot of people think Cursor is just a fancy Cmd+Shift+F. I used to think that too. Until the day I globally replaced user_id with userId and accidentally nuked the database column names in our Prisma schema. Our DBA chased me for three days.
Dumb. So dumb.
Cursor's actual superpower isn't replacement — it's that it kinda understands what you're trying to do.
Here's the difference.
Regular find-and-replace:
Replace A with B → Every "A" in every file becomes "B"
Cursor edit mode:
In API route files, swap fetch for safeFetch.
Keep parameter structures intact. Add Sentry reporting to catch blocks.
One's a regex engine. The other... honestly, it's like an intern who's actually read your code. Occasionally does something stupid, but at least understands context.
Make It Understand Your Intent First
My first attempt was embarrassingly naive. I just said "replace fetch with safeFetch."
It did exactly that. Then it discovered safeFetch itself contained fetch calls. So it replaced those too. Infinite recursion. Facepalm.
Don't laugh. You'll do this too eventually.
The right approach:
- Manually refactor one file first. Let Cursor see what you want.
- Cmd+K to open the command palette. Write clear instructions.
- Define hard boundaries: "Only touch .ts files under src/app/api. Nothing else."
Here's the prompt template I use now:
Using the changes I just made in users/route.ts as reference, refactor all fetch calls in the api directory's route files to use safeFetch. Keep parameter structures and type annotations identical. Add captureException to every catch block.
Break that down and it's three things:
- Reference sample (the file you manually fixed)
- Scope (path or glob pattern)
- Boundaries (what stays untouched, what must be added)
Miss one of these, and your failure rate doubles. I've tested this. Extensively. Against my will.
Batch It. Don't Get Greedy.
One time I got cocky. Asked Cursor to process 60+ component files in one shot, migrating state management from useState to useReducer.
It churned through them. I watched, mesmerized.
Ran the build. Forty-three errors.
Some reducer type definitions didn't match. Dispatch calls made zero sense. And one file? It just skipped it entirely. I went to look at that code and fell silent. I'd written it three years ago — a 200-line useEffect with seven dependencies in the array. Can't blame the AI. I couldn't understand it either.
Lessons carved into my soul:
- 15-20 files per batch. Then
git add -pimmediately. - Run tests between batches. Don't wait until everything's "done."
- Group by complexity. Let it handle the simple stuff first. Tackle the monsters yourself.
This sounds conservative. It's actually way faster than the alternative — because you're not spending half a day debugging. Took me until Chinese New Year 2024 (working through the holiday, naturally) to figure that out.
Give It a Map
This is the single biggest accuracy booster. Cursor treats each file like an island by default. But real projects? Files reference each other constantly. Change an exported function signature, and every import needs updating.
My solution: drop a CONTEXT.md in the project root.
# Project Context
- Components use `@/components/xxx` imports
- API functions in `@/utils/api.ts`, wrapped with safeFetch
- Type definitions under `@/types/`, separate files for component Props
- Error handling: try/catch + captureException
- Node version 20.11.1, avoid bleeding-edge syntax
Before every batch edit, I make it read this first:
Read CONTEXT.md, then refactor all fetch calls in the api directory to safeFetch
Accuracy jumped from ~70% to over 90%. It's like giving someone a map instead of making them cross a river blindfolded.
Diff Is Your Lifeline
Real failure story incoming.
December 19th last year. Refactored 12 files, glanced at the changes, looked fine. Committed, pushed, deployed. Two hours later, customer support Slack channel exploded — payment flow was dead. Cursor had flipped a >= to < in a conditional. I'd skimmed the diff too fast to catch it.
That bug cost us... roughly 200-something orders. My manager didn't yell at me. The silence was worse.
My process now is non-negotiable:
- Go through every file with Cursor's built-in diff (not git diff, it's too hard on the eyes)
- Obsessively check logic conditions, type conversions, and regex — these are the danger zones
- Make the AI summarize what it changed
I ask for a change summary like this:
Summarize the changes you just made, ranked by risk. Flag anything that could affect business logic.
It confesses. Sometimes brutally — "I simplified error handling in 3 files to just return null, which may lose error context."
Yeah... this part is actually kind of messed up. It knows something's questionable but does it anyway. Won't mention it unless you ask. So ask.
Don't Let It Touch Code You Don't Understand
Sounds obvious, right?
Too easy to violate.
Last month I had Cursor refactor all utils functions in a directory, converting functional patterns to modern syntax. It modified a recursive function. I glanced at it, thought "looks fine," moved on.
Later, an edge case hit production. I traced it back to that recursive function. Cursor had removed the tail-call optimization. Deep nesting threw RangeError: Maximum call stack size exceeded.
I didn't fully understand that recursive logic, and I let AI modify it anyway.
My rule now: if I can't explain a file's logic clearly to myself, AI doesn't touch it. Either I figure it out first, or I mark it for manual handling. This rule has saved me multiple times.
Real Numbers
Enough storytelling. Here's the data.
Same task (migrating error handling across 40+ API routes), three different approaches:
| Method | Time | Bugs | Emotional State |
|---|
| Pure manual | 6 hours | 3 | Considering career change |
|---|
| Blind AI full-send | 20 minutes | 17 | Definitely changing careers |
|---|
| Batched + review + context | 45 minutes | 2 | Actually felt competent |
|---|
Cael Lee
Full-stack developer with 8+ years of experience. Currently building AI-powered developer tools. I've tested 20+ AI API providers and coding assistants.