Celmis
Dezoito ferramentas sobre um grafo de símbolos dos seus repositórios: busca de símbolos entre repositórios, consumidores, superfícies de API, descobertas de dependências e revisões — sob as mesmas regras de acesso por repositório do restante do produto. Auto-hospedado, AGPL-3.0.
Documentação
Celmis
Inteligência de código auto-hospedada — pergunte sobre seus codebases, revise pull requests e produza as evidências que um auditor pede
celmis-labs.github.io · Documentação · Início rápido · Resultados
O Celmis lê seus repositórios uma vez e mantém um grafo de símbolos deles. Todo o resto — perguntas, revisões, auditorias de dependências, documentação gerada — é uma forma diferente de ler esse grafo. Ele roda em uma única máquina sob docker compose, com o provedor de modelo de sua escolha por trás, e nada sai da sua rede exceto as chamadas que você configurar.
Na narrativa mais antiga, Kelmis era o fundidor — um dos três Dáctilos Ideanos, ao lado de Damnameneu, o martelo, e Acmon, a bigorna, a quem se creditava o trabalho do ferro. O índice faz a redução aqui; as superfícies são o que trabalham o resultado.
O que isso oferece que uma ferramenta só de diff não consegue
Faça uma pergunta que abrange dois repositórios, e a resposta cita ambos:

