Multi-File Editing and Refactoring
Multi-File Editing and Refactoring
Real-world development tasks rarely involve a single file. Renaming a component means updating the component file, every file that imports it, and possibly tests and documentation. Updating an API means changing the route handler, the client-side fetch call, the TypeScript types, and any tests. Adding a feature across a layered architecture touches the database layer, the service layer, and the UI layer simultaneously.
Before AI editors, this kind of cross-cutting change required either a project-wide find-and-replace (fragile) or careful manual coordination across a dozen open files (tedious). Cursor's Agent mode and Composer are built specifically for these scenarios — coordinated, multi-file edits driven by a single natural-language request.
What You'll Learn
- How Agent mode and Composer enable multi-file edits
- How to request changes across multiple files in plain English
- Reviewing multi-file diffs before accepting changes
- Common multi-file tasks and how to approach them
- Using
@-mentions to scope multi-file edits precisely - Working with checkpoints and reverting changes
- Best practices for large refactors
Agent Mode vs. Composer: What's the Difference?
Cursor has two interfaces for AI-driven editing: the Chat panel and Composer. Both can make multi-file changes, but they are optimized for different workflows.
Agent Mode (Chat Panel)
Agent mode runs inside the Chat panel and is activated when you select "Agent" from the mode dropdown (or set it as your default). In this mode, Cursor gives the AI access to tools: it can read files, write files, run terminal commands, and search your codebase. The conversation style is iterative — you describe what you want, the AI makes changes, you review and continue the conversation.
Agent mode is well-suited for:
- Exploratory tasks where you want the AI to figure out which files need changing
- Tasks that require running commands (installing a package, running tests, checking output)
- Complex changes where you want to ask follow-up questions mid-task
Composer
Composer is a dedicated multi-file editing interface, typically opened with Cmd+I (macOS) or Ctrl+I (Windows/Linux). It presents a diff view for all the files it plans to change, which you review and accept or reject before anything is written to disk.
Composer is well-suited for:
- Well-defined refactors where you know exactly what you want
- Changes where you want to see the full diff before committing to anything
- Tasks like component renames, API updates, or adding a feature across layers
In practice, many developers use both: Composer for clean, bounded changes and Agent mode for exploratory or iterative work.
How to Request Multi-File Changes
The key to effective multi-file requests is being specific about what should change and providing the right context. Vague requests produce vague results.
A Structure That Works
A useful structure for multi-file requests:
[Context about the current state]
[What you want to change]
[Any constraints or conventions to follow]
For example:
@components/Button.tsx @components/Card.tsx @components/Modal.tsx
These components all use inline style objects for sizing. Refactor
them to use Tailwind utility classes instead. Follow the same
pattern used in @components/Badge.tsx which has already been
migrated.
Or using @codebase when you are not sure which files are affected:
@codebase I want to rename the UserProfile component to
AccountProfile. Update the component file, all imports, and
any tests that reference it.
Requesting vs. Confirming
For significant refactors, it is often worth asking Cursor to describe its plan before making changes:
@codebase I want to migrate all API calls from fetch() to Axios.
Before making any changes, list every file you would need to
modify and describe what change each one requires.
Reviewing the plan first catches misunderstandings before they turn into a large diff to unpick.
Reviewing Multi-File Diffs
After Cursor generates changes across multiple files, you need to review them before accepting. This is where discipline pays off — accepting changes blindly is how subtle bugs get introduced.
In Composer
Composer shows a unified diff view for all changed files. You can:
- Click each file to see exactly what changed
- Accept or reject individual files (not just all-or-nothing)
- Click "Accept All" once you are satisfied, which writes all changes to disk
- Click "Reject All" to discard everything
Review every file in the diff, even files you did not expect to be changed. Unexpected changes are sometimes correct (Cursor spotted something you missed) and sometimes wrong (Cursor over-applied a pattern). Either way, you want to know.
In Agent Mode
Agent mode applies changes incrementally as the conversation progresses. After each file is modified, the change appears in the editor with standard inline diff highlighting (green for additions, red for removals). You can:
- Review each change as it is applied
- Ask follow-up questions: "Why did you also change the test file?"
- Ask for corrections: "The change to utils/api.ts looks wrong — revert just that file and try again"
What to Look for in a Multi-File Diff
- Correct intent: Does each change do what you asked?
- Consistency: If the change is a rename, is every reference updated?
- Side effects: Did Cursor change anything you did not ask it to?
- Test coverage: Were tests updated to reflect the new behavior?
- Type correctness: For TypeScript projects, do the type signatures still match?
If anything looks wrong, describe the problem and ask Cursor to fix it before accepting.
Common Multi-File Tasks
Renaming a Component
Component renames are one of the most common multi-file tasks. A rename typically involves:
- The component file itself (file name + export name)
- Every file that imports the component
- Any tests that reference the old name
- Possibly CSS module files or style files with the component name
@codebase Rename the component UserCard to MemberCard. This
includes the file itself (UserCard.tsx → MemberCard.tsx), the
component function name, all imports, and all test references.
Preserve all existing props and logic.
Updating an API and Its Callers
When you change an API endpoint — adding a required field, changing a response shape, or updating the route path — every caller needs to update simultaneously.
@api/users/route.ts @hooks/useUser.ts @components/UserSettings.tsx
The API response now includes a `preferences` object alongside
the existing `profile` object. Update the route handler to
include it, the useUser hook to return it, and the
UserSettings component to display the new preferences fields.
Adding a Feature Across Layers
A new feature often needs to be wired through multiple architectural layers: database, service, API, and UI.
@codebase I want to add a "featured" boolean flag to products.
1. Add the field to the database schema (schema.ts)
2. Update the Prisma/Drizzle queries to include it in reads and writes
3. Update the API route response types to include it
4. Add a toggle to the product admin form
5. Display a "Featured" badge on the product card in the UI
Reference any existing boolean flag implementations for
the pattern to follow.
Breaking the request into numbered steps improves reliability. The AI follows the steps in order and is less likely to miss one.
Updating Imports After Reorganizing Files
When you move files to a new directory structure, all imports break. Instead of fixing them manually:
@codebase I just moved all the utility files from src/utils/
to src/lib/utils/. Update every import statement in the
codebase that references the old path.
Adding a New Required Parameter to a Function
When a function signature changes, every call site needs updating:
@codebase I'm adding a required `tenantId: string` parameter
to the `createUser` function in src/services/UserService.ts.
Find every call to createUser and add the appropriate tenantId
argument. The tenantId comes from the request context in API
routes and from the session in server components.
Using @-Mentions to Scope Multi-File Edits
@-mentions are not just for giving context — they also help scope multi-file edits precisely. When you include specific files in a multi-file request, you are telling Cursor: "These are the files in play. Make the changes here."
This prevents Cursor from making changes to files you did not intend to touch:
# Too open-ended — Cursor might change many files you didn't expect
@codebase Update the error handling to use the new AppError class.
# Better scoped — changes are limited to these three files
@services/OrderService.ts @services/PaymentService.ts @services/EmailService.ts
Update the error handling in these three service files to use
the AppError class from @utils/errors.ts instead of throwing
plain Error objects.
A practical approach: start with @codebase to discover which files are affected, then use targeted @Files mentions for the actual changes.
Checkpoints and Reverting Changes
Before starting a large refactor, protect yourself with two safety mechanisms: git commits and Cursor's checkpoint system.
Commit Before You Start
The simplest safety net is a git commit before any large AI-driven change:
git add -A && git commit -m "WIP: before Cursor refactor of auth system"
If the refactor goes sideways, git checkout . restores everything to the pre-refactor state. No Cursor-specific features required.
Cursor Checkpoints
Cursor automatically creates checkpoints during Agent mode sessions. A checkpoint captures the state of all open files at a point in time. If a sequence of changes makes things worse, you can restore to a checkpoint without losing everything in the conversation.
To access checkpoints: look for the checkpoint icon in the Agent mode chat history. Each checkpoint corresponds to a point in the session where files were stable.
Reverting Individual Files
If you want to undo changes to one file without reverting everything:
In Composer: click "Reject" on just that file before accepting the rest.
After accepting: use git to restore that file specifically:
git checkout HEAD -- src/components/OrderForm.tsx
Or in VS Code's Source Control panel: right-click the file and choose "Discard Changes".
When to Stop and Start Fresh
Sometimes a multi-file refactor diverges from what you wanted. Signs that it is time to stop and start fresh:
- The diff is growing but the changes look less and less correct
- The AI is fixing problems it created in earlier steps
- The conversation history is so long that each response repeats the same mistakes
When this happens: reject all changes, commit a clean state, and start a new Composer or Agent session with a more focused request.
Best Practices for Large Refactors
Start Small, Then Scale
For a large refactor, do not start by asking for everything at once. Start with one representative file:
@src/services/UserService.ts
Refactor this file to use the Repository pattern. Show me how
it should look. Do not change any other files yet.
Review the result. If it looks right, ask Cursor to apply the same pattern to the remaining service files. If it is wrong, correct it on one file before propagating the mistake across the whole codebase.
Use Tests as a Safety Net
If your project has tests, run them after each batch of changes:
@codebase Rename UserProfile to AccountProfile across the entire
codebase. After making the changes, remind me to run `npm test`
to verify nothing broke.
Having tests that pass before and after a refactor is far more reassuring than manually reviewing every diff line.
Be Explicit About What Should Not Change
When doing a focused refactor, tell Cursor what is out of scope:
@services/ProductService.ts
Refactor the data fetching to use React Query. Do NOT change
the component's props interface or the business logic inside
the service methods — only the data fetching layer.
Explicitly stating boundaries prevents Cursor from making "improvements" you did not ask for.
Break Large Tasks Into Phases
For refactors that touch dozens of files, break them into phases and commit after each:
- Phase 1: Update the core module (single file, careful review)
- Phase 2: Update direct callers (a few files, focused diff)
- Phase 3: Update tests (standalone, easy to verify)
- Phase 4: Update documentation (low risk)
Committing after each phase means you can always roll back to a clean intermediate state if a later phase goes wrong.
Key Takeaways
- Agent mode (Chat panel with AI tools) and Composer both support multi-file edits. Composer excels at bounded, well-defined changes with a full diff review. Agent mode excels at exploratory, iterative tasks.
- Effective multi-file requests describe the current state, the desired change, and any relevant constraints. Including a reference example (a file that already follows the target pattern) dramatically improves results.
- Always review every file in the multi-file diff — including files you did not expect to change.
- Common multi-file tasks include component renames, API updates, adding features across layers, fixing imports after reorganization, and propagating new function parameters to all call sites.
- Use
@-mentions to scope edits to specific files, preventing unintended changes to the broader codebase. - Commit before large refactors. Use Cursor's checkpoints during Agent sessions. Know how to revert individual files with git.
- For large refactors: start with one representative file, verify the pattern is correct, then scale to the rest. Use tests as a safety net between phases.
Quiz
Discussion
Sign in to join the discussion.

