O lgtmaybe põe um modelo a rever o teu código sem que uma linha saia da tua rede. E é por coisas assim que a soberania digital deixa de ser um slide.

Olá a todos.

Já conhecem a minha primeira camada: corro modelos locais, tenho um preconceito assumido a favor de tudo o que corre dentro de portas e desconfio, por defeito, de qualquer serviço que precise de levar os meus dados para o datacenter de outra pessoa para funcionar. A segunda camada é mais simples: não tenho relação nenhuma com o projeto de que vou falar, nem com o autor. Dei com ele, li a documentação de uma ponta à outra e gostei do que lá encontrei. Vale o que vale.

Há uma sigla que qualquer pessoa que já tenha trabalhado numa equipa de desenvolvimento conhece: LGTM. Looks good to me. Quatro letras que, numa sexta-feira ao fim da tarde, com um pull request de oitocentas linhas à frente e o fim de semana à espera, muitas vezes significam outra coisa: não li, mas confio em ti.

Cof cof, quem nunca?

O projeto desta semana chama-se lgtmaybe, e o nome é provavelmente a melhor linha de documentação que vi este ano. O autor resume-o no GitHub assim: “any model. no keys. maybe a merge.” O revisor acha que está bem. Talvez.

Ferramentas de code review com IA não faltam. O que me fez parar neste foi outra coisa. Quase todas partem do princípio de que o teu diff vai viajar até à API de alguém. O lgtmaybe trata essa viagem como uma opção. Pode correr inteiro dentro da tua rede, com o modelo num servidor teu, o repositório num Forgejo teu e o pipeline num runner teu. Se preferires um modelo na cloud, também dá. A diferença é que a escolha passa a ser tua, repositório a repositório, e deixa de ser uma condição imposta pela ferramenta.

Há uma semana escrevi sobre o ensaio do Satya Nadella e a ideia de que o que sai de um modelo é uma proposta até prova em contrário. O lgtmaybe é essa ideia aplicada a um problema muito concreto, com o “talvez” assumido logo no nome.

O que faz, em concreto

O lgtmaybe faz revisão de código com um LLM. Lê o diff de um pull request (ou merge request, conforme a casa) através da API do servidor Git, ou simplesmente o teu diff local, junta umas linhas de contexto à volta de cada alteração e pede ao modelo que procure problemas. Os resultados chegam como comentários na própria linha alterada, com uma severidade de info a critical, uma explicação e, às vezes, uma sugestão de correção. Se não encontrar nada, deixa um 👍 LGTM! no resumo. Funciona com GitHub, GitLab, Gitea, Forgejo (e portanto Codeberg) e Azure DevOps.

Dois pormenores de desenho dizem muito sobre quem o escreveu. Nunca faz checkout do código do pull request nem o executa. E comenta apenas linhas alteradas, deixando de fora ficheiros gerados, lockfiles, código vendorizado e binários.

A revisão está organizada por lentes, cada uma com uma pergunta focada que se faz ao modelo:

  • correção e segurança: erros de lógica, awaits esquecidos, injeção, autorização partida, segredos à vista, riscos na configuração de CI;
  • testes e documentação: comportamento alterado sem testes, APIs públicas por documentar, documentação que a alteração tornou falsa;
  • saúde do código: APIs obsoletas, dependências arriscadas, regressões de desempenho, complexidade a mais;
  • intenção: o código faz o que o título e a descrição do PR prometem?
  • arquitetura: decisões que vão sair caras de reverter, acoplamentos a atravessar fronteiras, dependências a apontar no sentido errado;
  • e a minha preferida, a lente ponytail, que procura código que não precisava de existir porque a biblioteca padrão, ou uma abordagem mais simples, já resolvia.

Conheço equipas que precisavam da lente ponytail ligada todas as segundas-feiras de manhã.

