Migrações de código, projetos que transferem uma base de código de produção para uma nova linguagem, eram empreendimentos de vários anos até recentemente.
No último mês, desenvolvedores individuais na Anthropic migraram 10 pacotes de código consistindo em dezenas a centenas de milhares de linhas de código usando Claude Fable 5, Claude Opus 4.8 e fluxos de trabalho dinâmicos.
Jarred Sumner (@jarredsumner), cofundador da Bun e Membro da Equipe Técnica na Anthropic, usou o Claude Code para migrar a Bun de Zig para Rust. Um milhão de linhas de código foram produzidas em menos de duas semanas, com 100% do conjunto de testes existente da Bun passando na CI antes do merge. Dezenove regressões surgiram após o merge e já foram todas corrigidas. A migração para Rust foi enviada dentro do Claude Code em junho.
Mike Krieger (@mikeyk), colíder do Anthropic Labs, migrou uma base de código Python para 165.000 linhas de TypeScript em um fim de semana. Isso incluiu centenas de agentes, oito portões de fase, três rodadas de revisão adversária e uma verificação de paridade final que comparou a saída de cada comando com o original em Python.
As novas capacidades do Claude Code mudam a matemática para esses projetos adiados por tanto tempo. Abaixo está o processo de seis etapas que usamos agora, extraído do que essas migrações nos ensinaram.
A ideia central é que você não corrige o código. Você corrige o processo (loop) que produziu o código.
Por que e quando migrar linguagens
As equipes iniciam migrações devido a mudanças no cenário entre a construção inicial e o projeto atual. Ou uma troca conhecida se tornou limitante, uma abordagem melhor surgiu, ou o ecossistema original está encolhendo.
Por exemplo, Jarred originalmente escolheu Zig porque oferecia desempenho de nível C com simplicidade radical, ideal para um fundador solo "escrevendo a Bun em 1 ano em um apartamento apertado em Oakland pré-LLM". Essa simplicidade veio com trocas conhecidas, sobre as quais ele escreve aqui.
A CLI da Bun está recebendo mais de 10 milhões de downloads mensais e é usada extensivamente dentro do Claude Code.
Ainda no último trimestre, essas trocas não seriam suficientes para justificar congelar o roteiro e comprometer recursos em um projeto de vários trimestres. Você poderia manter duas bases de código paralelas por trimestres ou anos, e se o resultado final fosse 90% de paridade, você teria uma dor de cabeça maior do que quando começou.
Agora, o pior cenário possível é você deletar o branch e tentar novamente.
Ainda precisa haver um caso de negócio justificável. Embora migrações de um milhão de linhas não custem mais US$ 3 a US$ 4 milhões em recursos de engenharia ao longo de um projeto de quatro anos, elas ainda custam dezenas a centenas de milhares de dólares ou mais para executar. A migração da Bun, por exemplo, consumiu 5,9 bilhões de tokens de entrada não armazenados em cache e 690 milhões de tokens de saída — cerca de US$ 165.000 no preço da API. A parte principal da migração de Mike foi de 27 milhões de tokens.

