A starter kit for running large-scale language migrations with Claude Code: the prompts, templates, and scripts behind the process described in How Anthropic runs large-scale code migrations with Claude Code.
This kit defaults to structure-preserving migrations — same architecture, same data structures, new language. That's the case where the process below is mechanical enough to template. If you're redesigning as you migrate, read If you're redesigning first: parts of this kit change, and one part becomes invalid.
Provenance: these prompts are reconstructions — generalized and reviewed against real production migrations, including Bun's 1M+ line Zig→Rust port, not transcripts. The real artifacts were messier and more specific: the Bun port's "prompt" was a 576-line rulebook (PORTING.md) and a one-sentence kickoff.
Status: Reference code. This repo is a companion to the blog post and is not actively maintained. Issues and PRs are not monitored.
- Clone the kit inside (or adjacent to) the repo you're migrating:
git clone <kit> ./migration-kit. - Copy or
@-import the kit'sCLAUDE.mdinto the target repo'sCLAUDE.mdbefore the six steps below begin — it's the operating manual every session reads. - Optional: install the skill —
cp -r migration-kit/skill ~/.claude/skills/code-migration, then replace[kit path]inside the installed SKILL.md. - Open Claude Code in the target repo and paste
prompts/00-feasibility.mdwith its placeholders filled. - Make sure you have a judge before Step 1. If your existing test suite hits
the public surface (or already lives in a third language), it's your judge —
carry it into Step 6. If it imports internals that die with the old language,
run
prompts/00b-judge-setup.mdto build a portable parity harness and validate it (against the original and against deliberately broken code) before any translation. No judge, no exit condition. - Before any translation fan-out (prompt 03 onward): copy
templates/settings.jsonto the target repo's.claude/settings.json(seetemplates/settings.README.md) — installed by you before prompt 03 (Step 2's pilot needs the denies live), active through Step 4 (prompt 05), test denies re-activated for Step 6's fix loops. Prompts 03 and 04 verify it exists and stop without it — in one early test run this step was silently skipped and nothing caught it. - Then work
prompts/01–06in order, one gate at a time.
Start with prompts/00-feasibility.md. Paste it into Claude Code in your repo.
It produces a read-only report: the case for leaving, three committed calls
(structure-preserving or redesign, what verification costs, whether your tests
survive), a sketch of the six steps for your repo, and a verdict. "Don't
migrate" is a valid outcome. Nothing else in this kit matters until that
report says go.
Every step below follows the same doctrine: you don't fix the code — you fix the process that produced the code. Individual failures get burned down by fixer agents; recurring failures indict a rule, and the rule gets amended.
| Step | What happens | Kit artifact |
|---|---|---|
| 1. Create the map and the rules | Dependency map orders the work; gap inventory traces what the target language demands; the rulebook decides every translation question once | scripts/depmap_*, prompts/01, prompts/02, templates/RULEBOOK.md, templates/inventory.tsv |
| 2. Stress-test the rules | Dual-translation bakeoff + pilot run on a handful of nasty files; the only surviving output is rule changes | prompts/03-stress-test.md |
| 3. Translate everything | Implementer + two adversarial reviewers + fixer per unit, fanned out over a mechanical queue; the compiler waits | prompts/04-translation-kickoff.md, scripts/queue_runner.mjs |
| 4. Compile | One survey build grades everything; error list becomes a machine queue sliced by module; fixers work without compiler access | prompts/05-survey-build.md, templates/settings.json (the bans) |
| 5. Run it | Hello world, then smoke tests — the cheap end-to-end proof before the expensive one | — |
| 6. Match behavior | Inherited test suite burndown, or build a parity referee against the old code | your existing test suite is the artifact; prompts/06-post-parity.md after the gate |
Three artifacts, built in parallel, audited together:
- The rulebook (
templates/RULEBOOK.md— copy it tomigration/RULEBOOK.mdin the target repo): every decision a translator could make two ways, decided once. The meta-rule: if two agents could answer differently, it goes in the rulebook. Draft it in a conversation with Claude, then have agents survey the codebase for the facts ("how many struct fields," "how many uses tree-wide") and adversarial reviewers audit it — one mistake class each. - The dependency map (
scripts/depmap_*.py|.mjs+prompts/01): a deterministic script, not agent judgment. Orders the work, finds the cycles — at file granularity AND at the target's package granularity. (Bun's port had a clean file graph and still hit ~16K compile errors from package-level cycles nobody had checked.) - The gap inventory (
prompts/02+templates/inventory.tsv): one flat table of every site where the target language demands something the source let you keep in your head — ownership, lifetimes, nullability, interface contracts. Implementers grep it; nobody reads it top to bottom.
The sign-off gates in these prompts work like this: a workflow runs its phase to completion, returns the evidence, and exits. Your sign-off is the act of kicking off the next one. Every queue is defined by what exists on disk, so stopping is free and resuming is a re-invocation, not a recovery. When a prompt says "use a workflow," it means: run the phase as parallel subagents that complete and stop at the gate — in stock Claude Code, that's a Task/Agent fan-out.
The rulebook has never met real code. Before any fan-out:
bakeoff (two translators, separate contexts — one follows the rules, one
never learns they exist; a diff inspector turns every difference into a verdict
on a rule) and pilot (the production pipeline exactly as the fan-out will
run it, graded on obedience, not output quality). Amendments are queued for
you, never self-applied. See prompts/03-stress-test.md.
The kickoff prompt is deliberately short (prompts/04) because by now the
rulebook is the prompt. The queue is mechanical: every manifest entry with no
output file on disk yet (scripts/queue_runner.mjs) —
migration/manifest.tsv is a Step 1 output, generated by prompt 01's
closing action from the dependency map's order with
scripts/make_manifest.py. Don't run the compiler — it grades everything next
step. Ban the expensive operations by configuration, not request:
templates/settings.json, copied to the target repo's .claude/settings.json
by you before prompt 03 (Step 2's pilot needs the denies live), active
through Step 4 (prompt 05), test denies re-activated for Step 6's fix loops
(see templates/settings.README.md).
One scripted survey build runs the compiler across everything and emits the
error list as a machine queue, sliced by module, leaves-to-root. Parallel
fixers work the queue without compiler access — the daemon that owns the
build is the only process that runs it. Repeat until clean.
prompts/05-survey-build.md runs this step. With denies active the standard
transport is scripts/build_daemon.sh: the human starts it once, it reruns
the build when the tree changes, and fixers consume the numbered
migration/build-output-rN.txt files. In one early test a human relayed every
build by hand — fine for a couple of rounds, not for fifty.
If your target's typecheck is cheap (TypeScript, Go), this step dissolves
into Step 3 — run the typechecker inside every unit's loop instead of
batching it. The rule: every referee has a price, and the price decides its
position in the loop. Dissolving is an edit to the installed
.claude/settings.json at the gate — remove the typecheck denies so the loop
may run it. If settings.json was never installed there is nothing to edit,
and the dissolve silently proceeds with no guardrails at all — one early run did
exactly that, end to end. Install first, then dissolve.
Hello world. Then the smallest end-to-end command your binary supports. Cheap proofs before expensive ones.
This is where the judge you set up before Step 1 does its job. If your old tests
hit the public surface (CLI, API, exported interface) they were your judge from
the start — run them against the new code, triage every failure by also running
it against the old code (regression / inherited / environment), and burn the
queue down. If your tests import source-language internals, the portable parity
harness you built with prompts/00b-judge-setup.md is the judge — run it against
both old and new and diff the results. Either way the old code is the executable
spec, the judge was validated against deliberately broken code before you trusted
it, and you've kept it running the whole migration. Keep it running until the
end.
The done-gate is explicit: every test in the parity referee passes, AND the
original suite has been re-run on the original code with zero inherited
failures — both counts documented in the report. For redesign migrations with no inherited suite, the gate is the
built parity referee's scenario list, signed off by the human. After the
gate, prompts/06-post-parity.md burns down the deferred BUG(port) /
TODO(port) / PERF(port) markers — each fix its own flagged change, proved
by a parity re-run.
One lesson from early testing: debug the referee before believing its verdicts. In one run, all reported divergences were false — traced to two comparator bugs (whitespace handling; JSON.stringify converting NaN/-Infinity to null), none to the port. A referee that fails everything is usually broken, not right.
Three things change and one thing breaks:
- The rulebook becomes a design document (what the new architecture is), not a lookup table (how each construct translates).
- The bakeoff is invalid — a diff between rule-following and native translations measures your redesign, not your rules. Substitute: adversarial review of the design doc itself, plus disposable full runs — run the whole migration cheaply, review what came out, fix the rules, throw the run away.
- The unit of work is a module or subsystem, not a file.
- Behavior matching still works unchanged — it never sees your internals.
JavaScript→TypeScript is the most common migration by volume, and you mostly don't need any of this: TypeScript is a superset, adoption can be incremental, file by file, with no parallel codebase. Use this kit when the move is total — when every file must cross and the old language ends up deleted.
prompts/ eight paste-ready prompts (00-feasibility, 00b-judge-setup → 06-post-parity)
templates/ RULEBOOK.md, inventory.tsv, settings.json (+ why each rule exists)
scripts/ dependency mappers (Python, JS/TS, C headers) + queue runner + manifest builder + build daemon
fixtures/ test trees + expected outputs for every script
skill/ a Claude Code skill that walks the six steps
examples/ a complete example run — filled-in rulebook, inventory, diff report, parity scripts
CLAUDE.md the operating manual Claude Code reads when opened here
RUN-NOTES.md receipts from this kit's own test runs
A production-scale port can consume billions of input tokens inside two weeks. The process works at any rate limit — the calendar doesn't. At standard API limits, expect the same steps across a longer wall clock. Budget with a visible multiplication: units of work × per-unit estimate, never a vibes number. And weigh the figure against what the spend buys: most of the multiplier is the review topology, and in testing that's where the catches came from (RUN-NOTES).
This is reference code published as a blog companion and is not accepting contributions. You're welcome to fork it and adapt the dependency mappers for your ecosystem.
Apache-2.0 — see LICENSE.