Por omissão corre o preset fast, que agrupa as categorias em cinco chamadas ao modelo. O --preset full corre cada categoria em separado, para uma auditoria mais funda. Segue-se uma passagem de reflexão, em que o modelo revê os próprios achados e deita fora os que não se aguentam de pé. E se tiveres ferramentas como ruff, bandit, mypy, semgrep, gitleaks, zizmor, ast-grep ou osv-scanner instaladas, o lgtmaybe corre-as e entrega os resultados ao modelo como pistas, para confirmar ou descartar. Gosto desta combinação. A análise estática é determinística e cega ao contexto; o LLM percebe o contexto e, de vez em quando, inventa. Juntos, um vigia o outro.

No terminal, lgtmaybe review imprime os resultados sem publicar nada em lado nenhum: texto legível, JSON, SARIF para quem usa code scanning, ou um formato pensado para ser lido por um agente de programação que depois aplica as correções. Num PR há comandos por comentário: /review, /ask para fazer perguntas sobre a alteração, /describe para gerar uma descrição estruturada e /diagram, que publica uma visão geral do risco da alteração, das zonas de maior impacto e um fluxograma em Mermaid. Este último, corrido localmente com lgtmaybe diagram antes de abrir o PR, é daquelas coisas que o colega que vai rever agradece em silêncio.

A parte que me chamou a atenção: o código não tem de sair de casa

Do lado dos modelos, o lgtmaybe fala com os suspeitos do costume na cloud (OpenAI, Anthropic, OpenRouter, Bedrock, Vertex, Azure, entre outros), com o ollama e com qualquer endpoint compatível com a API da OpenAI. Esta última opção cobre o llama.cpp, o vLLM e o LM Studio.

Com o ollama, o diff e tudo o que o acompanha vão apenas para o servidor que indicares com --api-base. Se esse servidor for a tua máquina, nada sai dela. Se for outra máquina da tua rede, ou um peer da tua tailnet, os dados vão para lá e para mais lado nenhum. O mesmo vale para um chama.cpp ou um vLLM que corras tu.

A documentação tem uma página inteira sobre dados e privacidade, coisa rara num projeto deste tamanho, e vale a pena lê-la com calma. Retive três coisas.

Mesmo quando usas um fornecedor externo, o lgtmaybe não envia o nome do repositório, o número do PR, os SHAs dos commits nem a identidade ou o email de quem fez commit, e nunca manda o repositório inteiro. Antes de qualquer chamada ao modelo, o diff passa por uma redação de segredos que troca chaves de cloud, tokens do GitHub, do Slack ou do npm, JWTs, chaves privadas e palavras-passe metidas em connection strings por [REDACTED], e isto acontece na primeira etapa do pipeline, antes de o prompt ser sequer montado. E as ferramentas de análise estática correm numa sandbox sem rede, pelo que ao modelo só chegam as pistas, também elas redigidas.

Há um pormenor quase escondido que mostra o grau de atenção. O diagrama publicado no PR traz um link para o abrir em ecrã inteiro no mermaid.live, e a fonte do diagrama vai no fragmento do URL, a parte depois do #, que o browser nunca envia ao servidor. Quem pensa nisto pensa no resto.

Sobre prompt injection, o lgtmaybe trata o diff como entrada não confiável do princípio ao fim. Embrulha-o em delimitadores próprios, neutraliza delimitadores forjados que alguém tente esconder no código e obriga a resposta do modelo a validar contra um esquema JSON estrito, rejeitando campos que não deviam lá estar. Há aqui um eco direto da conversa sobre o Nadella: o prompt de sistema diz ao modelo para ignorar instruções escondidas no diff, mas isso é uma sugestão. O controlo que conta é o esquema, porque está fora do modelo e o modelo não o consegue desligar.

Para experimentar no portátil, sem chave de API nenhuma:

O config init faz um pedido minúsculo ao modelo para confirmar que responde e guarda o fornecedor e o modelo, nunca a chave (quando há uma). Quem preferir ser explícito pode passar --provider ollama --model qwen3.6:27b diretamente. O --working inclui as alterações ainda por commitar e o --uncommitted revê apenas essas.

