← voltar ao blog
ACERTObackend9min de leituramin

Tinstar: as regras do AGENTS.md viram checagens que rodam sem LLM

O AGENTS.md diz as regras a um agente de IA, mas nada confere se foram seguidas, e cada regra custa tokens em toda conversa. O Tinstar compila cada regra uma vez numa checagem pequena que roda no terminal, no editor, na CI e no loop do agente. Como as leis são escritas, por que cada uma é testada antes de alguém vê-la e a checagem de ruído que pegou uma lei disparando 15 mil vezes.

Matheus Nunes PereiraEngenheiro de Software

Cada vez mais repositórios têm um AGENTS.md: as regras do projeto, escritas para os agentes de IA que mexem nele. “Dinheiro é um número inteiro de centavos.” “Só src/payments/ fala com o provedor de pagamento.” “Imagens nunca usam latest.” Esse arquivo tem dois problemas. Nada confere se as regras foram seguidas, então o agente obedece quando lembra. E cada regra é paga em tokens, em toda conversa, para sempre.

Os linters resolveram isso para pessoas faz tempo. O porém é que uma regra de linter é escrita na linguagem do linter, e as regras do AGENTS.md são escritas em texto corrido, por quem conhece o projeto. Eu queria manter o texto como fonte da verdade e ganhar o linter de graça. O Tinstar é isso, em Rust.

No Zed: o agente escreve as leis pelas ferramentas MCP do tinstar, eu aprovo e o editor passa a cobrá-las. A espera do agente está acelerada 10×. O vídeo está em inglês.

Uma lei é uma checagem com exemplos

Uma lei é um arquivo YAML em .tinstar/laws/. Ela guarda a regra como as pessoas escreveram, de onde veio, como checar e trechos de código que ela precisa aceitar e rejeitar:

.tinstar/laws/no-console-log.yaml
id: no-console-log
kind: pattern
statement: Code under src/ logs through logger, never console.log.
source: AGENTS.md#logging
message: console.log bypasses logger
hint: Import { logger } from the project logger module and call logger.info(...).
severity: warning
language: typescript
files: ["src/**"]
rule:
pattern: console.log($$$ARGS)
fix: logger.info($$$ARGS)
examples:
valid:
- logger.info("coupon", code, discount);
invalid:
- console.log("coupon", code, discount);

São três tipos, porque as regras de um AGENTS.md de verdade não falam só de código:

  • pattern: código, na árvore sintática, com regras do ast-grep. TypeScript, Python, Rust, Go, Java, C/C++, YAML, HCL, Bash e outras.
  • config: asserções sobre documentos YAML e JSON, como manifestos de Kubernetes, workflows do GitHub Actions ou o package.json. “Todo container declara limites de recursos” é um caminho e uma condição.
  • layout: arquivos e pastas. “Nenhum .env é commitado”, “toda pasta de feature tem um teste”, “migrações se chamam YYYYMMDD_nome.sql”.

Os exemplos são o que torna uma lei confiável. O tinstar test roda cada lei contra eles, então uma lei que não faz o que o statement diz nunca chega ao projeto. Regras que não dá para checar pelo código (“um desconto nunca passa de 30% do pedido”) continuam no AGENTS.md, para pessoas e agentes. Tudo bem: a ideia é tirar do prompt o que uma máquina consegue conferir.

Um motor, quatro lugares

O motor (tinstar-core) não sabe nada de terminal nem de JSON-RPC. Cada arquivo é lido e analisado uma vez, e a árvore é compartilhada por todas as leis daquela linguagem, então mais leis não significam mais análises. As interfaces são finas e reportam os mesmos diagnósticos:

  • CLI e CI. tinstar check para pessoas, --format json para ferramentas, --format agent com uma linha compacta por problema. O código de saída quebra a CI quando uma lei com severity: error é violada, e uma GitHub Action (uses: matpdev/tinstar@v0.1.0) instala o tinstar e mostra cada lei violada na sua linha do pull request.
  • Servidor de linguagem. O tinstar lsp sublinha as leis violadas enquanto você digita, com a regra em texto corrido e um link para a lei. Uma lei com fix vira correção rápida. O projeto inteiro é checado a partir do disco, então os problemas em manifestos de Kubernetes e workflows de CI aparecem também, em arquivos que ninguém abriu.
  • Hook de agente. No Claude Code, um hook PostToolUse roda tinstar hook claude-code depois de cada edição. Se uma lei foi violada, o agente fica sabendo qual, onde, como corrigir e de que regra ela veio, e corrige antes de seguir. Quando nada quebrou, o hook não diz nada e não custa nada.
  • Servidor MCP. Falo dele mais abaixo.

O servidor de linguagem foi a parte com mais desenho. Uma thread é dona do estado e trata as mensagens em ordem, então não há locks. As checagens rodam em threads de trabalho e voltam marcadas com a versão do documento: um resultado de uma versão antiga é descartado em vez de cancelado (“a mais recente ganha”). As teclas passam por um debounce, uma checagem por pausa. Editar uma lei troca o Arc<Lawbook> por um novo, então as checagens em andamento terminam com o livro antigo e nunca veem leis carregadas pela metade.

Do AGENTS.md às leis

Escrever regras de ast-grep à mão é justamente o trabalho que ninguém quer fazer, então o tinstar compile pede a um LLM. Ele envia cada seção do documento, com um retrato curto do projeto, e pede leis. O que volta nunca é aceito de cara. Cada rascunho é testado duas vezes:

  1. Contra os próprios exemplos. Uma lei que não carrega, ou que erra um exemplo, volta para o LLM com o erro. Depois de três tentativas ele desiste, e uma lei que volta com o mesmo erro duas vezes não é pedida de novo.
  2. Contra o projeto. Esta é a checagem de ruído, e ela nasceu de uma falha de verdade.

