← Voltar ao Blog

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

Vou começar pela contagem: nos últimos 3 meses, no meu time de 4 devs, o número de comentários de revisão humana relacionados a lint, formatação, import não usado, ordem de imports, semicolon esquecido foi exatamente zero.

Não zero na média. Zero na contagem absoluta. Puxei o export de comentários do GitHub via API filtrado por regex (no-unused-vars|prefer-const|@typescript-eslint/|prettier/|semi|import/order) e passei 4 vezes achando que a query estava errada. Não estava.

Antes disso, esse tipo de comentário era 34% do total de revisões humanas em PRs. Sumiu. Não porque a gente parou de revisar — a gente revisa mais, e melhor. Sumiu 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. Testei os dois em PRs reais do meu time durante 2 semanas. Ambos são bons em detectar bugs óbvios e péssimos em explicar por que a escolha de arquitetura X está incoerente com uma decisão que ficou registrada num ADR de 2024 que ninguém indexou. Isso é humano. E é onde meus 22 minutos por PR vão hoje.

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.

3 meses, 0 revisões humanas de lint, 22 minutos por PR na parte que importa. Se seu time ainda comenta manualmente remove esse import, você está pagando salário sênior para trabalho de máquina.


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