Quanto ao hardware, a documentação é honesta, o que agradeço. A sugestão para meados de 2026 é o Qwen3.6-27B, que a 4 bits ocupa uns 16 a 18 GB só em pesos. Com 32 GB de RAM ou VRAM estás no mínimo prático, com uma janela de contexto de 32k. Com 48 GB ou mais ficas confortável. A memória unificada dos Mac com Apple Silicon conta da mesma forma. Abaixo dos 32 GB, o conselho é descer para um modelo mais pequeno, como o gemma4:e4b, e aceitar que se vão perder os achados mais subtis.

O autor publicou também um benchmark com um modelo pequeno, o qwen3.5:4b, a rever ficheiros grandes bloco a bloco (o modo recursive, ligado por omissão) em vez de os rever inteiros. A recall subiu de 61% para 88%, e num caso de teste com seis bugs plantados apanhou os seis em vez de quatro. O próprio autor avisa que é uma única corrida, não determinística, e que deve ser lida como indicação de tendência e não como prova. Gosto que seja ele a dizê-lo.

Se tiveres uma máquina com GPU partilhada pela equipa, o mais prático é pôr o modelo lá e apontar todos os clientes para ela, no .lgtmaybe.yml do repositório:

E uma nota sobre desempenho, porque é aqui que muita gente vai desistir. Por omissão, o ollama atende um pedido de cada vez, por isso as cinco chamadas do preset fast ficam em fila. O OLLAMA_NUM_PARALLEL resolve, mas cada slot reserva a sua própria janela de contexto, e quatro slots de 32k pedem memória para 128k. O vLLM, com batching contínuo, é a escolha para quando a latência importa. O --profile mostra onde foi gasto o tempo.

Do portátil ao pipeline, sem passar por São Francisco

Rever no portátil antes de abrir o PR já é muito. Há hooks de pre-commit para tornar isso automático, e o formato --format agent permite que um agente de programação, também ele local se quiseres, leia os achados e aplique as correções antes de o código chegar a outro humano.

O passo seguinte é pôr o lgtmaybe no pipeline. No GitLab corre como um job de CI (lgtmaybe gitlab-ci). No Gitea e no Forgejo usa o mesmo contentor da GitHub Action, porque os runners reimplementam o mesmo runtime. No Azure DevOps corre como um job de Azure Pipelines. A pasta examples/ do repositório tem configurações prontas para cada um. Se o modelo correr no próprio host do runner, o contentor chega-lhe através de host.docker.internal. Se estiver noutra máquina, basta apontar o api_base para ela.

Quem leu o post do homelab sabe que o Forgejo é a minha sugestão por omissão para Git alojado em casa. Juntando as peças, a cadeia fica assim: o código sai do teu Forgejo, vai para o teu runner, é revisto por um modelo na tua GPU e volta como comentário no teu PR. Nenhum terceiro pelo meio. Quem preferir não alojar nada tem o Codeberg, mantido por uma associação sem fins lucrativos sediada em Berlim, com o mesmo suporte.

Com o GitHub.com a conversa é outra, e convém ser claro. Se o repositório vive no GitHub, o código já está nos servidores da Microsoft, e o lgtmaybe não muda isso. O que faz é não acrescentar mais um terceiro à lista. E nos runners alojados pelo GitHub um modelo local não faz sentido; a própria documentação encaminha quem quer modelos locais para o CLI ou para um runner próprio.

Há ainda duas funcionalidades que mostram que o projeto foi pensado para ser usado durante meses e não só experimentado num sábado. No GitHub e no GitLab, um 👎 num achado ensina o lgtmaybe a não repetir aquele tipo de ruído. E quando um commit posterior corrige o problema apontado, a conversa é resolvida automaticamente.

Porque é que isto é soberania, e não paranoia

Há quem ache que tudo isto é excesso de zelo. Afinal, o fornecedor garante nos termos de serviço que não treina com os dados enviados pela API. Vale a pena olhar para o que vai, de facto, dentro de um diff.

