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 behavior

Each 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