Por Que Agents Estragam Grandes Refatorações (e Como Definir o Escopo para Que Não Estraguem)
Pedi ao Claude Code para renomear um tipo de domínio central no meu app — Account para Workspace, mais três campos associados. Talvez sessenta arquivos. Começou rápido: a primeira dúzia estava limpa, imports atualizados, call sites renomeados, tipos propagados. Deixei rodar.
Por volta de trinta arquivos, começou a derivar. Passou a renomear Account novamente em arquivos que já tinha tocado vinte minutos antes, porque essas edições já tinham saído do seu contexto. Em dois arquivos aplicou a mudança pela metade: o tipo era Workspace, mas um método ainda retornava o formato antigo. O typecheck que rodei no final tinha noventa erros, alguns em código que o agent havia "terminado."
Nada disso foi porque o modelo era burro. Fui eu que entreguei a um único agent um trabalho maior do que ele conseguia manter na cabeça, sem nenhuma forma de se verificar pelo caminho — o equivalente agêntico de dizer a um dev júnior "vai lá renomear isso em todo lugar" e sair por uma hora.
A versão curta: agents falham em grandes refatorações por uma razão estrutural, não de inteligência — a mudança é maior do que o contexto ao qual eles conseguem prestar atenção de forma confiável, e nada os força a verificar antes de derivar. Você corrige com escopo: planeje primeiro, divida o trabalho em etapas verificáveis de forma independente, rode uma verificação determinística entre cada uma, e mantenha uma lista de verificação escrita que o agent relê em vez de lembrar. Quando a mudança é mecânica, pule o agent e use um codemod.
Por que as rodas saem do trilho
Três coisas se combinam numa grande refatoração, e todas remetem ao contexto.
A janela enche e a atenção se deteriora. O próprio doc de melhores práticas da Anthropic é direto: "A janela de contexto do Claude enche rápido, e a performance se degrada conforme ela enche... Claude pode começar a 'esquecer' instruções anteriores ou cometer mais erros" (Claude Code best practices). Isso não é exclusivo do Claude. O efeito "perdido no meio" — onde modelos prestam menos atenção às informações no meio de um contexto longo — é bem documentado, e pesquisas mostram que a degradação pode começar muito antes da janela estar cheia. Conforme a refatoração avança, cada arquivo lido e cada saída de comando se acumula na mesma janela finita, empurrando suas instruções originais e os invariantes que você declarou para o meio de baixa atenção.
A janela efetiva é menor do que o rótulo diz. Usuários do Cursor se deparam com isso constantemente: um modelo com janela anunciada de 200K frequentemente tem apenas ~40–60K tokens utilizáveis uma vez contabilizados o overhead do harness, o system prompt e as saídas de ferramentas, e "no terceiro ou quarto passo, o agent está operando com consciência degradada do que fez no passo um" (Morph, Augment Code on Cursor multi-file refactors). Uma refatoração que abrange 50 arquivos com 200K tokens de fonte simplesmente não cabe, então os arquivos 31–50 são editados por um agent que já esqueceu os arquivos 1–30.
Contratos entre arquivos são invisíveis para um editor de um arquivo por vez. Todo o risco de uma refatoração vive nas costuras: o caller que assumiu o formato de retorno antigo, a fixture construída em torno do nome de campo antigo, o serializer que ainda escreve a chave antiga. O agent edita o arquivo A corretamente e o arquivo B corretamente, mas o contrato entre eles é algo que nenhum arquivo individual expressa. É aí que moram as mudanças aplicadas pela metade e os invariantes quebrados.
Para um rename limpo como o meu, a primeira pergunta honesta é se um agent deveria tocar nisso.
Use um codemod quando a mudança é mecânica
Se a transformação é estrutural e baseada em regras — renomear um símbolo, mudar uma assinatura de chamada, trocar um import — um codemod AST determinístico bate um agent em cada eixo que importa. Ferramentas como jscodeshift operam na árvore sintática abstrata, então aplicam exatamente a mesma transformação a cada nó correspondente, em paralelo, por todo um codebase "em segundos," sem chance de dois arquivos divergirem (Toptal, Martin Fowler on codemods). Um agent, em contraste, "também terá chance de alucinação, dando resultados levemente diferentes a cada vez, e é lento e caro" num codebase grande (Carlos Cuesta).
O movimento que realmente usa a força do agent: fazer com que ele escreva o codemod, não que faça a edição. Você descreve a transformação em português, ele gera a transformação jscodeshift, você testa em três arquivos, depois roda deterministicamente nos sessenta. Você tem a fluência do agent em expressar a regra e a garantia da ferramenta AST de que a regra se aplica de forma idêntica em todos os lugares.
# O agent escreve transforms/account-to-workspace.js, você roda:
npx jscodeshift -t transforms/account-to-workspace.js src/ --dry # preview
npx jscodeshift -t transforms/account-to-workspace.js src/ # aplicar
git diff --stat # 60 arquivos, uma transformação determinística
Meu rename era 80% mecânico. A decisão certa era um codemod para os renames e um agent apenas para os ~12 arquivos que precisavam de julgamento — o serializer, a migração, os lugares onde Account e Workspace precisavam coexistir durante uma transição. Recorra ao agent onde a mudança requer ler intenção, não onde requer aplicar uma regra.
Planeje primeiro, num modo que não consegue editar
Quando a mudança genuinamente precisa de um agent, não deixe que ele comece a editar no turno um. A maior alavanca é separar exploração e planejamento da execução, para que o agent se comprometa com uma abordagem antes de tocar no disco.
No Claude Code isso é o modo plano (Shift+Tab, ou --permission-mode plan): o agent lê arquivos, rastreia imports e produz um plano sem fazer nenhuma edição, e você aprova primeiro (common workflows). O Aider tem a divisão equivalente — /architect para planejar a reestruturação, depois /code para executar (refactoring a large codebase with aider). O ponto não é cerimônia. Deixar um agent "pular direto para codificar pode produzir código que resolve o problema errado," e um plano que você lê em dois minutos detecta isso antes que custe uma hora de edições (best practices).
O doc traça uma linha clara sobre quando se preocupar: "Se você consegue descrever o diff em uma frase, pule o plano." Uma refatoração de múltiplos arquivos nunca cabe em uma frase. Planeje.
Um detalhe útil para grandes trabalhos: não aceite o primeiro plano que se decompõe em "editar todos os 60 arquivos." Empurre para refatorar na ordem de dependências — comece com os módulos dos quais nada mais depende e trabalhe para fora — e divida o trabalho em fases pequenas o suficiente para verificar. Na prática, agents permanecem confiáveis em aproximadamente 20–40 arquivos numa passagem; além disso, o consenso é dividir em fases e rodá-las como sessões separadas com um commit entre cada uma (Claude Code multi-file refactoring walkthrough).
Divida em etapas verificáveis de forma independente com uma barreira entre cada uma
Esta é a parte que realmente te salva. Uma refatoração que o agent faz numa única varredura indiferenciada não tem checkpoints internos, então o primeiro erro corrompe silenciosamente tudo que vem depois. Uma refatoração cortada em etapas — cada uma terminando numa verificação que passa ou falha — não consegue propagar um erro além da barreira que o captura.
A Anthropic enquadra toda a disciplina em torno disso: "Claude para quando o trabalho parece feito. Sem uma verificação que possa rodar, 'parece feito' é o único sinal disponível, e você se torna o loop de verificação." Dê a ele uma verificação que retorne pass/fail — uma suite de testes, um código de saída do build, um typecheck — "e o loop se fecha sozinho" (best practices). A verificação converte "o agent acha que está pronto" em "o typechecker concorda que está pronto."
Aqui está o plano em etapas que eu deveria ter usado para o rename Account → Workspace, na ordem de dependências, com uma barreira após cada etapa:
PLANO: Rename Account -> Workspace. Commit após cada etapa que passar a barreira.
[ ] 1. Renomear o tipo + campos em src/domain/ (sem dependentes).
BARREIRA: tsc --noEmit limpo em src/domain/. Commit.
[ ] 2. Atualizar a camada de dados (repositories, serializers) que importam o tipo.
BARREIRA: tsc --noEmit limpo; testes unitários para src/data/ passam. Commit.
[ ] 3. Atualizar call sites da camada service/API.
BARREIRA: tsc --noEmit limpo em todo o repo; testes de integração passam. Commit.
[ ] 4. Atualizar test fixtures + factories para os novos nomes de campos.
BARREIRA: suite de testes completa no verde. Commit.
[ ] 5. Atualizar componentes UI + quaisquer referências a strings restantes.
BARREIRA: tsc --noEmit limpo; build com sucesso; e2e smoke passa. Commit.
Cada etapa é pequena o suficiente para caber no contexto, termina numa barreira determinística e é commitada antes que a próxima comece. Se a etapa 3 joga noventa erros, você os vê na etapa 3 — com as etapas 1 e 2 já seguras no git — não no final em todos os sessenta arquivos. O commit importa tanto quanto a barreira: uma etapa falha está a um git reset de distância, não a um projeto de arqueologia.
Diga ao agent para rodar a barreira ele mesmo e mostrar a saída, não afirmar sucesso. "Peça ao Claude para mostrar evidências em vez de afirmar sucesso: a saída do teste, o comando que rodou e o que retornou" (best practices). Para garantir isso rigidamente, um Stop hook pode rodar a verificação como um script e recusar que o turno termine até que passe.
Mantenha a lista de verificação num arquivo que o agent não pode perder
O plano acima só ajuda se o agent ainda o tiver na etapa 5. Não vai ter, se ele viver só na conversa — na etapa 5 as etapas iniciais já saíram da janela, que é exatamente a falha com a qual começamos. Então a lista de verificação vai para um arquivo: PLAN.md ou REFACTOR.md no repositório, com o agent instruído a lê-lo, fazer a próxima etapa não marcada, rodar a barreira e marcar a caixa.
A Anthropic recomenda precisamente isso para trabalhos longos e exaustivos: para "migrações de código, corrigir numerosos erros de lint, ou rodar scripts de build complexos," faça o Claude "usar um arquivo Markdown (ou até um GitHub issue!) como lista de verificação e bloco de rascunho" (best practices). Um arquivo no repositório é memória externa. O agent o relê a cada turno, então o plano fica com atenção plena em vez de degradar no meio de um contexto sobrecarregado. As caixas marcadas são o registro durável do que está realmente feito — não o que o agent vagamente se lembra de ter feito.
Combine isso com higiene de contexto. Entre etapas — ou entre fases rodadas como sessões separadas — /clear para que a próxima etapa comece limpa, com apenas a lista de verificação e os arquivos da etapa atual carregados. Delegue qualquer exploração "vai lá ler como X funciona" a um subagent para que a leitura aconteça numa janela separada e só o resumo volte. (Esses dois têm seus próprios posts aqui — não vou re-discuti-los.)
Uma passagem de verificação que não escreveu o código
Uma última barreira, depois que as etapas estiverem feitas: revise o diff completo com um agent novo que nunca viu a implementação. Um revisor "rodando num contexto de subagent novo vê apenas o diff e os critérios que você dá, não o raciocínio que produziu a mudança, então avalia o resultado nos seus próprios termos" (best practices). Aponte para o plano:
Use um subagent para revisar o diff completo contra REFACTOR.md. Verifique se a mudança
de cada etapa está completamente aplicada — nenhum arquivo restando com o tipo antigo
ou os nomes de campos antigos, nenhum método ainda retornando o formato antigo. Reporte
apenas lacunas que quebrem a correção ou um requisito declarado, não estilo.
Essa última frase importa. Um revisor instruído a encontrar lacunas vai fabricá-las — "camadas de abstração extra, código defensivo e testes para casos que não podem acontecer" — então delimite-o a correção e contrato, ou você vai trocar uma refatoração limpa por uma semana de gold-plating (best practices).
O que continuo reaprendendo: o agent não é o gargalo numa grande refatoração — o escopo é. Um rename de 60 arquivos entregue de uma vez vai derivar, aplicar pela metade e mentir sobre estar pronto, não importa quão bom seja o modelo. O mesmo rename cortado em cinco etapas ordenadas por dependências, cada uma validada por um typecheck, cada uma commitada, rastreada num arquivo que o agent relê — esse termina, porque em nenhum momento pedi que ele retivesse mais do que conseguia. Planeje, fatie, coloque barreiras, escreva. E quando a mudança for apenas uma regra, escreva o codemod e deixe o AST fazer a parte em que agents são piores.