Um diff não é só código. É o mapa de um sistema: nomes de serviços internos, endpoints que não aparecem em documentação pública, a forma como a autenticação é feita, os sítios onde se validam (ou não) os dados que entram. É também, demasiadas vezes, fixtures com dados que alguém copiou de produção “só para testar”, comentários com o nome do cliente, queries com nomes de tabelas que dizem muito sobre o negócio. E um diff mostra precisamente a parte do sistema que está a mudar, que é quase sempre a parte menos testada. Para quem procura uma fraqueza, é um mapa do tesouro com um X desenhado no sítio certo.

Há umas semanas escrevi, a propósito da senhora da Florida que usava o Claude como diário, que um chatbot não é um caderno: tem leitores que não escolheste. Uma API de inferência é exatamente igual. Do outro lado estão os sistemas do fornecedor, os seus subcontratados, as suas políticas de retenção e, sobretudo, a jurisdição a que esse fornecedor responde. Uma empresa norte-americana está sujeita ao CLOUD Act, que permite às autoridades dos Estados Unidos exigir dados sob o seu controlo independentemente de onde estejam armazenados. Escolher a região de Frankfurt na consola não muda a nacionalidade de quem a gere.

Depois há a parte contratual, que é a que tira o sono a quem trabalha com clientes na banca, na saúde ou no setor público. Muitos desses contratos proíbem a partilha de código com terceiros, ponto. Enviar um diff para uma API externa é partilhar, digam os termos de serviço o que disserem sobre treino. E o RGPD não tem pausa para a hora do code review: se aquela fixture tem dados pessoais verdadeiros, acabaste de fazer uma transferência para um subcontratante que não estava no registo de atividades de tratamento. Com a NIS2 a apertar as obrigações sobre a cadeia de fornecimento, “o revisor automático manda o código para fora” é o tipo de frase que nenhum responsável de segurança quer ler num relatório de auditoria.

Mas soberania digital, para mim, é menos sobre onde ficam os dados e mais sobre quem decide. É poder escolher, e poder mudar de ideias sem ter de reescrever o processo. O lgtmaybe é MIT, funciona com qualquer modelo e com qualquer servidor Git, e mudar de fornecedor é mudar duas linhas num ficheiro YAML. As regras de revisão vivem no teu repositório, não na consola de alguém. É a mesma lógica dos controlos independentes de que o Nadella falava: a autoridade sobre o que a inteligência faz fica do teu lado, qualquer que seja o fornecedor da inteligência.

Nada disto é um argumento contra a cloud. Para um projeto open source, cujo código já é público, mandar o diff para o melhor modelo disponível é perfeitamente razoável, e provavelmente dá melhores resultados do que um modelo de 27B no teu servidor. O que importa é que essa decisão possa ser tomada repositório a repositório, por quem tem a responsabilidade, e não imposta por defeito pela ferramenta. A soberania constrói-se nestas decisões pequenas: este diff vai, aquele não vai.

Onde o lgtmaybe ainda não chega

Não há bela sem senão e seria pouco honesto acabar sem as reservas, e tenho algumas.

A primeira é o hardware. Um modelo que faz revisão a sério precisa de 32 GB como mínimo, e de preferência mais. Para uma equipa, isso é uma máquina com GPU dedicada, que custa dinheiro, gasta energia (em Portugal, a fatura da luz entra sempre na conta) e precisa de alguém que cuide dela. Os modelos mais pequenos funcionam, mas falham o que é subtil, e o subtil é exatamente o que um revisor humano cansado também falha.

A segunda é a velocidade. Um modelo local num servidor de um só slot transforma uma revisão de segundos numa revisão de minutos. Para pre-commit em cada commit isso pode ser demasiado. Na prática, vai acabar a correr no pipeline ou antes de abrir o PR, e não a cada gravação de ficheiro.

A terceira é a segurança do próprio pipeline, e esta é importante. No GitHub, o lgtmaybe usa pull_request_target, que corre no contexto do branch base, e por isso um PR malicioso vindo de um fork não chega aos segredos. Mas num merge request do GitLab, num pull_request do Gitea ou num build de PR no Azure Pipelines, a definição do pipeline vem da própria alteração. Quem consegue fazer push de um branch consegue reescrever o job que tem os segredos. O Gitea suporta pull_request_target desde a 1.21 e o workflow de exemplo usa-o; o GitLab e o Azure DevOps não têm equivalente. A documentação explica como mitigar em cada caso. Leiam essa secção antes de pôr isto em produção, não depois.

