Migrating legacy code is the task developers dread most. It's tedious, risky, and feels like defusing a bomb where half the wires are undocumented.
AI assistants are surprisingly good at it — if you prompt them correctly. Here's the one-shot migration prompt I've refined over dozens of real migrations.
The Prompt
Here's the template I use:
Migration Task
- Source: [paste the legacy code]
- Target framework/pattern: [what you're migrating to]
-
Constraints:
- Every public function must keep its exact signature
- All existing tests must pass without modification
- Internal implementation can change freely
- Add a comment
// MIGRATED: [reason]above every changed block - If you can't migrate something safely, leave it unchanged and add
// TODO-MIGRATE: [why]
- Output: The migrated code, a migration summary, and a risk assessment per function
The key is constraint #5: giving the AI an explicit escape hatch for things it can't safely migrate.
Why One-Shot?
Multi-turn migrations go wrong fast. Each turn the model loses context about what it already changed. One-shot with clear constraints forces it to think about the whole migration at once.
Typical failure pattern:
- Turn 1: 80% correct
- Turn 2: Fixes one thing, breaks another
- Turn 3: Fixes that, changes a signature
- Turn 4: Now nothing works
A Real Example
I had a 200-line Express middleware using callbacks and req.session for auth. Needed to migrate to async/await with JWT tokens.
The prompt included the full source, all five constraints, and a note that getSession should become verifyJWT from ./auth.js.
The AI produced clean async/await code with MIGRATED comments, and flagged that tests setting req.cookies.sid would need updating (MEDIUM risk). That flag saved me an hour.
The Constraint Breakdown
| Constraint | Prevents |
|---|---|
| Keep signatures | Breaking callers you forgot about |
| Tests pass unchanged | Regressions you won't notice until CI |
| Free internal changes | Giving the AI room to actually improve things |
| MIGRATED comments | Losing track of what changed in review |
| TODO-MIGRATE escape | Half-finished migrations that silently break |
Handling Large Files
For files over 200 lines, split the migration into batches of related functions. Explicitly tell the AI not to modify or reference functions from other batches.
The Migration Checklist
After every AI migration, before committing:
- Diff check: Read every line the AI changed
- Signature audit: Verify no public signatures changed
- Test run: All existing tests must pass
- TODO-MIGRATE review: Decide if skipped items need manual work
- MIGRATED comments: Verify they're accurate
When This Works Best
- Callback to async/await conversions
- Class components to hooks (React)
- REST to GraphQL adapter layers
- ORM migrations (Sequelize to Prisma, etc.)
- Configuration format changes
When to Skip It
- No tests exist (write tests first)
- Business logic is changing (that's a rewrite, not a migration)
- File is over 500 lines (break it into modules first)
The secret to AI-assisted migration isn't a better model — it's better constraints. Give the AI clear boundaries, an escape hatch for uncertainty, and a checklist for what done looks like.
One prompt. One pass. Then verify.
Top comments (0)