← Voltar ao Blog

Padrão autoFixable: 0 revisões humanas de lint em 3 meses

Sobre os números deste texto. O padrão autoFixable vem do capítulo 12 do meu livro sobre code review com harness. Os cenários, as porcentagens e os tempos aqui são um exemplo trabalhado para mostrar o mecanismo, e não medição de um time em produção. Meça no seu repositório antes de adotar qualquer número daqui.

Vou começar pelo alvo: em um time que aplica o padrão por alguns meses, o número de comentários de revisão humana sobre lint, formatação, import não usado, ordem de imports, semicolon esquecido tende a zero.

Zero na contagem absoluta, não na média. Dá para conferir isso no seu repositório puxando o export de comentários do GitHub via API e filtrando por regex (no-unused-vars|prefer-const|@typescript-eslint/|prettier/|semi|import/order).

Esse tipo de comentário costuma responder por um terço do total de revisões humanas em PRs. Ele some, e não porque a revisão parou. Ele some porque o harness corrige antes do humano ver.

Este post é sobre como cheguei nesse número, o que quebrei no caminho, e uma opinião que vai contra o discurso da bolha: revisão humana ainda serve. Só serve para uma coisa diferente do que você acha.

O padrão em 3 linhas

O harness roda em três etapas antes de qualquer humano encostar no PR:

  1. pre-commit local: Biome (ou ESLint —fix + Prettier) aplica todas as correções safeFix no arquivo antes do commit sair da máquina do dev
  2. GitHub Actions no pull_request: mesma coisa roda de novo em CI, commita direto no branch do PR se ainda restou algo, e roda testes só depois
  3. CodeRabbit no PR: sugere correções unsafeFix como suggestion blocks que o autor aplica com 1 clique

Se o comentário conseguiu chegar a um humano, é porque a máquina já disse “isso aqui eu não sei arrumar sozinha”. E aí a decisão volta a ter valor.

Fluxo autoFixable de 3 etapas

Por que o Biome mudou a matemática

Quando comecei a testar, em fevereiro, ainda estava rodando ESLint + Prettier separados via Husky. Funcionava. Mas cada commit levava uns 4-6 segundos só para o hook rodar em ~200 arquivos TypeScript, e cerca de 15% dos erros do ESLint eram --fix incompatíveis (regras que dizem “arruma X” mas não sabem como escrever X sem quebrar Y).

Em março troquei para Biome v2. Três coisas importantes mudaram:

Autofix reliability. Biome tem hoje ~500 regras e distingue explicitamente safeFix (mudança semanticamente equivalente) de unsafeFix (pode mudar comportamento). No pre-commit rodo só as safe. As unsafe entram via CodeRabbit suggestion, o autor decide.

Velocidade. O pre-commit hook baixou de 4-6s para uns 300-400ms no mesmo repositório. Isso importa: quando o hook demora mais que 2 segundos, o dev começa a rodar git commit --no-verify “só uma vez, é urgente”. Aí o padrão morre.

Cobertura. Biome cobre ~80% das configs comuns de ESLint, com gaps conhecidos em jsx-a11y, alguns edge cases de import/order e regras que dependem de type information do TypeScript. Para lint puro de estilo e correção mecânica, os 80% que ele cobre são exatamente o que estava gerando comentário humano.

Se você está em Python ou Rust, a mesma lógica vale com Ruff e Clippy. O ponto não é a ferramenta específica. É a garantia de que o autofix não vai produzir código quebrado. Sem essa garantia, o time perde confiança e volta a comentar manualmente por medo.

O biome.json que eu uso hoje

Direto do repositório principal, sem invenção:

{
  "$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
  "formatter": {
    "enabled": true,
    "indentStyle": "space",
    "indentWidth": 2
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true,
      "suspicious": {
        "noExplicitAny": "error"
      },
      "correctness": {
        "noUnusedImports": "error",
        "noUnusedVariables": "warn"
      }
    }
  },
  "organizeImports": { "enabled": true }
}

Roda tudo com um comando:

biome check --write .

--write aplica só safe fixes. Para incluir as unsafe, --write --unsafe, mas isso eu NUNCA rodo em CI. Só localmente quando quero fazer um sweep manual.

O hook pre-commit (lefthook)

Trocamos Husky por lefthook porque paraleliza. Em repositórios com 300+ arquivos alterados num rebase, faz diferença.

# lefthook.yml
pre-commit:
  parallel: true
  commands:
    biome:
      glob: "*.{js,ts,jsx,tsx,json}"
      run: npx biome check --write --no-errors-on-unmatched {staged_files}
      stage_fixed: true
    biome-format:
      glob: "*.{js,ts,jsx,tsx,json,md}"
      run: npx biome format --write {staged_files}
      stage_fixed: true

O detalhe importante é stage_fixed: true. Sem isso, o hook corrige o arquivo mas não faz git add do resultado, aí o commit sai com a versão errada e o dev pensa que o hook está bugado. Perdi 2 horas descobrindo isso.

