Como montar um projeto que decide se um pull request precisa de revisão humana

Revisão automática de pull request aqui não é um bot que escreve comentário bonito: é um classificador. Você monta um estado enxuto com metadados do PR (via gh pr view --json), lista de arquivos tocados (via gh pr diff --name-only) e um resumo do diff, manda pro Jev, o modelo de decisão tipada da TypeSafe AI, e recebe de volta valor estruturado com probabilidade e confiança. A partir daí você roteia: aprovar automaticamente, pedir revisão humana ou bloquear. O post monta o projeto passo a passo e mostra onde essa triagem quebra.
Fala aí, beleza? Se a fila de pull request do teu time parece infinita, o problema quase nunca é a quantidade: é que a maior parte dela é bump de dependência, ajuste de texto e teste novo, e o revisor humano gasta a atenção dele ali em vez de gastar no PR que mexe no fluxo de pagamento
A ideia deste projeto é simples: colocar um triador na frente da fila
E o candidato natural pra isso não é um LLM que escreve parecer
É o Jev, o primeiro modelo System One da TypeSafe AI, anunciado em setembro de 2026. Ele não gera texto: recebe um estado e perguntas tipadas, e devolve valores estruturados com probabilidade e confiança
Ou seja, ele não te conta uma história sobre o teu PR, ele te devolve uma decisão que teu código consegue ler num if
Bora montar? O objetivo é classificar cada PR em três rotas: aprovar automaticamente, pedir revisão ou bloquear
O que você precisa antes de começar
A lista é curta, e isso é parte da graça
- Python 3.10 ou superior, que é o mínimo exigido pelo SDK
- SDK instalado:
pip install typesafe-sdk(ouuv add typesafe-sdkse tu já vive no uv) - Chave na variável de ambiente
TYPESAFE_API_KEY - GitHub CLI autenticado, porque todo o estado do PR vai sair de
gh pr viewegh pr diff
Se tu preferir não usar o SDK, dá pra falar HTTP direto com a API System One:
curl -X POST https://api.typesafe.ai/v1/systemone \
-H "Authorization: Bearer $TYPESAFE_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"state": { "titulo": "chore: bump eslint" },
"model": "jev-latest",
"questions": { }
}'
Toda requisição tem os mesmos três campos de topo: state (o conteúdo a avaliar), model e questions (o mapa de perguntas tipadas)
O Jev também está disponível via OpenRouter, com as entradas typesafe/jev-1.13 e jev-latest, caso tua stack já roteie tudo por lá
E tem um atalho pra quem vive dentro de agente de código: a TypeSafe publica uma skill oficial, e a doc dela lista os comandos. No Claude Code é claude plugin marketplace add typesafe-ai/skills e depois claude plugin install typesafe@typesafe-ai, e o plugin é invocado por /typesafe:typesafe-ai. Em outros agentes, npx skills add typesafe-ai/skills --skill typesafe-ai
A instalação é project-local por padrão, e tem -g pra global, tudo conforme a mesma doc da skill. Se tu já brincou de montar agentes no Claude Code, o fluxo é exatamente o mesmo de sempre
Formação Claude Code
Domine Claude Code do absoluto zero até o avançado
- 120 aulas
- 4 projetos
- 9h 45min
Passo a passo: do pull request à decisão tipada
A regra de ouro do projeto inteiro: o modelo só é tão bom quanto o estado que tu monta
A doc é bem direta nisso, a acurácia cai conforme o estado se enche de conteúdo irrelevante, então a filtragem é feita no teu código, antes da chamada
- Extrair os metadados do PR
gh pr view 482 --json title,body,labels,author,baseRefName,changedFiles,additions,deletions,files
Esse JSON já te dá quase tudo que interessa pra uma triagem: o que o PR diz que faz, quem abriu, pra qual branch vai e o tamanho da mudança
O erro comum deste passo: mandar o body inteiro cru pro estado. Descrição de PR costuma vir com template gigante, checklist e link de ticket, e isso é peso morto pro modelo
- Puxar a lista de arquivos tocados, já filtrada
gh pr diff 482 --name-only \
--exclude '*.generated.*' \
--exclude '*.lock'
A flag --exclude aceita glob e é repetível, então tu corta arquivo gerado e lockfile antes de qualquer coisa chegar na API
O erro comum deste passo: deixar lockfile passar. Um package-lock.json sozinho consegue inflar o diff e empurrar a decisão pro lado errado, porque o modelo vê um volume de mudança que não representa risco nenhum
- Montar o
statecomo objeto estruturado
O state aceita string, objeto, array ou null, e a doc recomenda objeto estruturado pra qualquer requisição não trivial
state = {
"pr": {
"titulo": pr["title"],
"branch_destino": pr["baseRefName"],
"labels": [l["name"] for l in pr["labels"]],
"linhas_adicionadas": pr["additions"],
"linhas_removidas": pr["deletions"],
"arquivos_alterados": pr["changedFiles"],
},
"arquivos": arquivos_filtrados, # saída do gh pr diff --name-only
"resumo_diff": resumo_por_arquivo, # hunks encurtados, sem o diff inteiro
"contexto_do_repo": {
"areas_criticas": ["src/auth/", "src/billing/", "db/migrations/"],
},
}
Repara no contexto_do_repo: é ali que entra o histórico que TU já tem, tipo o mapa de áreas que já deram incidente. O modelo não adivinha a convenção do teu monorepo, tu precisa contar
O erro comum deste passo: mandar número ou booleano puro como state. Isso retorna 422, o tipo simplesmente não é aceito. O estado é string, objeto, array ou null, e ponto
E tem o orçamento de contexto: no jev-1.13 são 64k tokens pra estado mais todas as perguntas juntas, e 32k pra estado mais a pergunta mais longa. Passou disso, a requisição falha
- Escrever as perguntas tipadas, todas na mesma requisição
Esse é o pulo do gato: o modelo ingere o estado UMA vez e avalia todas as perguntas em paralelo
from typesafe_sdk import Choice, Noul, Score, TypeSafeClient
questions = {
"rota": Choice(
instructions=(
"Classifique este pull request quanto à necessidade de revisão humana, "
"considerando os arquivos tocados em `arquivos`, o resumo em `resumo_diff` "
"e as áreas listadas em `contexto_do_repo.areas_criticas`"
),
criteria={
"aprovar_auto": "mudança de baixo impacto: documentação, testes, bump de dependência sem breaking change",
"pedir_revisao": "mudança de lógica de produto que não toca área crítica",
"bloquear": "toca área crítica, remove teste, altera migração ou mexe em permissão",
},
),
"toca_area_sensivel": Noul(
instructions="Algum arquivo em `arquivos` pertence a autenticação, migração de banco ou pagamento?"
),
"risco": Score(
instructions="Avalie o risco de este PR quebrar produção se for para main sem revisão humana"
),
}
Três detalhes que mudam o desenho:
- Choice devolve a opção de maior probabilidade mais todas as opções mapeadas em probabilidades que somam 1, com limite de 255 opções
- Noul é o sim/não: o valor vai de 0 a 1 e representa a probabilidade de a resposta ser sim, e ele não tem campo de confiança separado (Choice e Score têm)
- Score existe, mas a própria doc de jaggedness registra que os níveis de Score do jev-1.13 são fracos em calibração numérica. Trate como sinal de apoio, nunca como o número que decide sozinho
O erro comum deste passo: escrever instructions pedindo uma coisa e criteria pedindo outra. Instrução e critério brigando é uma limitação documentada do modelo, e o resultado é decisão instável entre requisições parecidas
- Chamar a API
client = TypeSafeClient() # usa TYPESAFE_API_KEY do ambiente
resposta = client.system_one(state=state, questions=questions)
Tem AsyncTypeSafeClient pra versão assíncrona, se o teu triador roda dentro de um serviço que já é async
- Ler a resposta e aplicar o roteamento por confiança
As respostas voltam sob as MESMAS chaves que tu escolheu (rota, toca_area_sensivel, risco), e Choice e Score trazem um confidence entre 0 e 1 derivado da distribuição de probabilidade
Aqui entra o padrão que a doc chama de confidence-gated routing, em três faixas:
| Faixa de confiança | O que a doc indica fazer | Como isso vira ação no PR |
|---|---|---|
| Alta | agir automaticamente | aplica a rota que o modelo escolheu |
| Média | seguir com cautela | aplica, mas só a rota conservadora (pedir revisão) |
| Baixa | não agir, mandar pra uma pessoa ou outro sistema | cai em revisão humana, sem label automático |
E o ponto que mais importa: o threshold é diferente por ação, conforme a consequência do erro
Errar pro lado de "pedir revisão" custa alguns minutos de alguém. Errar pro lado de "aprovar automaticamente" custa um deploy quebrado. Então o piso de confiança pra aprovar sozinho tem que ser o mais duro do projeto inteiro
O erro comum deste passo: usar um threshold único pras três rotas. Funciona nos testes, morre no primeiro PR ambíguo
- Devolver o resultado pro PR
A saída final é comentário ou label. Label é melhor: dá pra filtrar a fila por ela, dá pra medir depois, e não polui a timeline do PR
O erro comum deste passo: ligar merge automático no dia um. Não faça isso ainda, eu explico no fim do post
Onde esse triador rende mais (e onde não vale a pena)
O cálculo é bem favorável quando o volume existe
O Jev 1.13 no OpenRouter custa US$ 0,042 por 1 milhão de tokens de entrada e US$ 0,00 por 1 milhão de tokens de saída, e a TypeSafe estima custo médio em torno de US$ 0,0004 por decisão nos benchmarks publicados dela
A latência fica entre 70 e 500 ms ponta a ponta, o que significa que o triador cabe dentro de um step de CI sem ninguém reclamar do tempo de build 🙂
Rende MUITO em:
- Repositório com enxurrada de PR de dependência e documentação, onde a maior parte da fila é ruído
- Monorepo com áreas críticas mapeadas por caminho, porque aí tu passa
areas_criticasno estado e o modelo tem em que se apoiar - Priorização de fila, ordenando o que o humano olha primeiro em vez de bloquear qualquer coisa
- Gate em branch de release, onde a rota "bloquear" vira um sinal explícito de que aquilo precisa de olho humano antes do corte
Se tu quer aplicar isso em escala de organização e não em um repo só, vale ver como ativar checagens só nos repos certos antes de sair ligando em tudo
Rende pouco em:
- Time pequeno com poucos PRs por semana, onde o revisor já lê tudo em dez minutos e o triador só adiciona peça pra manter
- Repositório sem convenção de caminho, onde
src/tem de tudo e não existe área crítica identificável. Sem convenção, o estado fica pobre e a decisão fica no chute
Problemas comuns nessa integração e como resolver
Sintoma: a requisição falha em PRs grandes
Causa: tu mandou o diff inteiro. O orçamento é 64k tokens pra estado mais todas as perguntas, e 32k pra estado mais a pergunta mais longa
Solução: resumir o diff em código antes da chamada, guardando por arquivo só o que a pergunta precisa (caminho, tamanho da mudança, trechos curtos das áreas sensíveis)
Como prevenir: medir o tamanho do estado antes de enviar e cortar por prioridade, e não por ordem alfabética de arquivo. Lembrando que isso não é só evitar erro: a acurácia cai conforme o estado se enche de conteúdo irrelevante
Sintoma: 422 na resposta
Causa: state em tipo inválido. Número ou booleano puro não passa
Solução: embrulhar em objeto, sempre. Mesmo que tu esteja avaliando uma coisa só
Como prevenir: padronizar um builder de estado no projeto, com a serialização acontecendo em um lugar só
Sintoma: a mesma classe de PR cai ora em pedir_revisao, ora em bloquear
Causa: instructions e criteria pedindo coisas diferentes, que é uma confusão documentada do modelo
Solução: deixar a instrução como enquadramento (o que avaliar, olhando quais campos) e os critérios como a régua de cada opção, sem repetir regra nos dois
Como prevenir: revisar a pergunta como se fosse código: instrução e critério são responsabilidades separadas
Sintoma: um PR nitidamente arriscado volta como aprovar_auto
Causa: conteúdo do próprio diff ou da descrição empurrando a resposta. O estado não é tratado como hostil por padrão, e conteúdo adversarial consegue mover o resultado
Solução: nunca colocar body do PR e comentário de código no estado sem sanitização, e manter a decisão de "aprovar automaticamente" amarrada a sinais que vêm de metadado (caminho de arquivo, label, branch), não de texto livre escrito por quem abriu o PR
Como prevenir: pensar o estado como input de usuário. Porque é exatamente isso que ele é 😀
Sintoma: a pergunta "não toca autenticação?" devolve o oposto do esperado
Causa: o jev-1.13 lê a pergunta de forma bastante literal, e escopo, negações e condições implícitas vão ao pé da letra
Solução: escrever pergunta afirmativa e direta, sem negação e sem "exceto quando". Se precisa de duas condições, faça duas perguntas (elas rodam em paralelo de qualquer jeito)
Até onde dá para confiar: os limites de julgar um PR sem ler o código inteiro
Agora o veredito honesto, porque prometi isso no começo
No benchmark interno da TypeSafe, feito sobre 4 fluxos de produção, o Jev marcou 67,8% de acurácia. Vale dizer com todas as letras: é benchmark do próprio fabricante, e não existe métrica pública de acurácia do Jev especificamente em code review ou análise de diff
Ou seja, tu não sabe qual é o número NA TUA tarefa. Tu só sabe que não é 100%
Soma a isso as limitações que a própria TypeSafe registra na página de jagged edges do jev-1.13: dificuldade com tarefas que exigem níveis extras de indireção, leitura bastante literal da pergunta e dificuldade com precisão numérica
Indireção é justamente o coração do code review
"Essa função mudou de assinatura, quem chama ela quebra?" é uma pergunta de dois saltos, e você não colocou os chamadores no estado, porque não caberia. Então o modelo está julgando a SUPERFÍCIE do PR: onde mexeu, quanto mexeu, o que o repo diz sobre aquela área
É triagem e priorização
Não é revisor
A postura que sai disso é bem concreta: aprovar_auto fica com o threshold mais duro do projeto, e baixa confiança cai em revisão humana por padrão, sempre. O custo de errar pra mais é uma pessoa olhando um PR bobo; o custo de errar pra menos é produção
Um triador que acerta a maioria e manda o resto pro humano já devolve a tarde do teu time. Um triador que aprova sozinho pra parecer esperto devolve incidente…
Próximo passo
Recapitulando o desenho, que é o que realmente importa aqui:
estado enxuto (filtrado em código, nunca o diff cru) + perguntas tipadas na mesma requisição (Choice pra rota, Noul pro sim/não, Score só como apoio) + roteamento por confiança com threshold diferente por ação
O resto é encanamento de gh e de label
E o próximo passo prático, antes de ligar QUALQUER automação: roda em modo sombra por algumas semanas. O triador decide, grava a decisão num log, e não faz nada no PR. Aí tu compara com o reviewDecision real, que também sai do gh pr view --json
Depois de algumas dezenas de PRs tu vai ter a tua própria taxa de acerto, na tua base, com a tua convenção de pastas. Essa é a única acurácia que vale pra decidir se o "aprovar automaticamente" pode existir no teu repo
Pra afinar o uso do modelo dentro de agente de código, as skills oficiais estão em typesafe-ai/skills
Até o próximo post! =)
Perguntas frequentes
Quanto custa rodar revisão automática de pull request com o Jev em volume alto?
A TypeSafe estima um custo médio bem baixo por decisão nos benchmarks publicados dela, e os preços por token do jev-1.13 no OpenRouter estão na seção de custo aqui do post. Pra um fluxo de triagem de PR, que manda um estado enxuto por chamada, isso fica bem baixo comparado a rodar um LLM generativo pra escrever parecer.
Dá pra avaliar vários critérios do mesmo pull request numa chamada só?
Dá sim. A API ingere o state uma única vez e avalia todas as questions em paralelo, então rota de aprovação, se toca área sensível e outros checks podem ir juntos na mesma requisição. É exatamente esse desenho que sustenta o passo 4 do projeto, com Choice e Noul na mesma chamada.
Posso confiar na decisão do Jev sem nenhuma revisão humana no meio?
Não é essa a proposta. A doc descreve um padrão de confidence-gated routing em três faixas (alta, média, baixa), com threshold por ação conforme a consequência do erro. E, como mostro na seção sobre os limites, o único número de acurácia que existe vem do benchmark do próprio fornecedor, o que reforça manter a faixa baixa sempre indo pra um humano.
Qual a diferença entre usar Choice e Noul pra decidir a rota de um PR?
Choice devolve a opção de maior probabilidade entre até 255 alternativas, com a distribuição completa somando 1 e um confidence de 0 a 1. Noul é a pergunta binária: um valor de 0 a 1 representando a probabilidade de sim, sem campo de confidence separado, diferente de Choice e Score.
O que acontece se o diff do pull request for grande demais pro modelo avaliar?
O jev-1.13 tem orçamento de 64k tokens pra estado mais todas as perguntas juntas, e 32k pra estado mais a pergunta mais longa. Passar desse limite faz a requisição falhar, por isso o passo de filtrar arquivos com gh pr diff –name-only e –exclude antes de montar o state é tão importante.
Existe algum tipo de PR que costuma confundir o Jev na hora de classificar?
Sim, a documentação lista jagged edges do jev-1.13: leitura bastante literal de negações e condições implícitas, dificuldade com níveis extras de indireção, precisão numérica fraca (inclusive na calibração do Score) e o estado não é tratado como hostil por padrão. Vale considerar isso ao escrever as instructions de cada Choice.
Formações
Formação Vibe Coding
Do Prompt ao Produto: Crie Software Real com IA
- 474 aulas
- 20 projetos
- 39h 27min
Blog | Mais populares
O que significa “ChatGPT network error” e como resolver
O “ChatGPT Network Error” é uma ocorrência frequente na rotina de muitos usuários do ChatGPT. Porém, poucos compreendem seu significado, quando esse erro surge, etc. […]

Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação
Checklist de segurança n8n VPS pública: guia essencial para proteger sua instalação A popularidade da automação de processos com o n8n está em alta, principalmente […]

Como usar o Antigravity do Google: guia completo do zero ao primeiro app
Aprenda neste guia prático como usar o Antigravity do Google: descubra a instalação, configuração, criação de projetos com o Agent Manager e o primeiro deploy, […]