A quarta é a redação de segredos. É boa e cobre muitos formatos, mas é baseada em padrões, e o próprio autor diz que é uma defesa de melhor esforço. Não substitui um gitleaks no pre-commit nem, sobretudo, a disciplina de não pôr segredos no código. Com um modelo local o risco é menor, mas o hábito é o mesmo.

A quinta é a maturidade do projeto. À data em que escrevo tem pouco mais de 170 estrelas e três forks no GitHub, com um ritmo de desenvolvimento intenso mas, ao que parece, muito concentrado no autor. O bus factor é baixo. A licença MIT garante que ninguém fica refém, porque se pode sempre fazer fork, mas manter um fork é trabalho. Para uso numa equipa, eu fixava versões e acompanhava o changelog com atenção.

A sexta não é do lgtmaybe, é nossa. Um revisor automático que comenta tudo, sempre, arrisca-se a criar o mesmo viés de automação que queria combater. Vinte comentários seguidos sem interesse e ao vigésimo primeiro já ninguém lê. A reflexão, as lentes configuráveis e o min_severity ajudam, mas a calibração tem de ser feita por quem conhece o projeto. O “maybe” do nome é um lembrete útil: os achados são propostas, e a decisão de fazer merge continua a ser de uma pessoa.

O que eu faria para experimentar isto se não viesse já a chafarica montada

Para quem quer experimentar a sério, deixo o que me parece um caminho razoável:

  1. Instalar o ollama (ou o chama.cpp, ou o vLLM, se houver GPU NVIDIA) numa máquina com memória suficiente e puxar um modelo de código.
  2. Instalar o lgtmaybe, correr o config init e fazer a primeira revisão num branch antigo em que já se sabe que havia um bug. É a forma mais barata de perceber o que o modelo apanha e o que deixa passar.
  3. Pôr essa máquina numa VLAN própria, com firewall, porque o ollama não tem autenticação.
  4. Ligar o lgtmaybe ao Forgejo, Gitea ou GitLab interno, com um runner próprio e o modelo apontado para a máquina do passo anterior, e restringir quem pode disparar revisões aos contribuidores de confiança.
  5. Ler a secção de segurança do guia do teu servidor Git sobre quem pode alterar o pipeline, e agir em conformidade.
  6. Escrever, preto no branco, a política de cada repositório: código público pode ir a um modelo externo, código de clientes fica em casa. Uma linha no .lgtmaybe.yml de cada um faz cumprir a decisão.
  7. Combinar com a equipa que o lgtmaybe é mais um revisor, não o último. O botão de merge continua a ter dono.

Nenhum destes passos exige orçamento de projeto nem autorização do conselho de administração. É uma tarde de trabalho, mais a máquina.

Looks good to me. Maybe.

Há uma ironia bonita neste projeto. Uma ferramenta com “talvez” no nome é mais honesta sobre o que a IA consegue fazer de metade dos produtos que andam por aí com “AI-powered” escrito no logótipo. Não promete apanhar tudo, não promete decidir por ti, e não exige que entregues o teu código a ninguém para funcionar.

Fala-se muito de soberania digital em Bruxelas, em conferências e em estratégias nacionais. Mas ela não se constrói só com regulamentos e datacenters europeus. Constrói-se também com ferramentas que tornam o caminho privado no caminho fácil, para que quem está a trabalhar numa sexta-feira às seis da tarde não tenha de escolher entre rever bem e proteger os dados do cliente. O lgtmaybe faz exatamente isso, e cabe num pip install.

Acho que é algo que vale a pena pensar.

Abraço!
Nuno

Documentação:

O projeto

Runtimes locais

Posts anteriores referidos

Este post foi feito e revisto por modelos LLM’s privados.