Skip to content
Back to skills

Large Scale Migration

ASecurity

How to execute a LARGE MECHANICAL change across any codebase with LEVERAGE instead of an agent-grind or hand-edits — a cross-cutting migration, refactor, rename, dialect/framework/DB port, library adoption, or bulk transform. The map→transform→gate pattern: a deterministic transform driven by a source-of-truth map, proven by a differential-equivalence gate. Use when the work is "migrate all X to Y", "rename Z everywhere", "port to a new DB/dialect/framework", "adopt a library across N files",...

  • 9 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 18, 2026
ai-agentsrustgosqlnodeexpressapi

Works with

  • api

Security analysis

A100/100

Scanned October 18, 2026

npx -y skills add mikeparcewski/wicked-garden --skill large-scale-migration --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Large Scale Migration?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Large Scale Migration
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/mikeparcewski-large-scale-migration/badge)](https://www.skillsdirectory.com/skills/mikeparcewski-large-scale-migration)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: wicked-garden-engineering-large-scale-migration
description: |
  How to execute a LARGE MECHANICAL change across any codebase with LEVERAGE
  instead of an agent-grind or hand-edits — a cross-cutting migration, refactor,
  rename, dialect/framework/DB port, library adoption, or bulk transform. The
  map→transform→gate pattern: a deterministic transform driven by a
  source-of-truth map, proven by a differential-equivalence gate. Use when the
  work is "migrate all X to Y", "rename Z everywhere", "port to a new
  DB/dialect/framework", "adopt a library across N files", "refactor this
  pattern in M places", or any change touching dozens-to-thousands of sites.
  Language/DB/framework-agnostic — the thinking, not a specific tool.
metadata:
  role: module
---

# Large-scale mechanical change — leverage, not grind

A change touching dozens-to-thousands of sites is NOT N problems. It is a small number of repeated PATTERNS. Reaching for "fan out 1000 agents to edit each site", "hand-edit every file", or "sed the repo" is the paper bag. Reduce the work to **MAP → TRANSFORM → GATE** instead.

## The core pattern

1. **MAP** — the source of truth that already exists or can be **deterministically extracted**: an inventory of the affected sites, an ownership/registry table, a call graph, a schema. Build it with an **AST tool** (ts-morph, libcst, jscodeshift, tree-sitter, Roslyn…) or **semgrep** — never a regex string-grep (quote/escaping/comment blind spots), and never "an agent grepped and *thinks* it found them all." An exhaustive, reproducible map is the foundation; an approximate one ships a partial migration.
2. **TRANSFORM** — a **deterministic** operation over the map: a **codemod** (AST rewrite), **codegen** (emit from the map), or **regenerate** (edit a single source-of-truth + re-run a generator that produces the derived artifacts). NOT per-site LLM editing. The transform should **preserve semantics by construction** — rewrite the call *shape* while keeping the literal; move a block intact; change the *binding* not the *logic* — so correctness is structural, not hoped-for.
3. **GATE** — a **verifiable equivalence/green check**: **differential execution** (run old vs new on identical seeded inputs, assert identical outputs), the full test suite, or a lint count driven to 0. The change is trusted because it is **proven equivalent**, not because someone reviewed N diffs.

The bulk goes through the transform. The transform **self-flags** what it can't handle (the exceptions). Humans/agents touch **only the residue** — never the bulk.

## The TRANSFORM techniques menu (ranked: deterministic → AI)

The TRANSFORM step is not one tool — it is a ranked ladder. **Prefer the lowest-numbered technique that fully covers the task.** Determinism is cheaper to trust than generation: a recipe or codemod preserves semantics by construction and is re-runnable; an AI wave must be eval-gated every time. Mixing rungs 80/20 across one migration is fine (most go through rung 1–2, the awkward residue through 4–5). **Never reach for rung 4/5 for what a rung-1 recipe already does** — that trades a proven transform for a hoped-for one. **Record which rung you chose** per wave, so "should I use AI here?" is a defensible, auditable decision, not an implicit one.

| # | Technique | Use when |
|---|-----------|----------|
| 1 | **Recipe** (e.g. OpenRewrite, ts-morph preset, framework upgrade-assistant) | A well-known, parameterized transform already exists for this exact change (version bump, API rename, framework migration). Highest leverage, lowest risk — adopt it. |
| 2 | **AST codemod** (jscodeshift, libcst, tree-sitter, Roslyn, ts-morph) | No off-the-shelf recipe, but the change is a structural rewrite expressible as a tree operation (rewrite the call *shape*, move a block intact, change a binding). Deterministic, semantics-preserving by construction. |
| 3 | **Pattern-replace / codegen / regenerate** | The change is driven by a source-of-truth map — emit the derived artifacts from the map, or edit the single source + re-run a generator. (Structured replace only; **never** raw `sed`/regex on code — string blind spots break semantics silently.) |
| 4 | **AI-assisted wave (eval-gated)** | The transform needs judgment a tree op can't encode (idiomatic re-expression, semantic translation across a paradigm gap) but is still bulk-shaped. Run it in waves, each **gated by the differential-equivalence check** — never ship an AI wave on a build-green alone. |
| 5 | **Manual + AI pair** | Genuinely non-portable / one-off residue the transform self-flagged (cross-cutting joins, sync/async boundaries, non-portable platform features). Judgment-only, case-by-case — the *smallest* slice, never the bulk. |

The GATE is the same regardless of rung: differential equivalence (or suite-green / lint-to-0) is what makes the change trustworthy. A higher rung does not lower the bar — it raises it (rungs 4–5 need *more* fixture coverage, not less).

## Before you transform: make the INSTRUMENT trustworthy
You cannot drive a count to 0 if the count is a fiction. Fix the *measurement* FIRST:
- Does the lint/audit/inventory actually see **all** the cases? Prove it: inject a known violation and confirm it's caught. (Real failure mode: a "complete" audit that masked single-quoted strings and silently under-counted by hundreds.)
- Is the **baseline** the right one? Diffing the after-state against a baseline that *already contains part of the change* catches nothing — pin the true before-state.

## Buy / rewrite / build — not "write more code"
Solve for the OUTCOME with maximum leverage, per sub-problem:
- An **OSS component** that meets the requirement beats building it — a query builder, an **in-process embedded engine** for tests (so the test→fix loop needs no infra), a parser/translator. Adopt it; build only the thin seam it doesn't cover.
- A clean **rewrite** of a section (you have the working code as the spec) beats editing it in place across dozens of touch-points.

## The rules (non-negotiable)
- **No grandfathering.** "Done" = the count is 0 **AND** zero exemptions (suppressions / allow-markers / "left raw with a TODO / DEFERRAL"). Fix the code; never exempt it. An exemption is a landmine that *defeats the migration's purpose* — it preserves the exact case the migration exists to remove, and it stays invisible until it breaks.
- **Differential equivalence is the trust — not review.** `old(input) === new(input)` on real seeded data. Strength tracks fixture coverage → seed broadly; an empty fixture passes trivially and lies.
- **Residue is small, self-flagged, judgment-only.** The transform reports what it couldn't do; that list is the only place to reason case-by-case (cross-cutting joins, genuinely non-portable constructs, sync/async boundaries).
- **Prove the runtime — don't trust build-green.** Build/test green ≠ feature works, *especially* for LLM/agent features (the assembled context can drift while stored rows stay byte-identical → degraded reasoning / hallucination that no unit test catches). Prove the running process serves the right build; **grade** functional outputs (content correctness + faithfulness), don't just check a 200-OK.
- **No implicit completeness, and never narrow the DoD to skip work.** Name what is NOT done. If a phase can't reach its green gate, STOP there and name the blocker — do not declare a convenient subset "done."

## Worked example (transferable): a data-access migration → second engine
Goal: move ~1,900 raw `db.prepare(sql)`-style call sites behind one typed port, then run the app on a second DB engine/dialect — without hand-editing 1,900 sites or standing up infra.
- **MAP:** an AST extractor walks the *call expressions* (matching the call node, not string contents → no quote blind spot) → an exhaustive inventory of every query, its tables, and static-vs-dynamic shape.
- **TRANSFORM:** a codemod rewrites the call *shape* (e.g. positional `?` → named params; SQL literal preserved) and injects the port; the sync→async cascade propagates up the call graph; sync-boundaries self-flag for manual handling.
- **GATE:** a differential test runs the OLD query and the NEW port call against the **same seeded in-memory DB** and asserts byte-identical results (rows + affected-rows + resulting state). Per-module then full-suite green is the bar.
- **Second engine:** adopt an **in-process embedded build of the target engine** for tests (no server), translate **only** the non-portable patterns the inventory *proves* exist (dialect translation at the adapter boundary), and route truly-non-portable features (full-text, vector) behind **thin adapter interfaces**. The **cross-engine differential** (same query, both engines, equivalent) is the gate.
- **Same pattern, different map, per phase:** correcting an *ownership/registry map* = edit the single source-of-truth + **regenerate** the derived artifacts (never hand-edit the derivatives). Relocating code by an ownership map = a codemod keyed on that map. Isolating onto separate connections/processes = a **config flip** on a registry the port already abstracts (no code rewrite — that's the payoff of routing everything through the port first).

## Anti-patterns this kills
- "Fan out 1,000 agents to hand-edit each site" — context blow-up, non-deterministic, no equivalence proof.
- "sed / regex replace across the repo" — string blind spots, false positives, breaks semantics silently.
- "Exempt the hard ones / leave a TODO / suppress the lint" — grandfathering, the landmine.
- "Build green + suite green, ship it" — never proved the feature, never proved the runtime.
- "This subset counts as done" — narrowing the DoD to skip work.

## Pair with (if your project has them)
A "prove the feature works, not just build-green" discipline; a "no silent failures" discipline; and a recorded "definition of done" you refuse to narrow.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.