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:

Ask the code, answering across two repositories

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 funcionaPergunte. 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 responderCada 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 outraCarregue-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 SBOMUm 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ênciaCorrigir 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ãoAgentes 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 nelesEscreva 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 mesaEle 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 codebaseAponte-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 segundosde git clone a seis serviços saudáveis, medido em um servidor limpo
$0,118por pull request revisado, no modelo com o qual isso é entregue
17º de 50no 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

Início rápido

O que você precisa

Docker24+ com Compose v2Docker Desktop no macOS/Windows, o engine nativo no Linux
Uma chave de API de modeloum deGoogle Gemini, Anthropic, OpenAI, OpenRouter, Groq ou Mistral. Uma chave gratuita do Gemini é suficiente para avaliar: https://aistudio.google.com/app/apikey
RAM~4 GB livresMedido 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.

First install: clone, generate .env, bring the stack up

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

  1. Configurações → Configuração de LLM — cole uma chave do provedor. Ela é criptografada com CREDENTIAL_MASTER_KEY antes de tocar o banco de dados, e a UI só mostra novamente o primeiro e os últimos quatro caracteres.
  2. 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.
  3. 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:

A project holding several repositories

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.

Compliance artefacts: SBOM, evidence pack, technical documentation

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:

Pessoalsua própria assinatura, visível para ninguém mais
Espaço de trabalhouma 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:

The session, pre-filled from a dependency finding

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:

The pull request the agent opened, one line changed

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.

The finished session, with its branch and a link to the pull request

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, WebSearch e 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:

  1. 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.
  2. Você o abre. O alerta carrega seu repositório, porque route_incident pode pegar um stack trace e dizer a qual repositório e a qual proprietário ele pertence.
  3. Você pressiona Corrigir com Claude. A sessão abre já contendo o alerta — não um chat vazio.
  4. O executor faz commit, envia um branch e abre o pull request.

An alert, ingested and bound to its repository

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çãoefeito
visibility: noneo repositório não existe para pesquisa
visibility: metadataapenas documentação e notas de arquitetura
visibility: codeo código-fonte é legível
deny_globsvence mesmo em code — credenciais, criptografia, conexões de banco de dados, verificação de segredos
allow_globsuma 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çãoDetecta
install_scripthooks de ciclo de vida do package.jsonuma dependência que executa código no momento da instalação
python_build_hookspyproject.toml / setup.pyexecução de código em tempo de build em um pacote Python
cargo_build_scriptCargo.tomluma crate com um build.rs
non_registrymanifestos e arquivos de lockuma dependência obtida de uma URL git ou tarball em vez de um registro
suspect_namea lista de dependênciastyposquats — um nome a uma edição de distância de um pacote popular
lock_driftmanifesto vs. arquivo de lockum arquivo de lock que não corresponde mais ao que o manifesto declara
cross_repo_drifto diff do PR e, em seguida, repositórios irmãosuma 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_reposquais repositórios existem, indexados, documentados, com auto-review ativado
search_symbolsonde uma função ou endpoint é definido, em todo um projeto
find_consumersquais repositórios chamam um símbolo — incluindo aqueles que você nunca clonou
get_api_surfaceos handlers HTTP que um serviço realmente expõe
get_owner · list_deprecationsquem é o dono de um arquivo; o que está em descontinuação e quem ainda usa
route_incidentdado um stack trace, a qual repositório e dono ele pertence
bootstrap_client · start_integration_walko que um cliente precisa para chamar o serviço de outra equipe
get_dep_audit · list_dep_findingsa última auditoria e suas descobertas, das piores primeiro
get_review · get_review_policya 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

An MCP client querying two repositories in one call

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/:

FerramentaResponde
list_reposquais repositórios estão indexados e quão recente está cada índice
list_groupsquais repositórios estão agrupados, para que perguntas entre repositórios tenham um escopo
find_symbolonde um nome é definido, em todos os repositórios indexados
get_symbola própria definição, com seu arquivo e intervalo de linhas
find_callerso que chama isto — a pergunta que um grep responde mal e um grafo responde com exatidão
find_calleeso que isto chama, um salto adiante
cross_repo_edgeschamadas que cruzam uma fronteira de repositório
query_graphCypher 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.

JuizF1PrecisãoRecallPosição
claude-opus-4.547,5%52,4%43,4%17 / 50
claude-sonnet-4.544,9%48,0%42,2%17 / 50
gpt-5.242,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:

ForkPRs
celmis-bench/keycloak9
celmis-bench/grafana10
celmis-bench/discourse-graphite10
celmis-bench/cal.diy10
celmis-bench/sentry6
celmis-bench/sentry-greptile4

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#11forEach com um callback assíncrono, então as exclusões são fire-and-forget e o try ao 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ávelPadrão
REVIEW_TIMEOUT_SECONDS900relógio de parede para uma revisão; além disso, os estágios finais recuam e o comentário diz isso
REVIEW_LLM_TIMEOUT_SECONDS300uma chamada de modelo. Aumente para ~600 para um modelo de raciocínio lento
REVIEW_LLM_TIMEOUT_RETRY_FACTOR2.0quanto mais tempo a nova tentativa ganha após um timeout; 1.0 desativa o alargamento
REVIEW_MAX_DIFF_SIZE_BYTES500000diffs maiores são recusados, não truncados
REVIEW_VERIFIER_ENABLEDfalseo veto de falso positivo do LLM
REVIEW_AGENT_CONCURRENCY3chamadas de provedor em voo por revisão
CELMIS_JOB_LEASE_SECONDS600teto de silêncio do worker antes que um job possa ser reivindicado
CELMIS_DEPLOYMENT_MODEsingle_tenantmulti_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 initcria a estrutura do workspace
analyzer index <path|url>analisa um repositório no grafo
analyzer ask "<question>"uma pergunta, resposta com citação
analyzer chatsessão interativa
analyzer review <provider> <repo> <pr>revisa um pull request; --post publica
analyzer generateconstrói o vault de documentação
analyzer refreshreindexa o que mudou
analyzer graph-stats <repo>o que foi analisado, por linguagem
analyzer servea API sem Docker
analyzer review-serveo 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_URL e LITELLM_MASTER_KEY juntos 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.