O PR de um milhão de linhas de Jarred.
No entanto, o caso de migração não precisa mais ser existencial. Um ano de correções de bugs de memória no changelog, ou um gargalo crônico, agora podem justificá-la.
A etapa de compilação foi o ímpeto para o projeto de Mike. A ferramenta interna em que sua equipe trabalha é enviada aos usuários como um único binário. Produzir esse binário com o toolchain Python levava aproximadamente oito minutos por plataforma, totalizando uma espera de 30 minutos em toda a matriz de build em cada lançamento. Após a migração, a mesma compilação agora leva cerca de dois segundos, o binário inicia 6x mais rápido e a equipe conseguiu aposentar um pipeline de implantação separado.
Por que a IA muda a matemática da migração de código
Fable e Opus 4.8 são particularmente bons em delegar, direcionar e verificar fluxos de trabalho paralelos com subagentes, enquanto encontram múltiplos caminhos em direção aos objetivos declarados.
Grandes migrações de código são um caso de uso particularmente eficaz para esses modelos avançados porque:
- O trabalho é paralelo. O trabalho pode ser executado em milhares de unidades independentes, como arquivos e crates, para que os agentes possam trabalhar ao mesmo tempo, em vez de um esperar pelo outro.
- O contexto é claro e abrangente. O código antigo serve como uma ótima especificação para o modelo.
- Há um árbitro embutido. Muitas bases de código grandes incluirão um conjunto de testes que os agentes podem usar para verificar seu trabalho.
- A fila se escreve sozinha. Quando uma compilação ou execução de teste falha, isso se torna o próximo item para um agente corrigir.
- Eles exigem consistência e tratamento de casos limite: os revisores citam a regra por trás de cada descoberta, para que uma violação se torne um item da fila, em vez de uma divergência silenciosa.
Seis etapas para grandes migrações de código
Para mais detalhes, você pode ler o blog de Jarred.
Pré-requisitos
Um pré-requisito antes de iniciar seu projeto de migração é ter um juiz forte em vigor; caso contrário, você não terá uma condição de saída ou medida de sucesso.
Para construir este juiz:
- Categorize os testes existentes. Use o Claude para identificar quais testes são expressáveis como chamadas externas e quais dependem de partes internas que não serão migradas.
- Reescreva para portabilidade. Converta os testes voltados para o exterior em asserções que possam ser executadas tanto contra o original quanto contra a migração. Use agentes adversários para verificar se os testes reescritos não enfraquecem as asserções.
- Valide o juiz. Execute-o contra o código original para confirmar que passa. Em seguida, execute-o contra o código deliberadamente quebrado para confirmar que falha — um juiz que não detecta quebras não é um juiz.
Isso segue principalmente a metodologia de Jarred, com revisões e portões em cada etapa. Mike seguiu uma estrutura geral semelhante usando fluxos de trabalho de loop semelhantes, mas executou toda a migração de ponta a ponta, revisou as regras e o fluxo de trabalho com base nos resultados e executou novamente — descartando a saída a cada vez até a terceira execução.