Testei num repositório grande: o do editor Zed, uma base de código Rust grande com o seu próprio AGENTS.md. Para uma das regras, o LLM escreveu uma lei sobre unwrap() que passava em todos os exemplos e disparava 15 mil vezes no projeto. Outra, sobre WeakEntity, disparava 14 mil vezes. As duas estavam certas nos exemplos e eram inúteis no código real: eram muito mais amplas que a regra.

A solução foi usar o próprio projeto como teste. Um projeto segue as próprias regras na maior parte do tempo, então uma lei que dispara mais de 25 vezes provavelmente está errada. Ela volta para o LLM com os números (quantas ocorrências estão em arquivos de teste, quantas estão nos testes inline do Rust depois do #[cfg(test)], em quais pastas) e uma amostra do código que ela pegou. O LLM pode estreitar a lei, confirmar com um motivo (um projeto pode ter muitas violações antigas) ou admitir que a regra não dá para distinguir pela sintaxe. Uma lei que ele não consegue resolver fica de lado com os rascunhos rejeitados e nunca é oferecida para aprovação.

Só as leis que passam chegam a .tinstar/laws/pending/, e nada ali é cobrado até uma pessoa rodar tinstar approve. No projeto de demonstração, o DeepSeek escreve de 8 a 9 das 9 leis em cerca de 11 segundos, por cerca de 35 mil tokens.

Gastar tokens uma vez só

A proposta inteira é “os tokens são gastos uma vez”, então o compilador também tinha que ser barato. Três coisas fizeram a maior parte do trabalho:

  • Triagem. Uma chamada barata sobre o documento inteiro separa as seções sem nada checável, como processo ou intenção.
  • Cache de prompt. A parte estável do prompt (o formato das leis e o retrato do projeto) vem primeiro e é marcada para o cache do provedor. A primeira seção roda sozinha para aquecer o cache, e as outras rodam de quatro em quatro.
  • Um registro. O .tinstar/compiled.json guarda uma impressão digital de cada seção e o que ela rendeu. Rodar de novo só envia as seções cujo texto mudou.

No AGENTS.md do Zed, uma execução foi de 164 a 189 mil tokens de entrada para 115 a 157 mil, com cerca de 90% deles servidos pelo cache. Rodar de novo sem nada mudado custa zero tokens e leva 0,03 segundo.

Usando o modelo do próprio editor (MCP)

O tinstar compile precisa de uma chave de API, e nem todo mundo trabalha assim. Muita gente já paga por um modelo dentro do editor: Copilot, Claude, DeepSeek, um local. Por isso o tinstar mcp é um servidor MCP com quatro ferramentas. O agente lê as seções do documento, escreve uma lei e chama tinstar_check_law, que compila a lei, roda os exemplos e a checagem de ruído e diz o que corrigir. O tinstar_save_law checa de novo antes de gravar em pending/. O modelo é o que o editor usa. As garantias ficam com o tinstar.

É o que aparece no vídeo acima: o agente do Zed, com DeepSeek, transformou o AGENTS.md da demonstração em nove leis que pegam exatamente as nove violações plantadas.

O vídeo também mostrou o limite de deixar um modelo escrever as leis. Numa execução, o agente fez a lei de log como error em vez de warning e deixou de fora o fix, então o editor não tinha correção rápida para oferecer. Nenhuma das duas coisas é bug do checador. São decisões de julgamento, e é por isso que as leis esperam em pending/ por uma pessoa. Corrigi a lei à mão e mudei o tinstar_check_law para apontar a falta de fix quando a reescrita é mecânica.

O que aprendi

  1. Não confie numa regra gerada porque ela passa nos exemplos. Rode no código real: um projeto que segue as próprias regras é um conjunto de testes de graça, e o número de ocorrências diz mais que qualquer exemplo.
  2. Quando o LLM erra, dê números e uma amostra, não um veredito. “Dispara 15 mil vezes, a maioria em código de teste, aqui estão dez” trouxe leis mais estreitas. “Esta lei está ampla demais” não trouxe.
  3. Faça a garantia não depender do modelo. Qualquer que seja o modelo que escreve a lei, por chave de API ou pelo agente do editor, ela passa pelas mesmas checagens e pela mesma aprovação humana.
  4. Num servidor de linguagem, descartar resultados velhos é mais simples que cancelar trabalho, e basta.

O Tinstar está no começo: tem três tipos de lei, a CLI, o servidor de linguagem (Zed), o hook do Claude Code, o servidor MCP e uma GitHub Action. A versão 0.1.0 tem binários prontos para Linux, macOS e Windows:

terminalTerminal window
curl -fsSL https://raw.githubusercontent.com/matpdev/tinstar/main/install.sh | sh

O código está no GitHub, sob licença MIT. Os próximos passos são regras para Dockerfile e imports, VS Code, saída SARIF para o code scanning do GitHub e um estudo que quero fazer: as mesmas tarefas de agente com e sem o tinstar, contando violações, tokens e autocorreções. Que regra do seu AGENTS.md você acha que um checador nunca conseguiria pegar?

Neste post
Escrito por Matheus Nunes PereiraEngenheiro de Software. Sistemas distribuídos, telemetria em tempo real e software que conversa com hardware. Se tiver um problema assim, vamos conversar.Engenheiro de Software

Sistemas distribuídos, telemetria em tempo real e software que conversa com hardware. Se tiver um problema assim, vamos conversar.