Isso não é um resultado de busca. O gateway e o serviço de pagamentos são repositórios separados, sem código compartilhado, e a resposta rastreia a cadeia de chamadas entre eles — depois percebe, sem ser solicitado, que o nome do tópico Kafka está codificado em ambos e que alterar um quebra silenciosamente o outro.
Um revisor que lê apenas o diff estruturalmente não pode dizer isso. Ele nunca teve o outro repositório aberto.
Nove coisas que as pessoas fazem com ele
| Você é um PM, um líder de entrega ou o cliente e quer saber o estado de um grupo de projetos, ou como algo realmente funciona | Pergunte. De qualquer dispositivo, de qualquer lugar, sem reservar tempo de um engenheiro e sem uma reunião cujo único resultado é um parágrafo → Pergunte ao código |
| Um novo engenheiro tem uma pergunta que um sênior teria que responder | Cada uma dessas tira alguém experiente do fluxo, no momento em que já estão ocupados. O codebase responde em vez disso, com citações arquivo:linha → Pergunte ao código |
| Duas equipes compartilham uma integração e nenhuma consegue ler o repositório da outra | Carregue-o, conceda o direito de perguntar e negue os caminhos que devem permanecer privados. Eles obtêm respostas; as credenciais são recusadas na origem → Quem pode ver o quê |
| Um cliente ou auditor pede seu SBOM | Um botão, CycloneDX, mais um pacote de evidências cujo manifesto permite que eles verifiquem sem confiar em você → Dependências, SBOM e o pacote de evidências |
| Uma vulnerabilidade chega em uma dependência | Corrigir com Claude entrega a uma sessão incorporada o repositório, o pacote e a descoberta. Ele edita, o runner envia um branch e abre um PR → Corrigir com Claude |
| Um pull request precisa de revisão | Agentes leem o diff — e, onde o grafo está construído, quem mais chama o que está sendo alterado, inclusive de outro repositório → Revisão de pull request |
| Quarenta serviços precisam da mesma coisa feita neles | Escreva a frase. O Celmis mostra a quais repositórios ela resolve e espera um segundo pressionamento, em vez de encontrá-los entre quarenta e pressionar um botão quarenta vezes → Peça trabalho entre repositórios |
| Um alerta dispara às 02:00 e você não está em uma mesa | Ele chega no Celmis, um push web alcança seu telefone, e Corrigir com Claude abre uma sessão já segurando o alerta. O runner abre o pull request → Alertas e correção pelo telefone |
| Seu próprio agente ou editor precisa entender o codebase | Aponte-o para /mcp/. Dezoito ferramentas sobre o mesmo índice, sob as mesmas regras de acesso — nenhuma segunda cópia do seu código em lugar nenhum → Conecte o Claude Code e outros clientes MCP |
As três primeiras são as que uma ferramenta de revisão de código não faz de forma alguma, e são a razão pela qual isso é uma plataforma em vez de um revisor: indexe uma vez, depois leia esse índice de qualquer lado do trabalho em que você esteja.
Três números
| 197 segundos | de git clone a seis serviços saudáveis, medido em um servidor limpo |
| $0,118 | por pull request revisado, no modelo com o qual isso é entregue |
| 17º de 50 | no conjunto offline do Martian Code Review Bench, sob todos os três juízes |
Esse último é deliberadamente pouco lisonjeiro, e permanece. Ele mede uma das superfícies abaixo — revisão de pull request em PRs isolados de repositório único — e esse conjunto não tem serviço irmão para um símbolo ter consumidores, então a coisa em torno da qual este produto é construído não está no número. A tabela, a auditoria de cada descoberta que ele marcou como falso e o comando que reproduz ambos estão em Resultados.
Sumário
- O que isso oferece que uma ferramenta só de diff não consegue
- Nove coisas que as pessoas fazem com ele
- Três números
- Início rápido
- Primeiro usuário e admin
- Conecte um repositório
- Pergunte ao código
- Revisão de pull request
- Dependências, SBOM e o pacote de evidências
- Corrigir com Claude
- Alertas e correção pelo telefone
- Peça trabalho entre repositórios
- Quem pode ver o quê
- Linguagens e formatos
- Verificações determinísticas — sem modelo, sem falsos positivos
- Conecte o Claude Code e outros clientes MCP
- Resultados
- Auditoria dos falsos positivos
- Repositórios de teste
- Configuração
- Operações
- Desenvolvimento local
- Referência da CLI
- Arquitetura
- Solução de problemas
- Estrutura do projeto
- Procedência e direitos
Início rápido
O que você precisa
| Docker | 24+ com Compose v2 | Docker Desktop no macOS/Windows, o engine nativo no Linux |
| Uma chave de API de modelo | um de | Google Gemini, Anthropic, OpenAI, OpenRouter, Groq ou Mistral. Uma chave gratuita do Gemini é suficiente para avaliar: https://aistudio.google.com/app/apikey |
| RAM | ~4 GB livres | Medido em uma execução real de indexação: pico de 1,1 GB em todos os cinco contêineres, 565 MB em repouso |
Postgres e Qdrant são incluídos — nenhum cluster externo para provisionar. Nenhuma instalação de Python ou Node.js é necessária para o fluxo Docker.
Inicie
git clone <your-fork-url> celmis
cd celmis
# Generates .env and fills every secret in the format each one needs.
# Idempotent: run it again after a pull and it fills only the new blanks.
./scripts/init-env.sh
docker compose --env-file .env up -d
# Wait for healthy — first boot pulls three images and applies migrations
docker compose ps
Abra http://localhost.
Nada é construído aqui. As três imagens são puxadas do registro nomeado por CELMIS_REGISTRY na tag em CELMIS_TAG, para linux/amd64 e linux/arm64 — Apple Silicon e um servidor ARM ambos recebem uma imagem nativa. Construí-las na máquina que as executa foi medido em 485 segundos e 4,2 GB de disco para api sozinho, que é por que instalar não significa mais compilar.
Porta 80, não 3000: um proxy reverso coloca o aplicativo e sua API em uma única origem e serve a API sob /backend. Isso não é uma preferência de implantação — o bundle do navegador pede um caminho relativo, que é a única maneira de uma imagem publicada servir a cada instalação em vez de apenas aquela em que foi construída.
Para trabalhar NO Celmis em vez de executá-lo, adicione o overlay de desenvolvimento e você recupera as construções locais:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build
init-env.sh --check relata o que ainda está vazio sem escrever nada.
Isso é uma renderização da sessão capturada, não uma gravação de tela — as figuras nele são as que a execução produziu em 26 de agosto de 2026, e a saída do compose é verbatim de logs/03-up.log no relatório de instalação. É desenhado em vez de fotografado porque uma segunda pilha não pode ser trazida ao lado de uma em execução: docker-compose.yml corrige container_name, então os nomes colidem.
Parar
docker compose down # stop, keep your data
docker compose down -v # stop and DELETE every volume
Primeiro usuário e admin
O formulário de inscrição em /login funciona assim que a pilha está saudável. Essa conta é um usuário normal — inscrever-se não concede direitos de admin, nem mesmo para a primeira pessoa a entrar.
O admin global vem do ambiente em vez disso: entre com CELMIS_MASTER_EMAIL e CELMIS_MASTER_KEY (como senha), ambos em .env. Quem quer que execute a caixa é o admin, que é o modelo que uma instalação auto-hospedada quer em vez de quem alcançou o formulário primeiro. O caminho não existe a menos que ambas as variáveis estejam definidas, e cada uso dele é escrito no log de auditoria.
Para promover uma conta normal:
docker compose exec api analyzer auth make-admin you@example.com
Conecte um repositório
- Configurações → Configuração de LLM — cole uma chave do provedor. Ela é criptografada com
CREDENTIAL_MASTER_KEYantes de tocar o banco de dados, e a UI só mostra novamente o primeiro e os últimos quatro caracteres. - Conexões — adicione um token do GitHub, GitLab ou Bitbucket. Use uma conta de máquina, não a sua: um token pessoal alcança todos os repositórios que você pode ver, e tokens acabam em backups, logs e capturas de tela.
- Repositórios → Adicionar — escolha repositórios do provedor, ou cole uma URL de clone. A indexação é enfileirada; o trabalho aparece na mesma página.
A indexação constrói duas coisas a partir do mesmo checkout: um grafo de símbolos (definições, chamadas, importações — sobre o que os agentes de revisão raciocinam) e embeddings no Qdrant (o que a Q&A recupera). Um repositório de 120 mil símbolos leva cerca de um minuto em quatro núcleos.
Vinte e três linguagens são analisadas no grafo. Um arquivo em uma linguagem sem parser é dito em voz alta em vez de ser silenciosamente ignorado — analyzer graph-stats lista o que foi e o que não foi lido.
Pergunte ao código
Uma pergunta em um chat, respondida com citações arquivo:linha de quantos repositórios você apontar. As respostas são transmitidas enquanto são escritas.
Agrupe repositórios em um projeto, e a pergunta é feita ao grupo:

As respostas citam código real, e apenas o código que o perguntador tem permissão de ver — que é o que torna seguro entregar a pergunta a alguém fora da equipe que possui o repositório. Veja Quem pode ver o quê.
Revisão de pull request
Agentes leem o diff e publicam descobertas no GitHub, GitLab ou Bitbucket. Em vez de mostrar isso em uma captura de tela desta interface, as revisões são deixadas onde foram publicadas — cinquenta pull requests em projetos reais, com os comentários ainda anexados às linhas sobre as quais foram escritos. Eles estão listados sob Repositórios de teste, e a saída lá é inédita, incluindo as descobertas que a auditoria abaixo marca como erradas.
Onde o grafo está construído, a revisão também carrega o que o diff não mostra: quem mais chama o símbolo que está sendo alterado, inclusive de outro repositório. Onde não está construído, a revisão ainda roda — apenas responde à pergunta mais estreita, que é o que o benchmark mediu.
Cada descoberta que o benchmark marcou como falsa foi aberta na fonte e publicada com um veredito. Trinta e três de setenta e nove acabaram sendo defeitos reais que o conjunto dourado não contém. Esse trabalho está em Auditoria dos falsos positivos, com o código e um permalink para cada um, para que você possa discordar de qualquer um deles.
Dependências, SBOM e o pacote de evidências
A auditoria de dependências é determinística: auditores nativos onde a ferramenta está instalada, OSV em todos os outros lugares, sem modelo envolvido. Um modelo de linguagem, se você der uma chave, escreve o resumo — ele não decide o que é vulnerável.

Dois arquivos saem de cada auditoria, e nenhum precisa de uma chave de LLM:
- SBOM — um inventário CycloneDX de cada dependência, sua versão, URL do pacote e as vulnerabilidades conhecidas contra ela. Este é o arquivo que as pessoas querem dizer quando dizem "envie-nos seu SBOM".
- Pacote de evidências — a auditoria como um arquivamento: cada SBOM, cada achado, o cronograma de execuções anteriores e um sha256 de cada arquivo, para que um terceiro possa verificar se nada foi editado depois sem precisar confiar em nós. Uma pasta cujo conteúdo pode ser alterado posteriormente não prova nada; o manifesto é o que a torna evidência.
Junto a eles, a documentação técnica gerada — PRDs de módulos, documentos de recursos e guias de integração escritos a partir do código — que é sua para manter e continua funcionando após o término de qualquer assinatura.
Por que isso existe agora. A partir de 11 de setembro de 2026, a Lei de Resiliência Cibernética da UE exige que um fabricante relate uma vulnerabilidade explorada ativamente à ENISA dentro de 24 horas. O mandato formal de SBOM entra em vigor em dezembro de 2027, mas você não pode responder à questão das 24 horas sem visibilidade em nível de componente primeiro — para relatar o que é afetado, você precisa saber o que está dentro.
Celmis não reivindica conformidade e não o fará. Ele produz os artefatos que um arquivamento precisa. Se um arquivamento é adequado é um julgamento de advogado, e uma ferramenta que implica o contrário está vendendo uma falsa sensação de segurança.
Mais uma coisa que a página de auditoria diz em voz alta, porque é a falha que ninguém procura: um ecossistema que ninguém escaneou relata zero vulnerabilidades exatamente como um limpo. A cobertura é mostrada ao lado dos achados — qual auditor produziu cada resultado e, mais útil, o que ficou sem verificação e por quê.
Corrigir com Claude
Encontrar algo é metade de um ciclo. Uma sessão incorporada do Claude Code é executada dentro da instalação, edita o checkout, e o executor faz commit, envia um branch e abre um pull request.
Ele roda na sua assinatura, na sua máquina
Não há chave para comprar de nós e nenhum modelo incluído. Você conecta sua própria assinatura
do Claude, da mesma forma que o Cursor faz: execute claude setup-token uma vez no seu próprio
laptop e cole o token em Configurações. Ele é armazenado criptografado no armazenamento de
credenciais, e a sessão que roda depois o utiliza.
Dois slots, resolvidos nessa ordem:
| Pessoal | sua própria assinatura, visível para ninguém mais |
| Espaço de trabalho | uma assinatura que um administrador compartilha com o espaço de trabalho — aceitação explícita |
O slot do espaço de trabalho está desativado, a menos que alguém o ative, e a interface diz o porquê antes de você salvar: compartilhar a assinatura de uma pessoa entre várias pessoas pode violar os termos de consumo da Anthropic. Essa é uma decisão para quem detém a assinatura, e o produto não a tomará silenciosamente.
E a sessão não está rodando na nuvem de alguém. Ela roda na sua própria instalação, em um espaço de trabalho isolado por sessão. O que isso compra não é "acesso à nuvem" — é que a máquina que contém seu código é uma que você controla, e você a acessa de um laptop em um escritório ou de um telefone em um trem pela mesma razão que você acessa qualquer serviço que executa: porque é seu e está ativo.
Esse é o resumo de Alertas e correção pelo telefone — um alerta chega, e a correção começa de onde quer que você esteja lendo.
Uma vulnerabilidade na auditoria de dependências carrega um botão Corrigir com Claude. Ele não abre um chat vazio — ele entrega à sessão o repositório, o pacote, ambas as versões e os limites do trabalho, já escritos:

Aqui está um desses ciclos, de ponta a ponta, em um achado real — lodash 4.17.11 com uma
vulnerabilidade conhecida contra ele. 220 segundos de Iniciar sessão até um pull request
aberto, em cinco turnos:
Read package.json
→ "Only package.json has lodash; no requirements.txt/pyproject/go.mod exist here."
Edit package.json: "lodash": "4.17.11" → "4.18.0"
mcp__exec__run: cat package.json | grep -A2 lodash; ls
→ "Confirmed no other manifest files exist, so no other changes were needed."
O branch que ele enviou e o pull request que abriu, no GitHub:

Veja o que não está nesse diff. axios 0.21.1, minimist 1.2.0,
node-fetch 2.6.0 estão nas linhas diretamente acima e abaixo — todos desatualizados, todos
sinalizados na mesma auditoria — e todos intocados. A tarefa dizia apenas manifestos, e
um agente que arrumasse mais três no caminho teria sido um resultado pior
para revisar, não melhor.
É um pull request ao vivo, não uma captura de tela:
celmis-demo-gateway#6
— branch celmis-agent/b8960e01, um commit, +1/-1.