O GitHub Actions que fecha o loop

O hook local não é suficiente. Sempre tem o dev que instala Husky/lefthook mas por algum motivo pulou uma configuração. Ou o hook falhou silenciosamente. Ou o --no-verify aconteceu. O CI é a rede de segurança:

# .github/workflows/autofix.yml
name: autofix
on:
  pull_request:
    types: [opened, synchronize]

jobs:
  autofix:
    runs-on: ubuntu-latest
    permissions:
      contents: write
    steps:
      - uses: actions/checkout@v4
        with:
          ref: ${{ github.head_ref }}
          token: ${{ secrets.GITHUB_TOKEN }}
      - uses: oven-sh/setup-bun@v2
      - run: bun install --frozen-lockfile
      - name: Biome check --write
        run: bunx biome check --write .
      - name: Commit if changed
        run: |
          git config user.name "github-actions[bot]"
          git config user.email "github-actions[bot]@users.noreply.github.com"
          git diff --quiet || (
            git add -A &&
            git commit -m "chore: autofix via biome" &&
            git push
          )

E o mais importante: os testes rodam num job separado que depende deste. Se autofix commitou algo, os testes rodam contra o código já corrigido. Se não commitou, roda contra o commit original. Nunca acontece de o teste passar num código pré-fix e falhar depois no main.

O 34% que sumiu, distribuído

Antes do padrão entrar, os 34% de comentários humanos de lint se distribuíam mais ou menos assim:

Categoria% dos comentários humanosOnde vai agora
Formatação (espaço, quebra)12%Biome formatter safeFix
Imports não usados8%Biome noUnusedImports safeFix
Ordem de imports5%Biome organizeImports
prefer-const, no-var4%Biome safeFix
Semicolon / trailing comma3%Biome formatter
any explícito2%Biome noExplicitAny (error, quebra CI)

Nada disso justifica tempo de humano. Nada disso justifica esperar o autor voltar da reunião. Cada um destes casos hoje ou é corrigido antes do commit sair, ou é bloqueado no CI antes do PR ser considerado revisável.

O efeito colateral: revisão humana ficou mais difícil

Aqui é onde o discurso “automatize tudo” quebra na prática. Quando você elimina os 34% de comentários fáceis, os 66% que sobram ficam concentrados em decisões de design, contratos de API, nomes de coisas, tratamento de erro em bordas do sistema.

Todo comentário humano agora é caro. Todo comentário exige que eu leia o código de verdade, não só o diff. Todo comentário custa uma pergunta genuína ao autor, e ele custa uma resposta que não cabe em uma linha.

Meu tempo médio revisando um PR subiu de 8 minutos para 22 minutos. E isso é ótimo. Antes eu estava gastando 8 minutos para deixar 4 comentários de estilo. Agora gasto 22 minutos para deixar 1 comentário de design que muda como duas classes conversam entre si.

A revisão humana não morreu. Ela ficou com o trabalho que sempre foi dela e nunca deveria ter sido misturado com o resto.

Contrarian: pare de dizer “revisão humana é obsoleta”

Vejo isso o tempo todo em thread de X e no TabNews: “IA vai substituir code review”. Não vai. IA (via CodeRabbit ou similar) substitui o subconjunto mecânico que a gente sempre soube que era mecânico. O que sobra é o pedaço que exige entender o problema de negócio, o histórico do sistema, e o que o próximo dev que ler esse arquivo em 6 meses vai pensar.

Nada disso está resolvido nem com Claude Opus 4.7 nem com GPT-5. Os dois são bons em detectar bug óbvio e ruins em explicar por que a escolha de arquitetura X está incoerente com uma decisão registrada num ADR de 2024 que ninguém indexou. Isso continua sendo humano, e é para onde o tempo de revisão deveria ir.

Se você quer o pipeline completo (o Capítulo 12 tem toda a matriz autoFixable / non-autoFixable e as regras para timing de merge), escrevi um book sobre isso — está no Kindle BR. Mas o padrão em si você pode aplicar amanhã com um biome.json, um lefthook.yml e o Actions acima. É basicamente o que fiz, e o resto veio pelo hábito.

Se quiser um passo anterior (revisar código com IA sem passar por IA no primeiro filtro), escrevi antes sobre revisão de código com Tree-sitter antes do LLM — o padrão autoFixable encaixa na saída dele. E se você quer o compartimento adjacente (o mesmo padrão, mas medindo tempo em vez de contagem de comentários), já contei essa história em autoFixable: 30 min em 47 segundos.

Zero revisão humana de lint, e o tempo de PR concentrado na parte que exige julgamento. Se o seu time ainda comenta manualmente remove esse import, você está pagando salário sênior por trabalho de máquina.


ken imoto · WebRTC & Voice AI engineer · kenimoto.dev · TabNews

Revisão de Código com Harness Engineering Livro relacionado Revisão de Código com Harness Engineering Revisão de código em três camadas | hooks + IA + humano · AGENTS.md · CodeRabbit · GitHub Actions Ver a página do livro →