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

Qoder Free Credits Claim and Usage Management SOP

A hands-on SOP for claiming and managing Qoder's double promo: download and install (international qoder.com or China qoder.cn, across desktop, mobile, IDE, JetBrains plugin and CLI), sign up (the two editions keep separate accounts and quotas), confirm the free window works (selecting Qwen3.8-Flash in the model picker bills at a 0x coefficient, nothing to claim), then the daily 100 Credits rhythm (opens 10:00 daily, one claim per cycle, no carryover of missed days, each grant valid 30 days and stackable), usage management (check burn in the usage panel, let Qwen3.8-Flash carry routine work and save Credits for hard tasks), deduction rules (earliest-expiring credits are consumed first, in-plan before add-on packs on the same day), and a closing plan for when the window ends on September 30. UI details follow the actual client.

Sep 18, 20268 min read
Field SOP

Wiring diagram-design into Claude Code: a hands-on SOP

A hands-on SOP for wiring the diagram-design diagram skill pack into a daily coding workflow, with commands taken verbatim from the project's official README. It runs in seven steps: first what the pack does and does not solve; then a per-host install and update command table (Claude Code's /plugin marketplace add and /plugin install, Codex's codex plugin marketplace add and plugin add, the copilot plugin family for GitHub Copilot, the droid plugin family with --scope user for Factory Droid, pi install plus /reload for Pi, a subdirectory URL import for Kiro, and a directory copy or symlink for OpenCode); then the first-run gate, which stops to ask when the default skin is untouched, and brand onboarding, which reads your site for palette and fonts, maps them to semantic tokens, checks WCAG AA contrast and emits a fidelity receipt; then drawing and self-check, with three copy-paste natural language prompts, the official six criteria for it working, and self_check.py printing OK as the pass condition; then export and import, covering the four dials, the diagram-only boundary, and what a fidelity ledger looks like; and multi-client brand isolation via named profiles plus a .diagram-design marker file. The seventh section is a ten-item pitfall table with symptom and cause for each: Claude Code disables auto-update by default for third-party marketplaces, Factory Droid tracks plugins by commit rather than manifest version, Pi has no auto refresh and needs pi update --extensions, Kiro copies rather than links, OpenCode copied installs never self-update, a legacy standalone npx skills add copy will not follow the Codex marketplace, a customized style-guide.md can be overwritten by package updates, the first PNG export fails without Playwright and Chromium, readers assume exports include the full layout, and motion HTML screenshots capture an intermediate frame. The core claim: the real barrier is not installation but update paths and output boundaries, and the fact that the official README spells out update commands per host is itself the signal that cross-host skill distribution and upgrades still have no unified answer.

Sep 15, 202611 min read
Field SOP

LLaDA-Image Local Deploy SOP: Setup, Inference, Production

A five-step SOP for running Ant's open-source 6B image model LLaDA-Image: (1) environment setup with dependencies and mirror-accelerated downloads; (2) choosing among four weight variants (Base 50-step / Turbo 4-step, each in BF16 or FP8, with ModelScope for China); (3) generating the first image with minimal Base and Turbo commands; (4) advanced work - reference-image editing, text rendering, ComfyUI integration, and degradation strategies when VRAM runs short; (5) productionizing with batch queues, concurrency sizing, cost monitoring, result storage and graceful failure modes. Includes 6 pitfalls and a 10-item launch checklist, with every command copied verbatim from the official README; note the repo license is null, so confirm rights before commercial use.

Sep 9, 202611 min read