Field SOP
Field SOP

AI Code Refactoring SOP: A 5-Step Method for Safely Refactoring Legacy Code with Claude Code/Cursor

Refactoring legacy code with Claude Code/Cursor takes more than a "refactor this" prompt. This 5-step SOP hard-codes engineering discipline: build a test safety net first, define refactoring boundaries, give the AI precise context, execute step-by-step with manual diff review, then regression-verify. Includes 4 copy-ready prompt templates and 5 real-world pitfalls.

Published July 29, 202610 min read
<!-- ai-code-refactoring-sop | sop | AI Code Refactoring SOP -->

Legacy code. Everyone who's touched it knows the feeling.

You inherit a service nobody's touched in three years. Inside is an 800-line orderService.js, functions nested in functions, a comment reading "TODO: optimize later," timestamp 2022. You want to add a new payment channel, change two lines, tests go red, CI fails, the frontend dev pings you in the group chat. You revert, exhale, and decide never to touch this thing again.

Now you have Claude Code and Cursor, and you figure you're saved. You open Claude Code and type "refactor orderService.js for me." It churns out 600 lines of new code, functions split nicely, names tasteful, looks legit. You git diff: 412 lines changed across 7 files. One seemingly harmless variable rename also rewrote a reference in another file-somewhere that shouldn't have been touched. You don't dare merge. You revert again.

Tools leveled up, the legacy code didn't. The problem isn't that the AI is dumb. It's that you handed an engineering-discipline job to a partner with no discipline. This SOP turns that discipline into 5 steps, so AI helps you refactor legacy code without burying you and your team.


1. Step One: Build the Safety Net Before Anything Else

Michael Feathers' line in Working Effectively with Legacy Code applies more now than a decade ago: "Before you refactor, have tests." In the AI era this gets amplified-AI changes fast; without a test net catching it, the faster it changes, the faster you die.

Passing the existing tests is the floor. The first thing you do with an inherited service isn't open the AI. It's run npm test / pytest / go test ./.... Green earns you the next step. Red means fix the red first-refactoring on a broken foundation is letting AI build on quicksand.

No tests? Write characterization tests. Legacy code likely has no unit tests, and backfilling all of them is unrealistic. First add "snapshot tests" or "golden cases" for the few most critical functions-no need for pretty test names, just lock in "input X produces output Y." Claude Code, which reads project context, is well suited for this. Give it a precise prompt:

