| name | systematic-debugging |
| description | Use quando encontrar qualquer bug, teste falhando, comportamento inesperado, erro de compilação persistente ou container que não sobe. Processo de 4 fases: investigar → analisar → hipótese → implementar. Proibido pular fases ou fazer tentativas aleatórias. |
Debugging Sistemático — 4 Fases
Derivado do systematic-debugging do Superpowers (Jesse Vincent / obra),
adaptado para as stacks e contexto deste projeto.
Regra central: nunca fazer mudança aleatória esperando que resolva.
Cada mudança deve ter hipótese clara e verificação do resultado.
Pular fase = investigação incompleta = bug volta ou piora.
Fase 1 — Reprodução confiável (não avance sem isso)
Antes de qualquer diagnóstico, reproduza o problema de forma consistente.
docker compose run --rm backend <comando-teste> -run TestNomeExato
docker compose logs backend --tail=50
docker compose ps
Critérios para avançar:
Se não conseguir reproduzir consistentemente: documentar as condições
em que aparece (flaky test? só em CI? só com dados específicos?) antes de avançar.
Fase 2 — Investigação da causa raiz (não dos sintomas)
Erro: tratar o sintoma. Se o log diz "connection refused", o sintoma é
"conexão recusada" — a causa pode ser container não iniciado, porta errada,
variável de ambiente vazia, ou banco ainda inicializando.
Correto: rastrear até a origem.
docker compose logs backend 2>&1 | head -100
docker compose run --rm backend env | grep DATABASE
docker compose exec postgres pg_isready
docker compose run --rm backend <comando-migrate-status>
git log --oneline -10
git diff HEAD~1 -- <arquivo-suspeito>
Técnicas específicas por tipo de problema:
| Tipo | Onde olhar primeiro |
|---|
| Teste unitário falhando | Mock configurado errado? Interface do port mudou? |
| Teste E2E falhando | Estado do DOM assíncrono? Selector quebrou? Dados diferentes? |
| Container não sobe | docker compose logs <serviço> — ler as primeiras linhas do erro |
| 500 no backend | Stack trace completo nos logs + request que disparou |
| Frontend não renderiza | Console do browser → erros de rede → response da API |
| Migration falha | Conflito com migration anterior? Coluna já existe? |
| Build quebrado | Primeiro erro, não o último — compiladores encadeiam erros |
Critérios para avançar:
Fase 3 — Hipótese e teste mínimo
Antes de modificar qualquer código, formule uma hipótese explícita:
Hipótese: "O erro ocorre porque [causa específica].
Se eu [mudança mínima], o comportamento deve mudar para [resultado esperado]."
Teste mínimo primeiro: faça a menor mudança possível que valida ou
refuta a hipótese. Não corrija e refatore ao mesmo tempo.
docker compose run --rm backend env | grep DATABASE_URL
Se a hipótese for refutada: volte à Fase 2 com a nova informação.
Não construa sobre uma hipótese errada.
Critérios para avançar:
Fase 4 — Implementação da correção
Com a causa raiz confirmada, implemente a correção:
-
Corrija apenas o que causou o problema. Não refatore código adjacente
enquanto corrige um bug — isso mistura responsabilidades e dificulta
o review. Se notar algo errado por perto, anote para uma tarefa separada.
-
Escreva ou atualize o teste que teria capturado este bug.
Bug sem teste = bug que pode voltar silenciosamente.
-
Verifique defense-in-depth: o bug poderia ocorrer em outro lugar
pelo mesmo motivo? Se sim, corrija todos os pontos ou documente o padrão.
-
Verificação final:
docker compose run --rm backend <comando-teste> -run TestNomeExato
docker compose run --rm backend <comando-teste-completo>
Critérios de conclusão:
Armadilhas comuns — o que nunca fazer
| Armadilha | O que fazer em vez disso |
|---|
| Mudar configuração aleatória esperando que resolva | Fase 2 — identificar a causa antes de mudar qualquer coisa |
| "Acho que é X, vou corrigir X" sem verificar | Fase 3 — formular hipótese e testar com mudança mínima |
| Corrigir e refatorar na mesma mudança | Separar em dois commits: fix + refactor |
| Deletar e reescrever quando há bug | O reescrito vai ter os mesmos bugs se a causa raiz não for entendida |
| Pedir ao LLM "tenta outra abordagem" em loop | Voltar à Fase 2 — mais informação, não mais tentativas |
| Marcar como resolvido antes de verificar | Fase 4 sempre inclui verificação explícita |
Condition-based waiting (testes assíncronos)
Quando testes E2E falham por timing (elemento não encontrado, estado ainda carregando):
await page.waitForTimeout(2000)
await page.waitForSelector('[data-testid="grid-results"]')
await expect(page.getByRole('button', { name: 'Salvar' })).toBeEnabled()
await page.waitForResponse(resp => resp.url().includes('/api/v1/processos'))
A condição de espera deve ser o estado real que indica que a operação
completou — nunca um tempo fixo.