Dois detalhes nessa transcrição valem mais que o diff. O agente não assumiu que não havia outros manifestos — ele executou um comando na sandbox para verificar. E a tarefa dizia "apenas manifestos, não toque em dependências não relacionadas", então a mudança é exatamente uma linha.
O que o executor permite e o que não permite
Isso é decidido pelo executor, não pelo prompt — que é a parte que vale a pena ler antes de conceder qualquer coisa a um agente:
- Sem shell próprio.
Bash,WebFetch,WebSearche edição de notebook são desabilitados. Os comandos são executados através do contêiner sandbox, que é um serviço separado com seu próprio uid e um sistema de arquivos raiz somente leitura. - Git é trabalho do executor. O agente nunca faz commit ou push. Quando o trabalho está concluído — ou quando você pressiona Concluir e enviar — o executor faz o commit, envia o branch e abre o PR. Nunca para o branch padrão.
- Um limite do provedor é uma pausa, não uma perda. A primeira tentativa da execução acima
atingiu um limite semanal da conta no meio da sessão. A sessão não morreu: ela mudou para
paused, manteve seu trabalho retomável por quatorze dias e mostrou a mensagem do próprio provedor em vez de uma falha genérica. Uma segunda chave a concluiu. - A sessão é observável. A saída é transmitida via SSE com replay, então uma reconexão retoma de onde parou em vez de começar em branco.
A conexão é um token de configuração, mantido por usuário ou por espaço de trabalho. A API nunca o retorna depois de salvo — apenas se ele está lá e se ainda funciona.
Alertas e correção pelo telefone
O ciclo acima começa a partir de um achado de dependência. Ele também começa a partir da produção.
Aponte qualquer sistema de monitoramento para a URL de ingestão do espaço de trabalho e seus alertas chegam em Celmis:
POST /webhook/alerts/{workspace_id}.{secret}
O webhook de alerta unificado do Grafana é analisado como está; qualquer outra coisa pode postar
{"title", "body", "severity", "repo"}. A metade secreta é armazenada criptografada com Fernet
por espaço de trabalho e comparada em tempo constante. O endpoint é não autenticado por
design — sistemas de monitoramento não podem fazer OAuth — e limitado ao locatário por
construção: um token só pode escrever no espaço de trabalho ao qual pertence.
O que isso compra é a rota sem laptop:
- Um alerta dispara. Ele chega ao Celmis e uma notificação push da web chega ao seu telefone — push web real, VAPID e um service worker, então chega esteja a aba aberta ou não.
- Você o abre. O alerta carrega seu repositório, porque
route_incidentpode pegar um stack trace e dizer a qual repositório e a qual proprietário ele pertence. - Você pressiona Corrigir com Claude. A sessão abre já contendo o alerta — não um chat vazio.
- O executor faz commit, envia um branch e abre o pull request.