Etapa 1 — Crie o livro de regras, o mapa de dependências e o inventário de lacunas
A ordem importa: o livro de regras deve vir antes do inventário de lacunas. O inventário de lacunas é definido pelo que os padrões do livro de regras não cobrirão, e os dois são testados juntos em uma auditoria conjunta.
Livro de regras
A forma exata do livro de regras depende de decisões arquitetônicas importantes que você deve tomar no início. A principal delas é se o novo código seguirá a mesma estrutura ou se será completamente reprojetado.
Se for o primeiro caso (Jarred), o livro de regras será principalmente tabelas de consulta que traduzem tipos e expressões idiomáticas entre linguagens, enquanto aponta para o inventário de lacunas para os componentes mais difíceis de traduzir. Se for o segundo caso (Mike), será um documento de design.
Jarred criou seu livro de regras conversando com o Claude, formando uma política para cada área de ambiguidade. Ele também usou oito subagentes especificamente projetados para revisar 8 categorias diferentes de modos de falha comuns com base em sua própria intuição.
Mapa de dependências
Você precisa entender as dependências de arquivos para dividir efetivamente os fluxos de trabalho para uma migração paralela, para saber quais arquivos migrar primeiro e quais arquivos conter no mesmo lote. O Claude Code pode implantar agentes para criar e executar um script determinístico para produzir este mapa.
Inventário de lacunas e revisores céticos
A nova linguagem tem requisitos diferentes da linguagem antiga que devem ser atendidos. Para Zig para Rust, a diferença era o gerenciamento manual de memória (C e C++ funcionam da mesma maneira). Por exemplo:
1// Zig23fn readConfig(allocator: std.mem.Allocator) ![]u8 {4 const buf = try allocator.alloc(u8, 1024);5 // ...preenche buf...6 return buf; // quem chama deve liberar isso — mas apenas o comentário diz7}89// Um chamador que esquece 'defer allocator.free(buf)' ainda compila — o vazamento só aparece em tempo de execução.
1fn read_config() -> Vec<u8> {2 let buf = vec![0u8; 1024];3 // ...preenche buf...4 buf // a propriedade move para o chamador; a memória é liberada automaticamente5}67// Usar depois de movido? Liberar duas vezes? Nenhum compila.8// Esquecer de liberar? Não há chamada free para esquecer — drop é automático.
Para Python para TypeScript, a lacuna eram interfaces e contratos. Python não exige um contrato declarando qual forma de objeto aceitará ou o que retorna, mas TypeScript exige.
Tanto Jarred quanto Mike criaram arquivos de inventário de lacunas capturando esse conhecimento implícito. Jarred inventariou essas lacunas antecipadamente, que é o que fazemos aqui, enquanto Mike escolheu traduzir primeiro e depois criar o inventário de lacunas auditando posteriormente. Você pode precisar fazer ambos.
Confira este exemplo de prompt do Claude Code para criar um arquivo de inventário de lacunas.
Etapa 2 — Teste as regras sob estresse

Nesta etapa, Jarred usou um agente para traduzir três arquivos usando o livro de regras, um agente para traduzir três arquivos "como um engenheiro Rust sênior" e um agente para usar o diff para criar novas regras de tradução. Nesta fase, ele pegou duas questões críticas que teriam criado inúmeros problemas se espalhadas por todos os 1.448 arquivos.
Esse tipo de teste de estresse só funciona para migrações que preservam a estrutura, onde duas traduções do mesmo arquivo são comparáveis linha por linha. Se o seu livro de regras for um redesenho — como o de Mike — o teste equivalente é atacar o documento de design diretamente com revisores adversários e depois validá-lo com uma execução de ponta a ponta descartável.
Independentemente disso, jogue fora quaisquer arquivos traduzidos. O objetivo é refinar as regras, não fazer progresso incremental.
Etapa 3 — Traduza tudo

Para as etapas restantes, você executa a mesma arquitetura de loop multiagente: implementar, revisar e corrigir.
Você pode transferir o trabalho do implementador para modelos menores e manter os revisores nos maiores. Por exemplo, Mike usou Claude Sonnet quando distribuiu 12 subagentes para a migração principal.
A fila de trabalho deve ser mecânica. Um script em lote decide o que está feito verificando se o arquivo traduzido existe no disco e então divide os arquivos pendentes em lotes para os agentes implementadores. Como a fila é reconstruída a partir do disco a cada vez, a migração é retomável por construção.
Qualquer coisa que o tradutor não possa executar com confiança é sinalizada com "// TODO(port): <motivo>" para ser tratada na etapa 4.
Dois revisores adversários avaliam o trabalho dos implementadores usando contextos separados e a discordância entre os revisores vai para um terceiro agente. Quando um revisor continua pegando o mesmo erro em vários arquivos, a correção não é por arquivo. Você adiciona uma frase ao livro de regras e regenera o lote afetado. O livro de regras continua crescendo nesta etapa; o código nunca é corrigido manualmente contra ele.
Uma decisão de design importante a ser observada nesta etapa é onde o compilador se senta. Mike executou o compilador TypeScript dentro de cada loop, porque ele verifica uma unidade em segundos. Jarred baniu o compilador do loop inteiramente e o adiou para a próxima etapa, porque o cargo leva minutos.
Etapas 4, 5, 6 — Compile, execute e corresponda ao comportamento

Essas três etapas compartilham a mesma arquitetura de loop e precisam progressivamente de menos julgamento humano, então as cobrimos juntas.
Jarred executou isso com um script orquestrador que invocou o compilador uma vez em todo o espaço de trabalho. "Agentes corretores" então percorreram a lista de erros em paralelo com revisão adversária. O build é executado novamente, e o processo se repete.
Revisar a lista de erros é útil para capturar problemas sistêmicos que podem exigir ajustes. Por exemplo, Jarred encontrou milhares de erros de módulo Rust que surgiram após corrigir importações cíclicas que a compilação preguiçosa de Zig tolerava. Ele corrigiu o loop codificando a lógica para classificar qual dependência deletar, mover ou reestruturar o limite.
A etapa 5 também tem uma fonte de verdade mecânica semelhante à lista de erros do compilador: falhas do teste de fumaça. Novamente, a correção do loop foi agrupar problemas em categorias, neste caso agrupando causas por causa raiz que são revisadas por subagentes adversários.
A etapa 6 e o fim da nossa história é comparar o comportamento dos programas entre as duas bases de código.
Nossos arquivos foram agora traduzidos, compilados e testados quanto à fumaça.
Agora é hora de fragmentá-los e executar o conjunto de testes (da etapa de pré-requisitos) contra eles. Enfrente as falhas com "agentes corretores" que revisam os testes com falha em ambas as bases de código. Revisores adversários verificam suas correções.
O próximo estágio neste loop é um daemon de build, que é o único processo autorizado a reconstruir o binário. Os corretores escrevem patches; o daemon os agrupa, reconstrói uma vez, reexecuta os testes afetados e alimenta os resultados de volta. Isso serializa a operação mais cara, em vez de permitir que vários agentes a acionem independentemente.
A abordagem de Mike é importante aqui, porque muitos desenvolvedores não terão um conjunto de testes desenvolvido ou migrado. Mike fez com que o Claude criasse um pequeno script para executar 7 cenários do mundo real tanto na nova migração quanto na base de código Python original, e comparou os resultados. Cada cenário com falha recebeu seu próprio agente de correção, e o loop foi executado até que todos os sete passassem.
Então ele foi um passo adiante. O Claude projetou seu próprio conjunto de testes de ponta a ponta e o executou autonomamente durante a noite, corrigindo o que quebrava e reexecutando por quatro noites seguidas. Como resultado, ele pegou os pequenos problemas que nenhuma lista de cenários teria previsto.
A lição é que a falta de um conjunto de testes não bloqueia esta etapa. Se você não pode herdar um árbitro, faça com que o Claude construa um. Sua base de código original é a verdade fundamental de qualquer maneira.
Melhores práticas para migrações de código
Cada execução nos ensinou algo que a anterior não ensinou. Mas algumas práticas se mantiveram em todos os projetos:
- Não siga este guia cegamente. Cada migração é diferente. Trate isso como um ponto de partida e planeje sua migração específica com o Claude antes de se comprometer com ela.
- Não foque em falhas individuais. Falhas individuais são trabalho do loop. Sua atenção pertence aos padrões.
- Torne a revisão adversária e a verificação mecânica. Deixe que scripts — um compilador, um diff, um conjunto de testes — sejam o árbitro.
- Não use o maior modelo para tudo. Modelos menores lidam bem com a distribuição de implementação de alto volume; guarde seu maior modelo para revisores e para qualquer coisa que escreva regras que outros agentes seguirão.
- Concentre as horas humanas no início. O livro de regras e o teste de estresse são os que mais consomem tempo. Tudo depois é principalmente filas sendo consumidas.
Revise os resultados do loop, não o código
A migração da Bun de Jarred está agora em produção, embora toda migração tenha trocas. Por exemplo, cerca de 4% do código Rust está dentro de blocos "unsafe", principalmente operações de ponteiro de linha única em limites C/C++.
Mas a nova base de código é mensuravelmente melhor. Cada vazamento de memória que as ferramentas da equipe podem detectar foi corrigido: um benchmark de 2.000 builds repetidos caiu de 6.745 MB de memória para 609. O binário é 19% menor no Linux e Windows. E a otimização cross-language o tornou 2–5% mais rápido em HTTP serving e cargas de trabalho do mundo real como next build e tsc.
Escolha a base de código que você tem tolerado e pergunte ao Claude como é o processo de migração para ela.