text
# Task: Add characterization tests for calcFinalPrice in orderService.js
#   DO NOT modify business code.
# Background: This function hasn't been touched in 3 years. Nobody dares
#   refactor it because there are no tests.
# Steps:
1. Read the implementation of calcFinalPrice in web/src/services/orderService.js
2. Find every call site in the project (grep calcFinalPrice)
3. Collect real input samples from call sites-cover normal/edge/error paths, at least 5
4. Write snapshot tests in web/tests/orderService.spec.js asserting current output
   (even if the output is wrong, don't fix it-just lock the behavior)
5. Run npm test -- orderService; must be all green
# Constraints:
- Do NOT modify any code in orderService.js
- Match the style of existing project tests (read any file under web/tests/)

The core of this prompt is "lock the current behavior"-even buggy behavior, lock it first. The sole goal of refactoring is "external behavior equivalence." You have to know what the original output was before you can verify nothing broke. Claude Code reads files, greps call sites, writes the test file, runs the tests, reads the error, fixes the test-all without you switching windows. This is exactly what its project-level context capability is for.

No CI? Build a manual safety net locally: npm test && npm run lint && npm run typecheck in sequence, run after every refactoring step. Put it in package.json under scripts.safety-one command, and you know immediately when something's off.


2. Step Two: Define Refactoring Boundaries-One Type of Change at a Time

The most common reason AI refactoring goes wrong isn't that the AI is dumb. It's that you let it do too much at once. "Refactor this file" packs in: split functions, extract constants, rename, move files, swap data structures. Each is safe alone; stacked together, the diff becomes an indecipherable mess nobody dares review.

One type of change per pass-this is discipline, not a suggestion. Cut refactoring into non-overlapping steps:

  • Rename: only change names, not behavior
  • Extract function: only pull a block into a function, logic unchanged
  • Replace magic numbers: only lift literals to constants
  • Move files: only relocate, content untouched
  • Change data structures: standalone step, must have test backing

Each step gets its own commit, with a message stating "what this commit does only." If a step goes red, git reset --hard HEAD~1 and have the AI redo it. Small steps aren't a slogan in the AI era-they're the only engineering means that lets you locate a rollback point in 5 minutes.

Claude Code's plan mode fits this rhythm naturally. Add "produce a plan first, don't execute" to the prompt, and it outputs a refactoring plan for you to review-then you decide which step to let it do and which to hold. Far safer than letting it charge ahead-during planning, the diff is zero, a zero-cost review moment.

Lock down the boundaries: telling the AI what not to touch matters more than telling it what to do. Refactoring boundary prompt template:

text
# Refactoring task: split calcFinalPrice in orderService.js
# Boundaries (must follow):
- Files allowed: web/src/services/orderService.js only
- Action allowed: extract pure calculation logic into helpers/price.ts,
  keep naming consistent
- Files NOT to touch: paymentService.js, couponService.js
  (these call calcFinalPrice; don't change the signature)
- Behavior NOT to touch: outputs for all existing inputs must match
  the snapshot tests
- Do NOT introduce new dependencies
# Acceptance:
- npm test all green
- git diff touches only orderService.js and the new helpers/price.ts

This prompt treats the AI like an intern who can read code but needs explicit instructions. Under vague instructions it improvises; under explicit ones it's steadier than anyone.


3. Step Three: Give the AI Precise Context

The second biggest reason AI refactoring fails: too little context. The AI doesn't know project constraints, which files are off-limits, or the project's naming style, so it falls back to generic best practices-and generic best practices can be flat wrong in your project.

Claude Code's project-level context relies on two things: CLAUDE.md and @ file references.

CLAUDE.md sits at the project root and is auto-loaded by Claude Code at the start of every session. Before refactoring, write the project's red lines in it: which test framework, naming style, which directories are legacy and untouchable, which are the new stack. Write it once, save yourself on every subsequent refactor. Claude Code's docs call CLAUDE.md a "memory file"-that's exactly what it's for.

@ references feed it specific files: @web/src/services/orderService.js @web/tests/orderService.spec.js. "Read this file" isn't enough-tell it "this is what's being refactored, this is the test," and it won't conflate test data with business code.

Cursor's multi-file editing takes a different path: @ multiple files in Composer, or use Agent mode and let it open files itself. Cursor is strong at multi-file collaborative editing and the diff review panel, weaker on long-term project memory (its .cursorrules file works like CLAUDE.md but has a thinner ecosystem). Each tool has its strengths-here's a real-world comparison:

DimensionClaude CodeCursor
Project memoryCLAUDE.md, auto-loaded.cursorrules, manual
Multi-file editsvia Read/Edit tools, steady but slownative Composer, fast
Diff reviewgit diff fallbackbuilt-in review panel, good UX
Refactoring planplan mode, zero-diff proposalAgent mode, edits as it thinks
Best forlong sessions, deep context, cross-dir big changesmid/small scope, visual diff, fast iteration

Choosing isn't either/or: use Cursor for bounded mid-sized refactors; use Claude Code's plan mode for cross-directory big refactors where you need the full picture first. Both backed by git diff. Trust neither blindly.


4. Step Four: Step-by-Step Execution + Manual Diff Review

At this point the safety net is up, boundaries are drawn, context is fed. Time to move. But "move" doesn't mean "delegate." Every step the AI changes, you review.

Single-step refactoring prompt template (copy, swap paths, ready to use):

text
# Single-step refactor: extract discount calc from calcFinalPrice
#   into a standalone function
# Context:
- File to change: @web/src/services/orderService.js
- Test file: @web/tests/orderService.spec.js
- Project rules: @CLAUDE.md
# This step ONLY:
1. Extract the discount logic at lines 120-145 of calcFinalPrice
   into a new function applyDiscount(cart, coupon)
2. applyDiscount goes in the same file; signature
   (cart: Cart, coupon: Coupon) => number
3. Don't change calcFinalPrice's external signature or return value
4. Don't touch other files
# After execution, you MUST:
- Run npm test -- orderService and paste the test output
- Paste the git diff

The key is the last two lines: have the AI run tests and paste the diff itself. Claude Code can run shell commands and read git output, so having it paste results after doing the work is far more reliable than a "done" from you.

Diff review prompt (AI changed, you review):

text
# Review this diff for potential issues
# Check specifically:
1. Whether changes exceed the boundaries I set (e.g., touched paymentService.js)
2. Whether function signatures stay unchanged externally
3. Whether new side effects were introduced (new imports, globals, console.log)
4. Whether there are "drive-by improvements" (renamed vars, tweaked unrelated code)
5. Whether types are compatible (TypeScript projects-must check)
# Output format: list suspicious points + line numbers; if none, reply "no issues"
# This is a review request, not a re-edit. Only report problems; do NOT fix them.

The last line matters. AI defaults to "fix on sight," so by the time you review it has produced another version and you don't know which to review. Have it report only, hands off. The decision is yours.

Run tests every step, don't batch. If the AI changes three steps and you run tests once, and step 2 broke something, you can't tell whether it was step 2 or step 3-you have to roll back two. One step, one run; red means git stash or git reset immediately. Localization cost is near zero.


5. Step Five: Regression Verification + Cleanup

The last step of the 5-step method is the most skipped, because by the time you've done the first four you're tired and want to merge. That's exactly when things blow up.

Full regression: run the entire test suite, not just the part you refactored. Legacy code's dependency graph usually exceeds what you assume-orderService changes one signature, and invoiceService downstream calls it in a corner case the unit tests don't cover. Only the integration tests surface that. npm test all green, CI all green, then proceed.

Manually verify critical paths: what automation can't cover, like the "place order-pay-issue ticket" chain, click through by hand. No matter how thorough the tests, they don't replace a human eye running the real flow once.

Clean up the AI's leftovers. After a refactor, AI leaves a pile of things to clean:

  • Debug console.log / print / fmt.Println
  • Commented-out old code (it didn't dare delete, so it commented)
  • Unused imports
  • Temp variable names (newVar2, temp_result)
  • Self-appointed JSDoc / docstrings whose content may be wrong

Write "refactor" in the commit message, not "fix" or "feat." That way git log shows at a glance which commits are behavior changes and which are structural. When debugging an issue, skipping refactor commits saves a lot of time.

Finally, leave a "rollback plan": keep the refactoring PR standalone, don't mix it with feature changes in one PR. If something goes wrong in production after merge, git revert <refactor commit> rolls it back without touching other changes. This discipline matters more than any AI tool.


6. Five Real Pitfalls

These 5 pitfalls are all from real projects, not hypothetical.

Pitfall 1: Letting the AI change too much at once, a big-bang diff nobody can review

Once told Cursor's Agent mode to "clean up this controller." It churned through 8 files and 600 lines in one go. The PR looked green, nobody dared approve, and we reverted and started over. Fix: cap a single task at "one file, one type of change, diff under 30 lines." Anything more, split it.

Pitfall 2: Skipping the test net, finding the breakage a week later

Skipped step one and had Claude Code refactor an untested utility function. It looked fine, merged it. A week later a user reports "the price is wrong." Tracing back, the AI had changed Math.round to Math.floor because "floor is more reasonable." No malice, but without a test catching it, this "reasonable but not behavior-equivalent" change is a bug. Fix: even 3 characterization test cases beat going bare by 100x.

Pitfall 3: Trusting the AI's "tests passed"-it never ran them

Claude Code does run tests for you. But when you ask it to "confirm tests passed," it sometimes replies "tests passed" by inferring they should pass, without actually running them-especially when the context window is tight, late in a long session, it slacks off. Fix: make it paste the raw output of the test command. Seeing PASS / FAIL strings counts. No raw output, no deal.

Pitfall 4: Cross-file cascade edits out of control

Cursor's multi-file editing is a double-edged sword. You have it change one function in file A, and it follows the import chain rewriting B, C, and D too, citing "consistency." Good intentions, but when you review, the A changes were expected and B/C/D are complete surprises. Fix: write explicitly in the prompt "files allowed: [list]; do not change other files even if related."

Pitfall 5: Refactoring mixed with business logic changes, behavior not equivalent

This is the most insidious pitfall. While refactoring, the AI spots "obviously buggy" logic and fixes it on the spot. Refactoring and bug-fixing are two things; mixing them in one PR means you can't tell which line is structural and which is behavioral during review, and you can't roll back selectively. Fix: write into the prompt "behavior equivalence principle: outputs for all existing inputs must match pre-refactor; log found bugs as separate issues, don't fix them in the refactor." Michael Feathers' iron law still applies in the AI era.


7. Don't Let AI Carry Your Discipline

Tools are only as strong as your discipline is loose. Claude Code's plan mode and Cursor's diff panel are amplifiers for people who already have discipline. Give it clear boundaries, precise context, a run-every-step rhythm, and it can safely turn three-year-old untouchable legacy code into something maintainable. Cut corners with a one-liner "clean it up" and you get a 600-line PR nobody dares merge, ending in another revert.

The 5-step method looks verbose, but every step saves time: the test net saves debugging time afterward, boundaries save review time, stepwise execution saves rollback-location time. Sharpening the axe doesn't waste chopping time-the old saying still holds in the AI era.

Next time you see that 800-line orderService.js, don't sigh. Set up the test net, draw the boundaries, give the AI precise context, go one step at a time, review each step, then regression-verify. You'll find you're braver about touching it than you thought.


References

This article is AI-assisted and human-edited. Last updated: 2026-07-29

FAQ

The legacy code has no tests, and writing them is too expensive-what do I do?
Start with characterization tests that lock current behavior, not pretty assertions. Three to five cases covering key paths will catch most AI refactoring disasters.
Claude Code or Cursor for refactoring?
For cross-directory big changes where you need the full picture first, use Claude Code's plan mode. For mid-sized edits where you want a visual diff panel, use Cursor. Both backed by git diff.
Can I trust the AI when it says "tests passed"?
Not fully. Ask for the raw test command output and look for the PASS string. Late in long sessions, the AI occasionally reports pass by inference; no raw output means it didn't run.
I found an obvious bug while refactoring-should I fix it on the spot?
No. Refactoring and bug-fixing must stay separate; mixing them in one PR makes review and rollback impossible. Log an issue, finish the refactor, then address the bug.
Which of the five steps gets skimped most often?
Step five, regression verification. By then you're tired and want to merge, but that's exactly when things break. Manually walking through the critical path beats any automation.

Related

Field SOP

AI Digital Human Creation SOP: A Repeatable Workflow from Script to Final Cut

Breaks AI digital human creation into a six-step repeatable workflow: pick the tool by use case (HeyGen/D-ID/Synthesia/Colossyan/DeepBrain plus China's Tencent Zhiying/Guiji Intelligent), write the talking-head script (with prompt template), pick or customize the avatar, lock the voice before driving lip-sync, post-process subtitles/editing/compliance, and publish with platform adaptation. Includes 5 pitfalls (avatar licensing/lip-sync drift/multilingual voice/long-video cost/compliance labels) and 5 FAQs. Representative workflow, not a single-tool hands-on test; features subject to official sites.

Aug 7, 20268 min read
Field SOP

Self-Hosting block/buzz: A Deployment SOP from Docker to Agent Onboarding

A full self-hosting SOP for block/buzz (paired with the buzz-hive-mind hotspot piece): local dev stack (just setup/build/dev) plus production single-node (deploy/compose Docker, Postgres/Redis/MinIO) plus configuration (.env: RELAY_URL/BUZZ_RELAY_PRIVATE_KEY/RELAY_OWNER_PUBKEY) plus agent onboarding (Nostr keypair NIP-98 signing, buzz-admin manages members) plus closed relay plus 5 FAQ. All deployment commands are sourced from README/compose/.env/CLI/ARCHITECTURE, nothing fabricated.

Aug 6, 20269 min read
Field SOP

Building an AI Agent Workflow in n8n: A Deployment and Pitfall SOP

A full SOP for building a tool-calling AI agent workflow inside the n8n canvas: one-command Docker self-host deployment, AI Agent node four-piece anatomy (Language Model, Memory, Tools, System Prompt), step-by-step build (pick trigger, configure node, add tools, output, test and publish), five pitfalls (amnesia from missing Memory, hardcoded API keys, over-engineering, context drift, data format mismatch) plus 5 FAQ. Node parameters per n8n official docs; gives config logic, no fabricated full JSON.

Aug 6, 20269 min read