Nenhum desses quatro passos precisa de um checkout, um terminal ou uma máquina em que você confie. O trabalho acontece dentro da sua própria instalação; o telefone é uma tela para isso.
A mesma página lista todos os alertas que o espaço de trabalho recebeu, então um alerta que ninguém acionou é visível em vez de perdido em um canal.
O que é registrado enquanto isso acontece. Dois registros separados, e eles respondem a perguntas diferentes:
- A trilha de auditoria — JSONL somente anexação, rotacionado por tamanho, filtrável por tempo, modo, operação e repositório, exportável como CSV. Ela responde quem fez o quê, em qual repositório, quando. A retenção padrão é de 90 dias e o arquivo ativo é nunca excluído, apenas arquivos rotacionados.
- Histórico de recursos — amostras e agregações sobre a própria instalação, exportável como CSV para uma planilha de dimensionamento. Ele responde quanto custou para executar.
Uma sessão de agente que edita um repositório às duas da manhã do telefone de alguém é exatamente o tipo de evento que precisa ser reconstruível depois. É.
Solicitar trabalho entre repositórios
Cada superfície acima age em uma coisa por vez: este repositório, este pull request, este achado. Essa é a forma certa para um botão e a forma errada para um conjunto definido por uma condição.
Gerar documentação para cada serviço que não tem nenhuma é uma frase. Através da
interface, é encontrá-los entre quarenta e pressionar um botão quarenta vezes.
Auditar tudo sob acme-ai que não foi auditado em trinta dias precisa de
filtros, seleções salvas e operações em massa — um subsistema — ou precisa de uma
frase.
Então há uma caixa de frase e um catálogo deliberadamente curto de verbos atrás dela. O trabalho de objeto único permanece nos botões, onde pertence.
Nada é executado no primeiro pressionamento. A interpretação é um palpite, e esses verbos custam dinheiro e horas — uma construção de cofre, uma frota de agentes de revisão, uma auditoria em um grupo. Então a resposta a uma frase não é o trabalho; é quais repositórios isso resolve, listados, com um segundo botão abaixo deles. Você confirma o conjunto, não a intenção.
O escopo é re-verificado no momento em que você confirma, não quando você perguntou. Um repositório registrado nos segundos entre a pergunta e o pressionamento não pode silenciosamente entrar em um conjunto que dizia "tudo". A mesma regra que governa o resto do produto — a verificação é executada onde o trabalho acontece — governa isso.
Um chamador automatizado nunca pode enfileirar um fan-out ilimitado. Os verbos são limitados, com escopo de espaço de trabalho e aceitam um ator explícito: nada aqui lê um contexto de solicitação ambiente, porque um conector processando uma fila não tem solicitação, e uma função que adivinha um espaço de trabalho é a forma de a automação de um locatário alcançar os repositórios de outro locatário.
Três chamadores estão convergindo nesses mesmos verbos — um agente externo via MCP, o agente incorporado e um conector de ticket que transforma auditar esses quatro serviços em trabalho e publica o resultado de volta. Eles compartilham uma implementação de propósito: um segundo "iniciar uma auditoria" é um segundo conjunto de regras sobre execuções ao vivo, deduplicação e reinicializações forçadas, e a cópia que ninguém mantém é a que corrompe a fila.
Quem pode ver o quê
O acesso é resolvido por repositório, por equipe, e governa todas as superfícies de uma vez — Q&A, gráfico, pesquisa, MCP:
| configuração | efeito |
|---|---|
visibility: none | o repositório não existe para pesquisa |
visibility: metadata | apenas documentação e notas de arquitetura |
visibility: code | o código-fonte é legível |
deny_globs | vence mesmo em code — credenciais, criptografia, conexões de banco de dados, verificação de segredos |
allow_globs | uma lista de permissões quando definida; negar ainda subtrai dela |
Isso é o que faz o caso da equipe vizinha funcionar em vez de ser uma promessa: carregue o repositório, conceda à outra equipe o direito de perguntar e negue os caminhos que não devem ser lidos. Eles obtêm respostas; esses arquivos são recusados na fonte, não filtrados de uma resposta que já os continha.
Idiomas e formatos
Dezessete módulos de gráfico, mais um caminho genérico através de consultas de tags tree-sitter para idiomas sem um:
Código — Python, TypeScript, JavaScript, Go, Java, C#, C++, PHP, Vue e mais através do caminho genérico.
Infraestrutura — Dockerfile, docker-compose, Helm, manifestos Kubernetes, Terraform e fluxos de trabalho de CI. Esta é a parte que a maioria das ferramentas de inteligência de código ignora, e é por isso que uma pergunta pode cruzar de uma função para a definição de serviço que a executa.
Verificações determinísticas — sem modelo, sem falsos positivos
Cada verificação abaixo é decidida lendo arquivos. Nenhum modelo de linguagem participa da decisão de que algo está errado, então a taxa de falsos positivos é zero por construção, em vez de por ajuste.
Essa distinção é o ponto principal. Cerca de vinte por cento de falsos positivos é onde os desenvolvedores param de ler os comentários de uma ferramenta completamente — um custa segundos de atenção, mil custa uma equipe que aprendeu a pular tudo o que a ferramenta diz. Um modelo é usado aqui para explicar e priorizar, nunca para detectar.
| Verificação | Lê | Detecta |
|---|---|---|
install_script | hooks de ciclo de vida do package.json | uma dependência que executa código no momento da instalação |
python_build_hooks | pyproject.toml / setup.py | execução de código em tempo de build em um pacote Python |
cargo_build_script | Cargo.toml | uma crate com um build.rs |
non_registry | manifestos e arquivos de lock | uma dependência obtida de uma URL git ou tarball em vez de um registro |
suspect_name | a lista de dependências | typosquats — um nome a uma edição de distância de um pacote popular |
lock_drift | manifesto vs. arquivo de lock | um arquivo de lock que não corresponde mais ao que o manifesto declara |
cross_repo_drift | o diff do PR e, em seguida, repositórios irmãos | uma constante alterada em um repositório e deixada para trás nos outros |
A varredura comum de CVEs está deliberadamente fora dessa lista. O OSV-Scanner
já faz isso, é gratuito e é o padrão de facto — o Celmis o executa (além do
auditor de cada ecossistema: pip-audit, npm audit, govulncheck,
cargo audit) e trata o resultado como uma entrada, não como um recurso.
Sobre conformidade. O Celmis produz os artefatos que uma auditoria pede — um SBOM CycloneDX, um inventário de dependências, um histórico de descobertas com carimbos de data/hora e as evidências em que cada descoberta se baseia. Ele não afirma que seu arquivamento é adequado, e nenhuma ferramenta pode honestamente fazer isso: o que um auditor aceita depende do seu setor, da sua jurisdição e dos seus próprios controles. Produza os artefatos; deixe as pessoas cujo trabalho é avaliá-los.
Conecte o Claude Code e outros clientes MCP
O Celmis expõe seu índice via MCP, para que um agente possa pesquisar símbolos, ler superfícies de API e encontrar consumidores em vez de fazer grep em um checkout que ele não possui.
Via HTTP (a stack em execução o serve em /mcp/):
# Mint a token (or issue one from Settings → MCP in the UI)
docker compose exec api analyzer mcp issue-token \
--scopes "read:graph read:groups" --duration 86400
// ~/.claude.json (or .mcp.json in a project)
{
"mcpServers": {
"celmis": {
"type": "http",
"url": "http://localhost:8000/mcp/",
"headers": { "Authorization": "Bearer <the token you just minted>" }
}
}
}
Via stdio, sem o salto HTTP:
{
"mcpServers": {
"celmis": {
"command": "docker",
"args": ["compose", "exec", "-T", "api", "analyzer", "mcp", "serve"]
}
}
}
O que um agente pode perguntar
O mount HTTP serve 18 ferramentas. Elas respondem às perguntas que um grep não consegue:
list_workspace_repos | quais repositórios existem, indexados, documentados, com auto-review ativado |
search_symbols | onde uma função ou endpoint é definido, em todo um projeto |
find_consumers | quais repositórios chamam um símbolo — incluindo aqueles que você nunca clonou |
get_api_surface | os handlers HTTP que um serviço realmente expõe |
get_owner · list_deprecations | quem é o dono de um arquivo; o que está em descontinuação e quem ainda usa |
route_incident | dado um stack trace, a qual repositório e dono ele pertence |
bootstrap_client · start_integration_walk | o que um cliente precisa para chamar o serviço de outra equipe |
get_dep_audit · list_dep_findings | a última auditoria e suas descobertas, das piores primeiro |
get_review · get_review_policy | a revisão mais recente de um PR e quais agentes rodam onde |
Os dois transports não são o mesmo conjunto. analyzer mcp serve via stdio
serve 13 ferramentas mais antigas, em formato de grafo (find_symbol, find_callers,
query_graph); o mount HTTP serve as 18 acima. Nenhum é um subconjunto do
outro — escolha o transporte pelas ferramentas que você quer.
Um guia passo a passo, com os escopos que cada ferramenta precisa e os modos de falha,
está em .claude/skills/celmis-mcp/SKILL.md.
O Claude Code o pega automaticamente quando este repositório está aberto.
O que o agente pode pedir
Uma chamada de search_symbols, um símbolo de contrato, e ele volta de dois
repositórios em duas linguagens — para um cliente que não fez checkout de nenhum dos dois. A
fronteira que um diff nunca cruza é a que isto torna comum.
Dezoito ferramentas, servidas via Streamable HTTP em /mcp/ e autenticadas com o
mesmo token bearer que /api/:
| Ferramenta | Responde |
|---|---|
list_repos | quais repositórios estão indexados e quão recente está cada índice |
list_groups | quais repositórios estão agrupados, para que perguntas entre repositórios tenham um escopo |
find_symbol | onde um nome é definido, em todos os repositórios indexados |
get_symbol | a própria definição, com seu arquivo e intervalo de linhas |
find_callers | o que chama isto — a pergunta que um grep responde mal e um grafo responde com exatidão |
find_callees | o que isto chama, um salto adiante |
cross_repo_edges | chamadas que cruzam uma fronteira de repositório |
query_graph | Cypher somente leitura, para perguntas que as sete acima não moldam |
cross_repo_edges é a que vale a pena entender, porque é a razão pela qual este
produto carrega um grafo de símbolos. Um revisor somente de diff — toda ferramenta na
tabela de benchmark acima, incluindo esta quando o grafo está vazio — pode dizer
que uma assinatura de função mudou. Ele não pode dizer que um serviço em um
repositório diferente ainda chama o formato antigo, porque nunca teve esse
repositório aberto. Agrupe os repositórios uma vez, e essa pergunta se torna
respondível:
> which services outside this repo call PaymentGateway.charge?
É também por isso que nossa posição no benchmark subestima o produto em vez de descrevê-lo: o conjunto de benchmark é de pull requests isolados de repositório único, então não há repositório irmão para uma aresta cruzar. A capacidade é real e o benchmark não consegue vê-la — o que é uma afirmação sobre o benchmark, não uma afirmação que você deve aceitar por fé. Aponte um cliente MCP para seu próprio grupo e verifique.
Resultados
O Celmis foi executado no Martian Code Review Bench
conjunto offline: 50 pull requests selecionados, 173 comentários dourados escritos por humanos, pontuados
contra o conjunto dourado por um juiz LLM. Medido em e0db376 com
gemini-3.6-flash a temperatura 0,1, sem tokens de raciocínio.
| Juiz | F1 | Precisão | Recall | Posição |
|---|---|---|---|---|
| claude-opus-4.5 | 47,5% | 52,4% | 43,4% | 17 / 50 |
| claude-sonnet-4.5 | 44,9% | 48,0% | 42,2% | 17 / 50 |
| gpt-5.2 | 42,7% | 46,0% | 39,9% | 17 / 50 |
O F1 varia 4,8 pontos dependendo de quem julga. A posição não varia nada — décimo sétimo sob os três.
A execução inteira custou $5,88 — $0,118 por pull request — e produziu 153 descobertas, 3,06 por PR (defeito 114, segurança 27, contrato 6, estrutural 6).
Por que esta comparação é justa. A Martian publica suas próprias avaliações de 49 ferramentas no repositório de benchmark, produzidas pelos mesmos três juízes nos mesmos 50 PRs contra os mesmos dourados. Não re-pontuamos ninguém: as linhas deles são tomadas como publicadas e a nossa é anexada. Reproduza a tabela inteira com:
python3 autoloop/offline_table.py anthropic_claude-sonnet-4-5-20250929
Offline não é o leaderboard público. A Martian executa dois benchmarks. O leaderboard público é o online — 200.000 pull requests reais pontuados pelo que os desenvolvedores realmente corrigiram. Esta tabela é a offline — 50 PRs selecionados pontuados contra um conjunto dourado. Elas medem coisas diferentes e os números não são intercambiáveis. Afirmações da forma "ferramenta X é nº 1 na Martian" geralmente se referem à tabela online, a uma métrica diferente ou a um juiz diferente.
O que este número não contém. O grafo estava vazio para todos os 50 PRs
(graph_status nulo, drift vazio em todos), porque o conjunto de benchmark é
pull requests isolados de repositório único — não há serviço irmão para um
símbolo ter consumidores. Drift entre repositórios, a coisa para a qual este produto
carrega um grafo de símbolos, contribuiu exatamente com nada para a pontuação acima. Não é
mensurável aqui, e não estamos afirmando isso a partir desta tabela. Veja
Repositórios de teste para vê-lo funcionar em código real.
Auditoria dos falsos positivos
A pontuação de benchmark tem um piso estrutural: o juiz compara nosso comentário contra uma lista finita de dourados escritos por humanos, então uma descoberta correta que o anotador nunca escreveu é contada como falsa por construção. Abrimos todos os 79 nossos na fonte no commit medido e atribuímos um veredito a cada um.
Das 79 descobertas pontuadas como falsos positivos, 33 são defeitos reais que o conjunto dourado não contém, 38 estão genuinamente erradas e 8 não puderam ser resolvidas a partir do código. Isso coloca a precisão verdadeira desta execução entre 69,7% e 75,0% em vez da medida de 48,0% — mas essa figura corrigida não pode ser comparada com nada na tabela acima, porque ninguém auditou as outras ferramentas da mesma forma e seus falsos positivos quase certamente contêm uma parcela semelhante de defeitos reais; para comparação com outras ferramentas, os 48,0% medidos são o número honesto, porque é o mesmo método aplicado a todos.
Vinte e quatro das 38 descobertas genuinamente erradas compartilham quatro causas raiz, e nenhuma
delas é "o modelo é fraco" — todas as quatro são sobre o que foi mostrado ao modelo.
A maior é um identificador declarado no mesmo arquivo, mas fora do trecho que
o agente recebeu: um parâmetro de método 26 linhas acima, um import na linha 3, um
attr_reader na linha 18.
O relatório completo dá a afirmação, o código naquele commit, o veredito, o raciocínio e um link permanente para cada um dos 79, para que qualquer veredito possa ser contestado com a mesma evidência diante de você.
Repositórios de teste
Cada revisão na execução acima ainda está ativa e pública. Estes são pull requests reais de projetos reais, bifurcados com seu histórico, carregando os comentários inline que o Celmis escreveu:
| Fork | PRs |
|---|---|
| celmis-bench/keycloak | 9 |
| celmis-bench/grafana | 10 |
| celmis-bench/discourse-graphite | 10 |
| celmis-bench/cal.diy | 10 |
| celmis-bench/sentry | 6 |
| celmis-bench/sentry-greptile | 4 |
Vale a pena abrir primeiro:
- keycloak#17 — um desreferenciamento nulo e uma questão de indexação de código de recuperação no provedor de armazenamento de teste do Keycloak
- grafana#16 — uma falha de Storage registrada contra a métrica Legacy, uma de três instâncias do mesmo erro naquele arquivo
- cal.diy#11 —
forEachcom um callback assíncrono, então as exclusões são fire-and-forget e otryao redor não captura nada - sentry#11 — sete comentários inline em um único PR de consumidor Kafka
Você está lendo saída não editada, incluindo as descobertas que a auditoria acima marca como erradas. Nada foi removido após a pontuação.
Configuração
./scripts/init-env.sh escreve .env a partir de .env.example e
gera cada segredo. O exemplo envia cada segredo vazio de propósito: uma
versão anterior colocava o comando gerador ao lado da variável, arquivos dotenv
não têm comentários inline, e toda instalação que o copiava rodava com uma senha
mestra impressa no repositório.
As configurações chegam aos contêineres apenas através do bloco environment: em
docker-compose.yml — a imagem não carrega .env. Uma variável não nomeada lá
assume seu padrão de código, não importa o que seu .env diga.
GET /healthz relata os relógios de revisão como o processo realmente os resolveu,
que é como você verifica o que chegou.
Os relógios são documentados como um conjunto em .env.example, com o invariante que
os une:
REVIEW_LLM_TIMEOUT_SECONDS × (1 + RETRY_FACTOR) ≤ REVIEW_TIMEOUT_SECONDS
Aumente um e o outro tem que seguir; um teste o impõe.
| Variável | Padrão | |
|---|---|---|
REVIEW_TIMEOUT_SECONDS | 900 | relógio de parede para uma revisão; além disso, os estágios finais recuam e o comentário diz isso |
REVIEW_LLM_TIMEOUT_SECONDS | 300 | uma chamada de modelo. Aumente para ~600 para um modelo de raciocínio lento |
REVIEW_LLM_TIMEOUT_RETRY_FACTOR | 2.0 | quanto mais tempo a nova tentativa ganha após um timeout; 1.0 desativa o alargamento |
REVIEW_MAX_DIFF_SIZE_BYTES | 500000 | diffs maiores são recusados, não truncados |
REVIEW_VERIFIER_ENABLED | false | o veto de falso positivo do LLM |
REVIEW_AGENT_CONCURRENCY | 3 | chamadas de provedor em voo por revisão |
CELMIS_JOB_LEASE_SECONDS | 600 | teto de silêncio do worker antes que um job possa ser reivindicado |
CELMIS_DEPLOYMENT_MODE | single_tenant | multi_tenant isola workspaces uns dos outros |
Operações
docker compose logs -f api # follow the API
docker compose exec api analyzer graph-stats <repo> # what parsed, what did not
./scripts/backup.sh # Postgres + volumes
./scripts/restore.sh <archive>
Admin → Monitoring mostra profundidade da fila, gasto por workspace e configurações de modelo
por agente. Usage & cost detalha o gasto por superfície, para que um build
de documentação em lote não pareça chat.
Fazer deploy para um servidor é ./scripts/deploy-on-server.sh v0.1.0; execute no
servidor: ele puxa as imagens publicadas, sobe a stack atrás do Caddy e
grava o build ao qual o rodapé do AGPL faz referência. Nada fora dessa máquina precisa de
credencial para isso. Consulte
docs/ORACLE_CICD.md, ou
docs/HETZNER.md para uma VM simples.
Desenvolvimento local
# Postgres and Qdrant from compose, everything else on the host
docker compose up -d postgres qdrant
python -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]"
alembic upgrade head
uvicorn src.api.main:app --reload --port 8000
cd web && npm install && npm run dev # http://localhost:3000
pytest -q # the suite
ruff check . # lint, ratcheted at zero
cd web && npx tsc --noEmit
Referência da CLI
analyzer é instalado por pip install -e .; dentro do Docker use
docker compose exec api analyzer …. Todo comando aceita --help.
analyzer init | cria a estrutura do workspace |
analyzer index <path|url> | analisa um repositório no grafo |
analyzer ask "<question>" | uma pergunta, resposta com citação |
analyzer chat | sessão interativa |
analyzer review <provider> <repo> <pr> | revisa um pull request; --post publica |
analyzer generate | constrói o vault de documentação |
analyzer refresh | reindexa o que mudou |
analyzer graph-stats <repo> | o que foi analisado, por linguagem |
analyzer serve | a API sem Docker |
analyzer review-serve | o receptor de webhook sozinho |
Subcomandos agrupados: analyzer repo, analyzer group, analyzer auth,
analyzer mcp, analyzer scip.
Arquitetura
┌──────────────┐
GitHub / GitLab ──▶│ webhook │──┐
Bitbucket └──────────────┘ │
▼
Browser ──▶ web (Next.js) ──▶ api (FastAPI) ──▶ Postgres jobs, policies, audit
│ Qdrant embeddings
│ sandbox untrusted execution
▼
model provider
(direct, or via a LiteLLM gateway)
- Postgres guarda jobs, políticas, histórico de execução, gastos e o log de auditoria. A
fila de jobs durável é uma tabela — a retirada é
SELECT … FOR UPDATE SKIP LOCKED, e um worker renova sua concessão enquanto trabalha, em vez de adivinhar uma duração antecipadamente. - Qdrant guarda embeddings, uma coleção por instalação, com isolamento de workspace aplicado no filtro.
- sandbox executa qualquer coisa não confiável — uma suíte de testes, um build — como seu próprio uid em sua própria rede, sem banco de dados, sem chaves e com root somente leitura.
- LiteLLM é opcional. Defina
LITELLM_PROXY_URLeLITELLM_MASTER_KEYjuntos e toda chamada passa pelo gateway; deixe qualquer um vazio e as chaves do provedor são usadas diretamente.
Solução de problemas
Um contêiner não inicia. docker compose logs <service>. A API informa
na inicialização quais recursos opcionais estão indisponíveis e por quê, em vez de falhar
silenciosamente.
As revisões não produzem nada. Verifique GET /healthz para os relógios resolvidos, depois
docker compose logs api | grep agent_. Cada agente registra seu tempo decorrido, seu
modelo e seu código de falha.
Um timeout, não uma interrupção. local_timeout significa que o prazo desta instalação
expirou antes de o provedor responder — aumente
REVIEW_LLM_TIMEOUT_SECONDS. Isso é deliberadamente não relatado como uma falha
do provedor.
P&R não cita nada. O repositório provavelmente não está indexado, ou está indexado
sem embeddings. Repositórios mostra o estado de cada um; analyzer graph-stats <repo> mostra o que foi analisado.
O sandbox está sempre ocupado. SANDBOX_SLOTS é quantos jobs rodam ao mesmo tempo
e é o controle que custa memória. SANDBOX_SLOT_WAIT é quanto tempo um chamador
fica na fila antes de ser avisado para voltar.
Estrutura do projeto
src/
api/ FastAPI app, routers, schemas
review/ PR review — agents, orchestrator, providers, policies
indexing/ parsers, symbol graph, embeddings
qa/ retrieval and answer composition
generation/ documentation vault
llm/ provider clients, error taxonomy, cost ledger
sync/ git providers, the durable job queue, workers
sandbox/ the isolated execution server
mcp_server/ the MCP surface
security/ redaction, patterns, log filtering
web/ Next.js UI (App Router, 16 locales)
tests/ 5200+ tests
deploy/ Caddy overlay and the LiteLLM gateway config
docs/ deploy guides and the end-to-end walk-through
bench/ benchmark harness and results
Proveniência e direitos
Este repositório tem um único commit raiz com cerca de cem mil linhas — a forma que um despejo de código de origem incerta tem para um scanner de proveniência, e uma que precisa de uma explicação em vez de um encolher de ombros. Tem uma: PROVENANCE.md declara a posição de licença e a origem do código — o desenvolvimento ocorreu em privado antes deste commit, e nada disso é necessário para construir, auditar ou bifurcar o que está aqui.
Esse arquivo é um registro de fatos, não a licença. A licença é
AGPL-3.0, com uma exceção: qualquer coisa sob ee/, e qualquer arquivo
cujo nome contenha .ee., é coberto por LICENSE_EE em vez disso.
LICENSING.md declara o limite por completo — LICENSE em si é
o texto AGPL não modificado, porque um arquivo de licença com um preâmbulo na frente
não é reconhecido como essa licença.
ee/ não contém código de produto hoje — o limite foi traçado antes da primeira
tag porque adicioná-lo depois significa re-perguntar a cada contribuidor que já
enviou trabalho sob um AGPL sem qualificação.
Tudo enviado aqui é AGPL, incluindo as partes que parecem comerciais: o console de auditoria, uso e gastos, verificações de conformidade, métricas de instalação. Controles de segurança nunca são exclusivos para empresas — o log de auditoria é escrito sob AGPL e sempre será. Consulte CONTRIBUTING.md para saber onde o novo código vai.