Migration Patterns
How you structure a migration in Tern depends on what you’re migrating. Here are three common patterns.
Complex Transformation: One Task, Many Steps
Example: Enzyme to React Testing Library
You’re converting 500 test files from Enzyme to React Testing Library. The syntax conversion is straightforward, but preserving test intent is hard. Each file needs the same multi-step process.
Structure: 1 task with 6-8 step instructions
# Gather Context
Find at {base_commit}:
1. Component source file
2. Original test file
3. A migrated test file (as example)
4. Helper functions used
5. Test intent (what each test verifies)
## Store
Cache this context for later steps.
# Migrate Test
Convert Enzyme to RTL using cached context.
- `mountWithTheme(<Component />)` → `renderWithTheme(<Component />)`
- Remove Enzyme imports, add RTL imports
## Validation
run: ! grep -q "mountWithTheme" {file}
# Run Tests
Get tests passing.
## Validation
run: CI=true yarn test {file}
# Verify Intent
Compare migrated test against original intent from step 1.
Ensure all assertions still verify the same behavior.What it looks like to run:
- Select files: All 500 Enzyme tests (or start with 10 to iterate)
- Run: Grid shows 500 files × 6 steps = 3,000 cells filling in
- Results: Maybe 450 succeed, 50 fail at different steps
- Iterate: Read failures, improve instructions, re-run just the 50
- Repeat: Until success rate is high enough
Why this pattern: Complex transformations need context gathering, transformation, and validation. One task keeps the logic together. Multiple steps let you see where failures happen and validate incrementally.
Breaking Changes: Many Tasks
Example: Vue 3 Migration
Upgrading Vue 2 → 3 means upgrading 5-30 packages that all have breaking changes. Each package has its own migration requirements. Your codebase is fast-moving - changes merge daily.
Structure: 1 task per breaking change per package
Migration: Vue 3
├── Upgrade Vue Core
│ ├── Migrate global API (Vue.config → createApp)
│ ├── Update v-model syntax
│ └── Remove filters
├── Upgrade Vue Router
│ ├── Update router.push syntax
│ └── Migrate navigation guards
├── Upgrade Vuex
│ ├── Update store creation
│ └── Migrate dynamic modules
└── Update component patterns
├── Remove $listeners
└── Update $attrs behaviorEach task has:
- Specific search query (finds all uses of that API)
- Focused instructions (just that one breaking change)
- Clear validation (tests still pass for that change)
What it looks like to run:
- Build plan: AI chat creates this task hierarchy by reading Vue 3 docs
- Run incrementally: Execute “Migrate global API” first, see what breaks
- Adapt to reality: Merge main → searches re-run → see new code that needs migrating
- Re-run when ready: After merging new features, re-apply the full migration plan
- Final pass: AI reviews everything for misalignments between changes
Why this pattern: Large migrations with many independent changes need granular tasks. When code changes frequently, you need to re-run specific parts without redoing everything. The task structure mirrors the migration’s conceptual structure.
Exploratory: Try It and Learn
Example: Standardizing on React
Your codebase has Django templates, jQuery, vanilla JS, and bits of React. You want to extract reusable widgets as React components. But you don’t know what qualifies as “reusable” or where these patterns live.
Structure: 1 experimental task to explore
# Find Potential Widgets
Search for interactive functionality that might be extractable:
- Form handling with validation
- Dynamic UI that responds to user input
- Repeated patterns across multiple templates
Look in templates/, static/js/, and anywhere else that might have client-side logic.
## Store
For each potential widget found:
{
"location": "path to files",
"description": "what it does",
"complexity": "how hard to extract"
}
# Describe Current Implementation
For the top 5 candidates, document:
- Where HTML lives
- Where JS lives
- Where tests live (if any)
- Dependencies and state management
## Store
Cache detailed descriptions.
# Draft React Component
Pick the simplest candidate.
Write a React component that mimics its behavior.
Don't integrate it - just show what it would look like.What it looks like to run:
- Run on small subset: Point at one area of the codebase
- Review what AI finds: Does it identify good candidates?
- Refine search: Adjust step 1 based on what you learned
- Iterate: Re-run with better instructions
- Decide: Based on results, structure the real migration (maybe 1 task per widget type, or 1 task for all simple widgets)
Why this pattern: When you don’t know what needs migrating, use AI to explore. The goal isn’t to finish the migration - it’s to understand the problem. Then you can structure a real plan based on what you learned.
Choosing Your Pattern
One task with many steps when:
- Same transformation applies to many files
- Context flows between steps
- You need quality checks at each stage
Many tasks when:
- Independent changes that can run separately
- You need to track progress on different parts
- Code changes frequently and you’ll re-run
Exploratory task when:
- You don’t know what needs changing
- The problem is under-specified
- You’re validating whether migration is feasible
