Refactor não tem sintomas — você não vê quando o Claude erra. Três guardrails: testes, commits atômicos, clique manual.
Refatorar é a tarefa em que o Claude é mais perigoso. Um bug tem sintomas — o botão não faz nada, o valor é undefined, stack trace — então dá pra saber se o fix do Claude funcionou. Um refactor não tem. "Ainda roda" pode significar "os testes ainda passam" enquanto o comportamento mudou em silêncio, você não percebeu, e produção explode uma semana depois.
Fiz um refactor considerável com o Claude no how2claude recentemente: migrei os pagamentos crypto x402 de um PaymentHandler + FacilitatorClient feitos à mão (139 linhas) para a gem x402-rails e, ao mesmo tempo, extraí o mapeamento de campos de Purchase.create! / Subscription.create! — duplicado em dois controllers — para métodos de classe do model. Um commit, 4 arquivos alterados, 2 apagados, 2 adicionados.
Meu prompt foi uma palavra: "refactor."
Podia ser tão curto porque havia guardrails ao redor.
Quando a branch chegou nesse ponto, tinha 221 testes. Todos os caminhos críticos do fluxo de pagamento cobertos.
A ação padrão do Claude antes de um refactor não é "olhar os testes primeiro." Então mandei ele rodar bin/rails test antes, confirmar verde, e aí sim mexer em algo.
Depois do refactor, rodar de novo. Ainda verde. Isso não significa zero regressão — significa que o comportamento conhecido não quebrou.
Se seu codepath não tem cobertura, faça o Claude escrever um teste mínimo que trave o comportamento atual. Rodar, comitar. Aí sim refatorar. Senão o que ele está fazendo não é refactor, é reescrita — e você não tem como verificar equivalência.
Esse refactor eram na verdade duas coisas:
Purchase / Subscription: controller → métodos de classe do modelHavia uma terceira no frontend: reescrever o fluxo de assinatura JS com viem + x402-fetch.
Fiz o Claude dividir pelas fronteiras naturais: backend + extração de model em um commit (9f3e239), frontend em outro separado (93746d8). Cada commit carrega descrição completa, lista de arquivos, e o porquê da mudança.
Benefícios:
- Diffs continuam legíveis. Um commit, uma coisa.
- Granularidade de rollback. Se produção pega um bug de frontend, git revert 93746d8 reverte só o frontend, deixando o backend.
- A atenção do próprio Claude fica focada. Um commit, uma coisa — e a atenção dele cobre só aquela coisa.
Uma vez completo o refactor, faço o Claude parar e me mostrar git diff --staged. Não roda os testes. Não roda a app. Lê o diff primeiro.
Sinais que eu procuro:
app/services/x402/payment_handler.rb apagado inteiro — ok, esse é o ponto de migrar para a gem. Mas se apagou algo que eu não pedi pra mexer, paro e pergunto.Purchase.create!(wallet_address: verify_result["payer"], ...) → Purchase.record_x402!(payment:, settlement:) agora lê payment[:payer]. A fonte mudou (request.env da gem vs retorno do client antigo), mas os campos têm que mapear um-pra-um.Armadilha 1: Os Stimulus controllers da gem não carregaram em silêncio
A gem x402-rails traz seus próprios Stimulus controllers. O Claude escreveu o código, os testes passaram verdes. Cliquei manualmente no botão de pagamento — nada.
Motivo: config/importmap.rb tinha um pin pro @hotwired/stimulus apontando pra um arquivo vendor que não existia, e o importmap descartou o pin em silêncio. Os controllers da gem nunca carregaram. Testes não pegam isso porque bin/rails test não executa JS.
Armadilha 2: YAML parseou 0x... como inteiro
wallet_address: 0x833589... — sem aspas. YAML viu o prefixo 0x, leu como inteiro hexadecimal. O facilitator recebeu uma não-string e rejeitou. O Claude não parou pra pensar nas regras de parse do YAML enquanto escrevia a config.
As duas armadilhas foram pegas porque eu cliquei no botão real. Teste passando não é a mesma coisa que feature funcionando. Verificação manual depois de um refactor não é opcional.
Refactor é o cenário "deixa o Claude dirigir" de mais alto risco. Os guardrails não são pro Claude. São pra você — pra quando o Claude errar algo, você pegar em 5 minutos em vez de num incêndio de produção.