Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Migrate codebases between frameworks, languages, and API versions. Use when a user asks to convert JavaScript to TypeScript, migrate React class components to hooks, upgrade Vue 2 to Vue 3, migrate Python 2 to 3, update deprecated APIs, switch ORMs, convert Express to Fastify, or modernize legacy code. Handles incremental migration with backward compatibility.
.claude/skills/terminalskills-code-migration/SKILL.md| Model | Eval pass | Runs |
|---|---|---|
| gemini-3.6-flashlowest | 99% | 346 |
| gemini-3.1-pro-preview | 100% | 5 |
| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-01 | ✗→✓ | ▲ Improved | 27% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 86% | 0% |
| case-10 | ✗→✓ | ▲ Improved | 207% | 0% |
| case-12 | ✗→✓ | ▲ Improved | 78% | 0% |
| case-03 | ✓→✓ | = Same ✓ | 36% | 0% |
Migrate codebases between frameworks, languages, and API versions with automated, incremental transformations. This skill handles JavaScript-to-TypeScript conversions, framework upgrades, deprecated API replacements, and ORM migrations while preserving functionality and maintaining backward compatibility.
When a user asks to migrate or modernize their code, follow these steps:
Analyze the codebase to understand:
bash# Count affected files find src -name "*.js" | wc -l # JS→TS migration grep -rl "React.Component" src/ # Class→hooks migration grep -rl "Vue.component" src/ # Vue 2 patterns
Before changing code, produce a migration plan:
markdown## Migration Plan: JS → TypeScript **Files to migrate:** 47 **Estimated effort:** ~2 hours with AI assistance ### Phase 1: Setup (non-breaking) - Add tsconfig.json with allowJs: true - Install TypeScript and type packages - Rename entry point to .ts ### Phase 2: Incremental conversion (file by file) - Rename .js → .ts/.tsx - Add type annotations to function signatures - Replace `any` with proper types - Fix type errors ### Phase 3: Strict mode - Enable strict: true in tsconfig - Resolve remaining `any` types - Add return type annotations
Process files incrementally, verifying each change:
JavaScript → TypeScript:
typescript// BEFORE: src/utils/api.js const fetchUser = async (id) => { const response = await fetch(`/api/users/${id}`); if (!response.ok) throw new Error('User not found'); return response.json(); }; module.exports = { fetchUser }; // AFTER: src/utils/api.ts interface User { id: string; email: string; name: string; createdAt: string; } export const fetchUser = async (id: string): Promise<User> => { const response = await fetch(`/api/users/${id}`); if (!response.ok) throw new Error('User not found'); return response.json() as Promise<User>; };
React class → hooks:
tsx// BEFORE class UserProfile extends React.Component { state = { user: null, loading: true }; componentDidMount() { fetchUser(this.props.id).then(user => this.setState({ user, loading: false }) ); } componentDidUpdate(prevProps) { if (prevProps.id !== this.props.id) { this.setState({ loading: true }); fetchUser(this.props.id).then(user => this.setState({ user, loading: false }) ); } } render() { if (this.state.loading) return <Spinner />; return <div>{this.state.user.name}</div>; } } // AFTER function UserProfile({ id }: { id: string }) { const [user, setUser] = useState<User | null>(null); const [loading, setLoading] = useState(true); useEffect(() => { setLoading(true); fetchUser(id).then(user => { setUser(user); setLoading(false); }); }, [id]); if (loading) return <Spinner />; return <div>{user?.name}</div>; }
Vue 2 → Vue 3 (Options → Composition API):
vue<!-- BEFORE: Vue 2 Options API --> <script> export default { data() { return { count: 0, items: [] }; }, computed: { total() { return this.items.reduce((s, i) => s + i.price, 0); } }, mounted() { this.fetchItems(); }, methods: { async fetchItems() { this.items = await api.getItems(); } } }; </script> <!-- AFTER: Vue 3 Composition API --> <script setup lang="ts"> import { ref, computed, onMounted } from 'vue'; const count = ref(0); const items = ref<Item[]>([]); const total = computed(() => items.value.reduce((s, i) => s + i.price, 0)); async function fetchItems() { items.value = await api.getItems(); } onMounted(fetchItems); </script>
bash# For JS→TS npm install -D typescript @types/node @types/react npx tsc --init # For Vue 2→3 npm install vue@3 @vue/compiler-sfc npm uninstall vue-template-compiler # Update import paths, build config (vite/webpack), and aliases
After each file or batch:
npx tsc --noEmitnpm testnpx eslint src/npm run buildReport any errors and fix them before proceeding.
User request: "Convert my Express app from JS to TS"
Actions taken:
@types/express, @types/node, ts-nodetsconfig.json with allowJs: true for incremental migration.js → .ts starting from leaf modules (utils, models)require → import, module.exports → exportResult:
Files migrated: 23/23
Type errors fixed: 41
New interfaces created: 8 (User, Product, Order, ApiError, etc.)
Build: ✅ passing
Tests: ✅ 34/34 passingUser request: "Upgrade our app from React Router 5 to 6"
Actions taken:
<Switch> → <Routes><Route component={X}> → <Route element={<X />}>useHistory() → useNavigate()<Redirect to="/"> → <Navigate to="/" replace><Outlet />react-router-dom to v6.20Before/After:
tsx// v5 <Switch> <Route exact path="/" component={Home} /> <Route path="/users/:id" component={UserProfile} /> <Redirect to="/" /> </Switch> // v6 <Routes> <Route path="/" element={<Home />} /> <Route path="/users/:id" element={<UserProfile />} /> <Route path="*" element={<Navigate to="/" replace />} /> </Routes>
Result: 12 files updated, 0 type errors, all 28 tests passing.
strict: false and allowJs: true, then tighten after all files are converted.require/import style consistent within each file during migration — don't mix.| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | pass→pass | 16,243 | 10,060 | -38% | 1 | 1 | 0% | 2,718 | 3,702 | +36% | 0 | 0 | — |
case-21 | pass→pass | 23,967 | 19,440 | -19% | 1 | 1 | 0% | 5,353 | 7,326 | +37% | 0 | 0 | — |
case-01 | fail→pass | 17,026 | 11,751 | -31% | 1 | 1 | 0% | 3,426 | 4,368 | +27% | 0 | 0 | — |
case-02 | fail→pass | 13,210 | 15,448 | +17% | 1 | 1 | 0% | 2,959 | 5,507 | +86% | 0 | 0 | — |
case-04 | pass→pass | 8,197 | 8,557 | +4% | 1 | 1 | 0% | 1,717 | 3,781 | +120% | 0 | 0 | — |
case-05 | pass→pass | 12,533 | 6,582 | -47% | 1 | 1 | 0% | 2,106 | 3,052 | +45% | 0 | 0 | — |
case-06 | pass→pass | 11,699 | 7,477 | -36% | 1 | 1 | 0% | 2,215 | 3,042 | +37% | 0 | 0 | — |
case-07 | pass→pass | 20,269 | 11,415 | -44% | 1 | 1 | 0% | 2,526 | 4,003 | +58% | 0 | 0 | — |
case-08 | pass→pass | 15,563 | 11,259 | -28% | 1 | 1 | 0% | 2,377 | 3,918 | +65% | 0 | 0 | — |
case-09 | pass→pass | 14,486 | 11,258 | -22% | 1 | 1 | 0% | 2,526 | 3,937 | +56% | 0 | 0 | — |
case-10 | fail→pass | 5,996 | 8,994 | +50% | 1 | 1 | 0% | 1,276 | 3,922 | +207% | 0 | 0 | — |
case-11 | pass→pass | 12,083 | 16,057 | +33% | 1 | 1 | 0% | 2,389 | 4,310 | +80% | 0 | 0 | — |
case-12 | fail→pass | 14,553 | 12,733 | -13% | 1 | 1 | 0% | 2,213 | 3,950 | +78% | 0 | 0 | — |
case-13 | pass→pass | 11,125 | 8,355 | -25% | 1 | 1 | 0% | 1,842 | 3,265 | +77% | 0 | 0 | — |
case-14 | pass→pass | 11,293 | 8,920 | -21% | 1 | 1 | 0% | 1,880 | 3,457 | +84% | 0 | 0 | — |
case-15 | pass→pass | 10,922 | 10,800 | -1% | 1 | 1 | 0% | 1,722 | 3,775 | +119% | 0 | 0 | — |
case-16 | pass→pass | 7,894 | 15,947 | +102% | 1 | 1 | 0% | 1,712 | 3,296 | +93% | 0 | 0 | — |
case-17 | pass→pass | 11,514 | 8,499 | -26% | 1 | 1 | 0% | 2,117 | 3,791 | +79% | 0 | 0 | — |
case-18 | pass→pass | 4,913 | 3,728 | -24% | 1 | 1 | 0% | 762 | 2,705 | +255% | 0 | 0 | — |
case-19 | pass→pass | 23,429 | 6,434 | -73% | 1 | 1 | 0% | 1,732 | 3,168 | +83% | 0 | 0 | — |
case-20 | pass→pass | 15,132 | 11,103 | -27% | 1 | 1 | 0% | 3,563 | 4,361 | +22% | 0 | 0 | — |
case-22 | pass→pass | 12,364 | 10,523 | -15% | 1 | 1 | 0% | 2,419 | 4,245 | +75% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +18 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.