# Anthropic 如何使用 Claude Code 进行大规模代码迁移

> 使用 AI 代理进行大规模代码迁移的分步指南——包括 Bun 百万行 Zig 到 Rust 的移植。

- 原文链接: https://laojin.blog/repost/20260721_anthropic_large_scale_code_migration
- 作者: Anthropic（老金转载）
- 原始出处: https://claude.com/blog/ai-code-migration
- 发布日期: 2026-07-21
- 标签: 代码迁移, Claude Code, AI 编程, 工程实践, 转载

---

> 原文:[Anthropic](https://claude.com/blog/ai-code-migration)

代码迁移（code migration）——将生产代码库移植到新语言的项目——直到最近还是需要多年才能完成的工程。

在过去一个月内，Anthropic 的个别开发者使用 Claude Fable 5、Claude Opus 4.8 和[动态工作流（dynamic workflows）](https://claude.com/blog/introducing-dynamic-workflows-in-claude-code)迁移了 10 个代码包，每个包包含数万到数十万行代码。本文将介绍两个案例以及从这些项目中总结的最佳实践。

Jarred Sumner，Bun 联合创始人兼 Anthropic 技术成员，使用 Claude Code 将 [Bun 从 Zig 迁移到 Rust](https://bun.com/blog/bun-in-rust)。不到两周内生成了一百万行代码，合并前 Bun 现有测试套件的 100% 在 CI 中通过。合并后出现了 19 个回归问题，目前已全部修复。Rust 版本已于六月在 Claude Code 内部上线。

Mike Krieger，Anthropic Labs 联合负责人，在一个周末内将 Python 代码库迁移为 165,000 行 TypeScript。这包括数百个代理、八个阶段门控、三轮对抗性审查，以及一次最终一致性检查——对比每个命令的输出与 Python 原始版本。

Claude Code 的新能力改变了这些长期搁置项目的收益计算。以下是我们目前使用的六步流程，源自这些迁移的经验。

核心洞察是：你不是在修复代码。**你是在修复产生代码的流程（循环）**。

## 何时以及为什么进行语言迁移

在直接进入*如何做*之前，值得讨论*何时*和*为什么*，因为围绕这类项目的假设已经发生了变化。

团队发起迁移是因为初始构建与当前项目之间的技术环境发生了变化。要么是已知的权衡变得越来越制约发展，要么是出现了更好的方案，要么是原始生态系统正在萎缩。

例如，Jarred 最初选择 Zig 是因为它提供了 C 级性能和极致的简洁性，非常适合一个独立创始人"在 LLM 之前的时代，在奥克兰狭小的公寓里用一年时间写出 Bun"。这种简洁性伴随着已知的权衡，[他在这里写了相关内容](https://bun.com/blog/bun-in-rust#just-be-really-smart-and-don-t-make-mistakes)。

快进到 2026 年。Bun 的 CLI 每月下载量超过 1000 万次，在 Claude Code 中被广泛使用。

就在上个季度，这些权衡还不足以证明冻结产品路线图并投入资源进行多季度项目的合理性。语言迁移可以带来更小、更快、更安全的系统，但没人愿意为此买单。

软件工程师还不得不面对这些曾经的超大项目固有的职业风险。你可能需要维护两个并行代码库数个季度甚至数年，如果最终结果只有 90% 的一致性，你的麻烦比开始时更大。

现在，最坏的情况不过是删除分支重新来过。

仍然需要有合理的商业理由。虽然百万行迁移不再需要在四年项目中花费 300 万到 400 万美元的工程资源，但执行成本仍然在数万到数十万美元或更多。例如 Bun 迁移消耗了 59 亿未缓存输入 token 和 6.9 亿输出 token——按 API 定价约为 165,000 美元。Mike 移植的主要部分消耗了 2700 万 token。

![Jarred 的百万行 PR](/images/reposts/20260721/01_jarred_pr.png)

*Jarred 的百万行 PR。*

**然而，迁移的理由不再需要是生死攸关的。** 变更日志中一年的内存泄漏补丁，或者一个长期瓶颈，现在就足以证明迁移的合理性。

编译步骤是 Mike 项目的触发因素。他团队开发的内部工具以单一二进制文件交付给用户。使用 Python 工具链生成该二进制文件每个平台大约需要八分钟，在每次发布时跨构建矩阵总共等待 30 分钟。移植后，相同的编译现在只需约两秒，二进制文件启动速度快了 6 倍，团队还淘汰了一条独立的部署流水线。

## 为什么 AI 改变了代码迁移的计算

Claude Fable 5 是我们功能最强大的通用模型。Fable 和 Opus 4.8 特别擅长委派、指导和验证使用子代理（subagent）的并行工作流，同时能找到多条通往目标的路径。

大规模代码迁移是这些高级模型特别有效的用例，因为：

- **工作可以并行化**。工作可以跨数千个独立单元（如文件和 crate）执行，因此代理可以同时工作，而不是一个等另一个。
- **上下文清晰且全面。** 旧代码可以作为模型的优秀规格说明。它还可以作为核心参考，帮助构建翻译代理遵循的指南。
- **内置裁判。** 许多大型代码库包含测试套件，代理可以用来验证工作。当验证是客观的时候代理表现最佳，因为模型可以针对真实标准连续运行数天，无需人工仲裁质量。
- **队列自动生成。** 当编译器或测试运行失败时，它就成为代理要修复的下一个任务。
- **需要一致性和边界情况处理**：流程的构建使得偏差无处藏身：审查者在每个发现后面引用规则，因此违规变成队列项而不是悄然偏离。当代理确实遇到边界情况时，修复方案变成后续每个代理都遵循的规则。

如下所述，Mike 和 Jarred 都在迁移过程的关键步骤中使用了 Fable，特别是在**顾问模式（advisory pattern）**中——使用多个模型层级来优化 token 消耗。

## 大规模代码迁移的六个步骤

*以下流程已被泛化，适用于多种语言和场景。更多细节可以阅读* [*Jarred 的博客*](https://bun.com/blog/bun-in-rust)*。*

### 前置条件

迁移项目开始前的一个前置条件是有一个强有力的裁判，否则你不会有退出条件或成功的衡量标准。

裁判必须能够在同等条件下评估原始代码和目标代码。用原始语言编写的测试套件通常依赖于目标代码中不存在的内部函数。

要构建这个裁判：

- **对现有测试分类**。使用 Claude 识别哪些测试可以表达为外部调用，哪些依赖于无法移植的内部实现。
- **重写以提高可移植性。** 将面向外部的测试转换为可以同时在原始版本和移植版本上运行的断言。使用对抗性代理验证重写的测试没有削弱断言。
- **验证裁判**。对原始代码运行它以确认通过。然后对故意破坏的代码运行以确认它能捕获失败——不能发现问题的裁判不是裁判。

Jarred 有一个用第三方语言（TypeScript）编写的大型测试套件，但大多数项目不会是这种情况。对于 Python 到 TypeScript 的移植，Mike 创建了七个真实场景的一致性测试框架，将任何行为变化视为需要修复的 bug。

在进入每个阶段之前，这主要遵循 Jarred 的方法论，每个阶段都有审查和门控。Mike 遵循类似的整体结构，使用类似的循环工作流，但他端到端运行整个迁移，根据结果修改规则和工作流，然后再次运行——每次都丢弃输出，直到第三次。

![大规模代码迁移的整体流程图](/images/reposts/20260721/02_migration_flow.png)

### 步骤 1 — 创建规则手册、依赖图和差距清单

![步骤 1 示意图](/images/reposts/20260721/03_step1.jpg)

在这个阶段，我们正在创建迁移的基础：需要重构而非简单翻译的代码清单、翻译代码的规则手册，以及排序迁移实施工作流的依赖图。

顺序很重要：规则手册必须在差距清单之前完成。差距清单由规则手册的默认规则无法覆盖的部分定义，两者在联合审计中一起测试。

#### 规则手册

[规则手册（rulebook）](https://github.com/anthropics/code-migration-kit-with-claude-code/blob/main/templates/RULEBOOK.md)的确切形式取决于你必须在开始时做出的关键架构决策。其中最重要的是：新代码是否遵循相同的结构，还是完全重新设计。

如果是前者（Jarred），规则手册主要是在语言之间翻译类型和惯用法的查找表，对于更难翻译的组件则指向差距清单。如果是后者（Mike），它将是一份设计文档。

Jarred 通过与 Claude 对话创建了他的规则手册，为每个模糊领域形成策略。他还使用了八个专门设计的子代理，根据自己的直觉审查八种不同类别的常见失败模式。

#### 依赖图

你需要了解文件依赖关系，才能有效地将工作流分解为并行迁移，知道哪些文件先迁移，哪些文件应该放在同一批次中。某些语言和代码库有明确的清单使这变得容易，但对于遗留代码库和许多流行语言如 C/C++ 和 Python，这些依赖关系需要被发现和映射。

Claude Code 可以部署代理来创建和运行确定性脚本以生成此映射。[迁移工具包中的提示词](https://github.com/anthropics/code-migration-kit-with-claude-code/blob/main/prompts/01-dependency-map.md)使用工作流创建审查修复循环。*注意：入门工具包是本文流程的泛化模板——不是这些具体移植实际使用的版本。*

#### 差距清单和怀疑论审查者

新语言与旧语言有不同的必须满足的要求。对于 Zig 到 Rust，差异在于手动内存管理（C 和 C++ 的工作方式相同）。例如：

Zig

```zig
fn readConfig(allocator: std.mem.Allocator) ![]u8 {
    const buf = try allocator.alloc(u8, 1024);
    // ...fill buf...
    return buf; // caller must free this — but only the comment says so
}

// A caller that forgets 'defer allocator.free(buf)' still compiles — the leak only surfaces at runtime.
```

Rust

```rust
fn read_config() -> Vec<u8> {
    let buf = vec![0u8; 1024];
    // ...fill buf...
    buf // ownership moves to the caller; memory is freed automatically
}
// Use it after it's moved? Free it twice? Neither compiles.
// Forget to free it? There's no free call to forget — drop is automatic.
```

对于 Python 到 TypeScript，差距在于接口和契约。Python 不要求声明它接受什么形状的对象或返回什么，但 TypeScript 要求。例如：

Python

```python
def register(handler):
    handler.setup()
    return handler.run({"retries": 3})

# Any object with .setup() and .run() works here. Which objects actually get passed in? Read the whole codebase to find out.
```

TypeScript

```typescript
interface RunResult { ok: boolean }

interface Handler {
  setup(): void;
  run(opts: { retries: number }): Promise<RunResult>;
}

function register(handler: Handler): Promise<RunResult> {
  handler.setup();
  return handler.run({ retries: 3 });
}

// The contract must be written down before this compiles
```

Jarred 和 Mike 都创建了捕获这些隐式知识的差距清单文件。Jarred 预先清点了这些差距，这也是我们在这里做的，而 Mike 选择先翻译，然后通过事后审计创建差距清单。你可能两者都需要做。

查看这个[创建差距清单文件的 Claude Code 示例提示词](https://github.com/anthropics/code-migration-kit-with-claude-code/blob/main/prompts/02-gap-inventory.md)。

### 步骤 2 — 压力测试规则

![步骤 2 示意图](/images/reposts/20260721/04_step2.jpg)

此步骤涉及一次小型迁移，作为更大迁移的"试航"。

在此步骤中，Jarred 使用一个代理按照规则手册翻译三个文件，一个代理"像资深 Rust 工程师一样"翻译三个文件，还有一个代理使用 diff 来创建新的翻译规则。在这个阶段他捕获了两个关键问题，如果扩展到所有 1,448 个文件将会造成大量问题。

提示词可能看起来像[这样](https://github.com/anthropics/code-migration-kit-with-claude-code/blob/main/prompts/03-stress-test.md)。

这种压力测试**仅适用于保持结构的迁移**，即同一文件的两个翻译可以逐行比较。如果你的规则手册是重新设计——像 Mike 的那样——等效的测试是直接用对抗性审查者攻击设计文档，然后通过一次性的端到端运行来验证。

无论如何，丢弃所有已翻译的文件。目标是改进规则，而不是取得渐进式进展。

### 步骤 3 — 翻译所有内容

![步骤 3 示意图](/images/reposts/20260721/05_step3.jpg)

在剩余步骤中，你运行相同的多代理循环架构：实现、审查和修复。

你可以将实现工作卸载到较小的模型，让审查者使用较大的模型。例如，Mike 在分发 12 个子代理进行主要迁移时使用了 Claude Sonnet。

工作队列应该是机械化的。批处理脚本通过检查已翻译文件是否存在于磁盘上来判断完成情况，然后将待处理文件切分为批次分配给实现者代理。因为队列每次都从磁盘重建，迁移天然可以恢复。

在这个阶段，代理可能对完成的工作量过于保守。解决方法可以是一个直接、强调性的提示词指令，附带上下文说明编译器会在下一步捕获错误。

翻译器无法自信执行的任何内容都用 `// TODO(port): <reason>` 标记，留待步骤 4 处理。从这里开始，待办列表自动生成：编译器列举错误，冒烟测试发现崩溃，测试套件报告失败。

两个对抗性审查者使用独立上下文评估实现者的工作，审查者之间的分歧交给第三个代理。当审查者在多个文件中不断发现相同的错误时，修复不是针对单个文件的。你在规则手册中添加一句话，然后重新生成受影响的批次。规则手册在这一步持续增长；代码永远不会绕过规则手册被手动修补。

此步骤中一个重要的设计决策是编译器的位置。Mike 在每个循环中运行 TypeScript 编译器，因为它在几秒内检查一个单元。Jarred 完全禁止编译器进入循环，推迟到下一步，因为 cargo 需要几分钟。

在这一步，大部分繁重工作已经完成，[提示词开始变得更短。](https://github.com/anthropics/code-migration-kit-with-claude-code/blob/main/prompts/04-translation-kickoff.md)

### 步骤 4、5、6 — 编译、运行和行为匹配

![步骤 4、5、6 示意图](/images/reposts/20260721/06_step456.jpg)

这三个步骤共享相同的循环架构，需要的人工判断逐步减少，因此我们一起介绍。

**步骤 4**，例如，根据语言和迁移规模，可能经常融入步骤 3。

根据编译步骤的规模和难度，代理可能根本不执行此步骤。Jarred 使用编排脚本在整个工作区上调用一次编译器来执行此步骤。然后"修复代理"并行处理错误列表，配合对抗性审查。再次构建，循环往复。

审查错误列表有助于捕获可能需要调整的系统性问题。例如，Jarred 遇到了数千个 Rust 模块错误，这些错误在修复 Zig 懒编译容忍的循环导入后浮现。他通过编码逻辑来分类哪些依赖需要删除、移动或重构边界来修复循环。

**步骤 5** 也有类似于编译器错误列表的机械化真相来源：冒烟测试的崩溃。同样，循环修复方法是将问题按根本原因分组，由对抗性子代理审查。

**步骤 6** 也是我们故事的结尾，是比较两个代码库中程序的行为。

我们的文件现在已经被翻译、编译和冒烟测试。现在是时候将它们分片并对其运行测试套件（来自前置条件阶段）。用"修复代理"处理失败，它们会对比两个代码库审查失败的测试。对抗性审查者检查其修复。

此循环的下一个阶段是[构建守护进程（build daemon）](https://github.com/anthropics/code-migration-kit-with-claude-code/blob/main/scripts/build_daemon.sh)，它是唯一被允许重建二进制文件的进程。修复者编写补丁；守护进程将它们批量处理，重建一次，重新运行受影响的测试，并将结果反馈。这将最昂贵的操作序列化，而不是让多个代理独立触发它。

当相同的失败在多个测试中重复出现时，修复向上游移动：你修改产生 bug 的规则，只重新生成该规则影响的文件。

Mike 的方法在这里很重要，因为许多开发者不会有构建好的或已移植的测试套件。Mike 让 Claude 创建了一个小脚本，对新移植版本和原始 Python 代码库运行 7 个真实场景，并 diff 结果。每个失败的场景都有自己的修复代理，循环运行直到所有七个通过。

然后他更进一步。Claude 设计了自己的端到端测试套件并自主运行了一整夜，修复中断的部分并连续运行了四个晚上。结果是，它捕获了没有任何场景列表能预测到的小问题。

经验是：缺少测试套件不会阻塞这一步。如果你无法继承裁判，让 Claude 构建一个。无论如何，你的原始代码库就是真相标准。

## 代码迁移最佳实践

每次运行都教会了我们前一次没有的东西。可以确定的是，你的下一次迁移会教你这份指南无法教的东西。但一些实践在每个项目中都经受住了考验：

- **不要盲目遵循这份指南。** 每次迁移都不同。将其视为起点，在投入之前先与 Claude 一起规划你的具体迁移。
- **不要关注单个失败。** 单个失败是循环的工作。修复代理会消灭它们。你的注意力应该放在模式上。
- **让审查对抗化，让验证机械化。** 对抗性审查允许更长时间运行的任务，通常值得 token 消耗。让脚本——编译器、diff、测试套件——做裁判。
- **不要所有事情都用最大的模型。** Token 消耗集中在你的循环中，所以要有意识地设计它们。较小的模型能很好地处理大量实现工作的扇出；将最大的模型留给审查者和编写其他代理遵循的规则。
- **将人工时间前置。** 规则手册和压力测试最耗时。之后的一切基本上都是队列在消耗。
- **让工作队列机械化且可恢复。** "完成"应该意味着"输出文件存在于磁盘上"。

## 审查循环结果，而非代码

Jarred 的 Bun 迁移现已在生产环境中运行，尽管每次迁移都有权衡。例如，约 4% 的 Rust 代码位于"unsafe"块中，主要是 C/C++ 边界的单行指针操作。

但新代码库可衡量地更好。团队工具能检测到的每个内存泄漏都已修复：一个 2,000 次重复构建的基准测试从 6,745 MB 内存降至 609 MB。二进制文件在 Linux 和 Windows 上小了 19%。跨语言优化使其在 HTTP 服务和实际工作负载（如 next build 和 tsc）中快了 2-5%。

考虑一下是否该重新评估你长期搁置的迁移项目。选择你一直在容忍的代码库，问问 Claude 迁移流程是什么样的。

***相关***

- [*迁移入门工具包*](https://github.com/anthropics/code-migration-kit-with-claude-code) *注意：入门工具包是上述流程的泛化模板——不是这些具体移植实际使用的版本。*
- [*代码现代化插件*](https://github.com/anthropics/claude-plugins-official/tree/main/plugins/code-modernization) *— 用于遗留代码现代化和框架升级，而非语言移植*
- [*Claude Code 中的动态工作流*](https://claude.com/blog/introducing-dynamic-workflows-in-claude-code)
