DEV Community

Sherotree
Sherotree

Posted on

Agent-Driven Refactors: Keep the PR Reviewable

I used to ask the agent for “a clean refactor of this module” and get a 40-file PR that touched naming, folder layout, hooks, and tests in one swing. CI was green. I still couldn’t review it.

Green doesn’t mean reviewable. An agent-driven refactor is useful only if a human can answer “what changed and why?” in under ten minutes. Here’s the process I use so the model does the mechanical work without turning the PR into a archaeology dig.

The failure mode: one prompt, one mega-diff

The ask that blows up:

Refactor BillingForm and related helpers. Clean up naming, extract hooks, align with our patterns, update tests.

What lands:

  • Renames across 12 files
  • A new hooks folder
  • Test rewrites that assert the same behavior with different structure
  • A drive-by import sort and a “while I was here” type tweak

None of that is wrong in isolation. Together it is unreviewable. You can’t tell which hunk is the real refactor and which is noise. Reviewers rubber-stamp or reject the whole thing. Both waste the agent’s work.

Reviewable means three things

I won’t merge an agent refactor unless all three hold:

  1. One intent per PR — rename or extract or move or behavior change. Not a combo meal.
  2. Diff you can narrate — I can write a 3–5 bullet PR description without opening every file.
  3. Tests that prove the same contract — same public behavior, preferably same assertion names where they still fit. Silent test rewrites hide regressions.

If the agent can’t keep those three, I split the ask before I accept the branch.

Slice the refactor before you prompt

I never start with “refactor X.” I write a short plan the agent must follow:

## Refactor plan (do in order; stop after each step)
1. Rename `useBill` → `useBillingState` in place — no logic changes
2. Extract `validateLineItems` from BillingForm into `billing/validate.ts` — same signature
3. Move `formatCurrency` from utils/misc to billing/format — update imports only
4. (Optional, separate PR) Split BillingForm into Form + Summary if step 1–3 stay green

Rules:
- One git commit per step
- Do not rename and extract in the same step
- Do not “improve” unrelated types or imports
- After each step: run unit tests for billing; report pass/fail before continuing
Enter fullscreen mode Exit fullscreen mode

Then I paste that plan as the prompt body. The agent gets a sequence, not a vibe.

Force small commits (or small PRs)

Reviewability dies when everything is one commit. I ask for one commit per plan step, with messages that match the plan:

refactor(billing): rename useBill → useBillingState
refactor(billing): extract validateLineItems
refactor(billing): move formatCurrency to billing/format
Enter fullscreen mode Exit fullscreen mode

If the tool can’t commit mid-run, I still force one PR per step. Slower wall-clock, faster review. I’d rather merge three 80-line PRs than one 800-line “cleanup.”

Stacking works too: open PR1, wait for review, then PR2 on top. Agents are fine with that if you say so up front.

Give the agent a blast-radius fence

Left alone, models expand scope. I put hard fences in the prompt:

Only touch files under src/features/billing/** and their colocated tests.

Do not edit src/components/ui/**, shared utils outside billing, or CI config.

If a rename requires a file outside that fence, stop and list the paths — don’t chase them.

That stop rule matters. Without it, a rename becomes a repo-wide find-replace that pulls in stories, docs, and e2e fixtures you didn’t mean to review tonight.

Keep behavior frozen while structure moves

For pure refactors I add:

No behavior changes. Same props, same return shapes, same error messages, same network calls.

If you think behavior should change, open a note in the PR description — don’t ship it in this branch.

Then I ask for a short “behavior freeze” checklist in the PR body:

  • [ ] Public component API unchanged
  • [ ] Snapshot / contract tests unchanged (or only path updates)
  • [ ] No new feature flags
  • [ ] No copy / i18n string edits

If those boxes aren’t honest, it’s not a refactor PR anymore — and it shouldn’t pretend to be.

What I review (human checklist)

When the PR opens, I skim in this order:

  1. File list first — does it match the plan’s fence?
  2. Commit messages — one intent each?
  3. Largest hunk — is it mechanical (rename/move) or clever?
  4. Test diff — assertion text drift without a reason?
  5. rg for drive-bys — prettier/eslint mass rewrites, import resorting outside touched files

If step 1 fails, I don’t deep-read the rest. I send it back with “split to match the plan.”

Prompt template I reuse

Execute ONLY step N of the refactor plan below.
Do not start step N+1.
Stay inside the blast-radius fence.
No behavior changes.
When done: paste `git status`, `git diff --stat`, and test results.

[paste plan]
Enter fullscreen mode Exit fullscreen mode

Boring. Repeatable. The agent does the typing; I keep the narrative.

Takeaway

Agent-driven refactors fail when you optimize for “the model finished” instead of “a human can own the merge.” Slice the work, fence the blast radius, freeze behavior, and ship one intent per PR (or per commit).

The agent is fast. Review time is the scarce resource. Write prompts that protect the reviewer’s ten minutes — that’s the whole game.

Top comments (0)