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
Auto-hospedado — do alerta que seu monitoramento já enviou até um pull request que alguém pode revisar
celmis-labs.github.io · Documentação · Início rápido · Resultados
Um alerta chega de um serviço que você executa. O Celmis já sabe qual repositório é esse serviço e quem o possui, então a correção começa aí: uma sessão incorporada do Claude Code — na sua assinatura, dentro da sua instalação — edita o código, e o runner faz commit, envia um branch e abre um pull request. Em uma descoberta medida que levou 220 segundos, a partir de qualquer dispositivo que recebeu o alerta primeiro.
O Celmis não monitora seus serviços. O Grafana, ou o que você já usa, faz isso e faz bem. Esta é a parte que vem depois do alerta: o caminho de uma regra disparada até um diff que alguém possa revisar.
Para ser exato, porque a linha é fácil de borrar. O aplicativo não ingere logs próprios — ele começa pelo alerta. Mas o repositório inclui uma camada opcional de observabilidade, e essa coleta os logs:
docker compose -f docker-compose.yml -f docker-compose.observability.yml up -d
Isso sobe Prometheus, Loki, Promtail e Grafana.
docker-compose.observability.yml é deliberadamente
abrangente à plataforma, não apenas ao Celmis: o Promtail rastreia os logs de contêineres do daemon Docker e rotula cada stream pelo nome do contêiner, então todos os serviços nesse host — todos os trinta, se você executar trinta — aparecem no explorador Loki do Grafana, filtráveis por contêiner. O Prometheus coleta api:8000/metrics para os contadores do próprio Celmis também.
Cada porta vincula a 127.0.0.1 e é acessada por um túnel SSH:
ssh -L 3100:localhost:3100 -L 9090:localhost:9090 you@your-server
Isso não é cautela por si só. O Loki não tem autenticação, então
POST /loki/api/v1/push aceita gravações de qualquer pessoa que consiga alcançá-lo, e um armazenamento de logs que estranhos podem gravar não é um armazenamento de logs. GRAFANA_ADMIN_PASSWORD é obrigatório — a pilha não inicia sem um.
Esse ciclo é o produto. Ele se fecha porque tudo está em um único índice: um grafo de símbolos que o Celmis constrói a partir dos seus repositórios uma vez. O mesmo índice responde a perguntas que cruzam fronteiras de repositórios, revisa pull requests, audita dependências em um SBOM CycloneDX e um pacote de evidências verificável, gera documentação e serve cinquenta e uma ferramentas via MCP — superfícies que valem a pena ter, e nenhuma delas é o ponto por si só.
Ele roda em uma 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ê configura.
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 trabalha o resultado.
O que isso compra que uma ferramenta só de diff não pode
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 mudar 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, em qualquer lugar, sem agendar 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 cobertos. O código responde em vez disso, com citações de arquivo:linha → Pergunte ao código |
| Duas equipes compartilham uma integração e nenhuma pode 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 fonte → Quem pode ver o quê |
| Um cliente ou auditor pede seu SBOM | Um botão, CycloneDX, mais um pacote de evidências cujo manifesto eles recalculam — contra um hash que você publica onde o pacote não está → Dependências, SBOM e o pacote de evidências |
| Uma vulnerabilidade chega em uma dependência | Corrija daqui entrega a uma sessão incorporada o repositório, o pacote e a descoberta. Ela edita, o runner envia um branch e abre um PR → Corrija daqui |
| Um pull request precisa de revisão | Agentes leem o diff — e, onde o grafo é 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 pressionar, 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 e vai para o canal de chat do workspace, e Corrija daqui abre uma sessão já segurando o alerta. O runner abre o pull request → Alertas e correção pelo celular |
| Seu próprio agente ou editor precisa entender o código | Aponte-o para /mcp/. Cinquenta e uma ferramentas sobre o mesmo índice, trinta e cinco delas somente leitura, 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 jeito nenhum, 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 que isso é enviado |
| 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 compra que uma ferramenta só de diff não pode
- 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
- Corrija daqui
- Alertas e correção pelo celular
- 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 motor 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 https://github.com/Celmis-labs/Celmis.git 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 pacote do navegador pede um caminho relativo, que é a única maneira de uma imagem publicada servir cada instalação em vez de apenas aquela em que foi construída.
Para trabalhar NO Celmis em vez de executá-lo, adicione a camada 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 levantada 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 a senha), ambos em
.env. Quem executa 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
Uma conta promovida (ou uma que um OIDC_ADMIN_ROLE torna admin) é um admin global: status do sistema, operações, conformidade, todos os workspaces visíveis. Não é o superadmin — esse é apenas a conta mestra, e é a única que pode distribuir os papéis abaixo que carregam poder.
Papéis de workspace
Cada workspace é um locatário, e uma pessoa pode ter um papel diferente em cada um — admin de um, editor de dois outros.
| Papel | Pode fazer nesse workspace |
|---|---|
viewer | ler |
member | ler; mudar o status de um problema de revisão |
editor | o editor de prompts: prompts de sistema de agentes, políticas de revisão por repositório (modelo de prompt, regras de pasta, substituições de prompt por agente), análises. Sem membros, convites, equipes, chaves de LLM, conexões git ou licença |
admin | direitos de editor, mais membros e visualizadores, convites, equipes, chaves de LLM, conexões git |
owner | direitos de admin, mais excluir o workspace |
Quem pode conceder o quê:
- owner, admin, editor — apenas o superadmin pode: conceder, alterar para ou remover. Um admin de workspace não pode rebaixar o owner ou outro admin.
- member, viewer — o superadmin, ou o owner/admin daquele workspace (diretamente ou por convite; um convite carrega apenas um papel que seu criador poderia conceder, revalidado quando é aceito). Uma conta existente é adicionada diretamente apenas se você já compartilha um workspace com ela; qualquer outra pessoa recebe um convite que precisa aceitar.
- Links de redefinição de senha emitidos por um admin de workspace são limitados por todos os workspaces aos quais a pessoa pertence, não apenas este. No modo multi-tenant, todos possuem um workspace pessoal, então na prática apenas o superadmin pode emitir um. Nenhuma rota emite um para a conta master.
- Workspaces compartilhados são criados pelo superadmin. Todos recebem um workspace pessoal próprio no cadastro e são seus owners.
O superadmin gerencia pessoas entre workspaces em Administração →
Usuários (/admin/users); toda alteração de associação, de qualquer página, é
registrada no log de auditoria com o ator, a pessoa, o workspace e o papel antigo e
novo. O filtro Sem acesso a equipe lista as contas cujo único workspace é o
pessoal — independentemente de como elas entram — das mais recentes para as mais antigas.
Como entrar: convites e solicitações de acesso
Um convite para alguém sem conta. Convidar um endereço de e-mail
que não possui conta cria um link de uso único vinculado ao e-mail. O convidador
o vê (copie-o da página da Equipe); quando o SMTP está configurado, ele também é
enviado por e-mail. Aberto sem login, /invite/<token> mostra o workspace, o papel e quem
o enviou, e oferece login, cadastro (quando AUTH_PASSWORD_LOGIN permite),
Google e SSO da empresa (quando habilitados); cada um retorna ao link para aceitar.
Aceitar revalida que o convidador ainda pode conceder aquele papel, é auditado,
e consome o link. Um link expirado, revogado ou usado informa qual é o caso; um
link vinculado a e-mail aberto por outra conta é recusado.
Resgate automático — apenas endereços verificados. Quando alguém entra
com Google ou SSO da empresa e o provedor de identidade marca o e-mail como
verificado (email_verified), os convites pendentes enviados para esse e-mail são
resgatados no login, pelo mesmo caminho de aceite (regra de concessão revalidada,
auditada, uso único). Uma conta de senha nunca é resgatada pelo seu
endereço: o Celmis não verifica o endereço digitado no cadastro, então qualquer
pessoa pode registrar o e-mail de outra. Uma conta de senha abre o link que
recebeu e aceita ali — e o mesmo faz uma conta que tem senha e
Google/SSO vinculados, já que quem definiu essa senha ainda pode entrar.
O caminho automático apenas adiciona alguém: um convite para um workspace no qual
a pessoa já está é consumido sem alterar seu papel, e remover um membro revoga
os convites ativos enviados a ele para aquele workspace.
Solicitações de acesso. Uma pessoa logada cujo único workspace é o
pessoal vê "Você ainda não tem acesso a workspaces de equipe — envie uma
solicitação" no painel e em /access-request, com um comentário opcional
(até 1000 caracteres). Uma solicitação pendente por vez; ela pode ser cancelada,
e uma nova pode ser enviada após uma rejeição. A solicitação não nomeia nenhum workspace, e o
solicitante não vê nenhum até que seja aprovada. Apenas o superadmin decide,
em Administração → Solicitações de acesso (/admin/access-requests): aprovar
com um ou mais pares (workspace, papel) — aplicados tudo ou nada, cada
associação auditada com via="access_request" — ou rejeitar com um motivo que o
solicitante lê. Acesso concedido por outro meio (a página de Usuários, um convite
aceito) encerra uma solicitação pendente como aprovada. Solicitações decididas permanecem listadas como
histórico. O solicitante vê
a decisão em /access-request e como um toast na próxima visita, e um
workspace aprovado aparece no seletor sem sair da conta. Com SMTP
configurado, o solicitante também recebe a decisão por e-mail, e o endereço master
recebe uma nova solicitação; não há outro canal de notificação.
POST /api/access-requests {comment?} | solicitar (409 se houver uma pendente, ou a conta já tiver acesso a equipe) |
GET /api/access-requests/me | elegibilidade e a solicitação mais recente |
DELETE /api/access-requests/me | cancelar a pendente |
GET /api/admin/access-requests?status= | superadmin: listar, pendentes primeiro |
POST /api/admin/access-requests/{id}/approve {grants:[{workspace_id, role}]} | superadmin: um ou mais, únicos, existentes, não pessoais |
POST /api/admin/access-requests/{id}/reject {reason} | superadmin: motivo obrigatório |
Decidir uma solicitação que não está mais pendente responde 409.
Conectar 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 interface só mostra novamente o primeiro e o último 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 job 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 — o que os agentes de revisão raciocinam) e embeddings no Qdrant (o que o 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 é informado explicitamente 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 solicitante tem permissão de ver — 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
Os 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 em Repositórios de teste, e a saída lá é não editada, incluindo as descobertas que a auditoria abaixo marca como erradas.
Onde o grafo é 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 é construído, a revisão ainda é executada — apenas responde à pergunta mais restrita, que é o que o benchmark mediu.
Falando com o revisor, e o que ele aprende
Além da primeira revisão, o revisor trabalha dentro do pull request:
- Comandos de comentário.
@celmis help,start-review,review --force,remember: <rule>, e uma pergunta de texto livre respondida no mesmo tópico (Comandos de PR). - Revisão incremental e cadência. Um novo push é revisado a partir do último commit revisado; um repositório pode pausar revisões automáticas após uma rajada de pushes, pular títulos por palavra-chave e escrever o resumo na descrição (Configurações de revisão).
- Memórias e aprendizado da equipe. Regras que a equipe declara e descobertas que descarta moldam revisões posteriores, com um modo de sombra para avaliar o efeito primeiro (Memórias e aprendizado).
- Backlog de problemas. As descobertas são acompanhadas entre pushes e após o merge, e se resolvem quando o código mostra que o defeito desapareceu (Backlog de problemas).
- Contexto de tarefa do Jira. O agente de lógica de negócios verifica a alteração contra a tarefa que um pull request nomeia, com uma lista de verificação de critérios de aceite (Jira).
- Métricas de produtividade. Tempo de ciclo e as quatro medidas DORA do seu histórico de pull requests, para owners e admins (Produtividade).
- Quem pode ver o quê. Papéis e acesso. Cada um desses recursos lê um repositório apenas para pessoas que podem lê-lo, pela mesma regra do MCP (Acesso MCP); respostas de chat e a verificação do Jira nunca citam código que o solicitante não pode abrir.
- Um perfil MCP compacto para assistentes de codificação.
/mcp/devserve nove ferramentas somente leitura dimensionadas para um editor, comhowtopara "como este serviço faz autenticação" e um plugin do Claude Code (O perfil do desenvolvedor, howto).
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 revelaram-se defeitos reais que o conjunto dourado não contém. Esse trabalho está em Auditoria dos falsos positivos, com o código e um link permanente 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 a ele uma chave, escreve o resumo — ele não decide o que é vulnerável.

Dois arquivos saem de cada auditoria, e nenhum precisa de 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 descoberta, o histórico de execuções passadas e um sha256 de cada arquivo. Esses hashes provam que o arquivo é internamente consistente; eles não provam por si só que é o arquivo que saiu daqui, porque o manifesto não registra hash para si mesmo e qualquer pessoa que editar um arquivo pode reescrever sua entrada. A exportação retorna o sha256 do próprio manifesto em
X-Celmis-Manifest-SHA256— publique isso onde o pacote não está, e um terceiro pode então verificá-lo sem precisar confiar em nós, contra um valor que você não entregou a ele com o arquivo. Uma pasta cujo conteúdo pode ser alterado depois não prova nada; o manifesto é o que a torna evidência.Essa última frase costumava ser uma afirmação que ninguém podia usar: verificar um pacote significava clonar este repositório e instalar quarenta dependências sob Python 3.13 — toda a cadeia de ferramentas do produtor, para verificar a saída do produtor. A verificação agora é seu próprio pacote, sem dependências e sem chamadas de rede:
pip install celmis celmis verify evidence-pack.zipQualquer Python a partir de 3.9, offline, em uma máquina que nunca executou o Celmis. Ele recalcula cada sha256 que o manifesto lista e relata listado-mas-ausente, incompatível e presente-mas-não-listado; saída 0 limpa, 1 problemas encontrados. Este é apenas o verificador — a plataforma em si são seis serviços sob docker compose e não instala com pip, e o pacote informa isso quando executado sem argumentos.
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 à pergunta de 24 horas sem visibilidade no nível de componentes primeiro — para relatar o que é afetado, você precisa saber o que está dentro.
O Celmis não afirma 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. 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 daqui
Encontrar algo é metade de um ciclo. Uma sessão embutida do Claude Code roda dentro da instalação, edita o checkout, e o runner faz commit, envia um branch e abre um pull request.
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, do jeito que o Cursor faz: rode 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 usa.
Dois slots, resolvidos nessa ordem:
| Pessoal | sua própria assinatura, invisível para qualquer outra pessoa |
| Workspace | uma assinatura que um admin compartilha com o workspace — aceitação explícita |
O slot do workspace está desligado 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 workspace isolado por sessão. O que isso compra não é "acesso à nuvem" — é que a máquina que guarda seu código é uma que você controla, e você a alcança de um laptop em um escritório ou de um telefone no trem pela mesma razão que alcança qualquer serviço que você roda: porque é seu e está de pé.
Esse é o todo de Alertas, e corrigir do telefone — um alerta chega, e a correção começa de onde você estiver lendo.
Uma vulnerabilidade na auditoria de dependências carrega um botão Corrigir daqui. 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 loops, 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 ele abriu, no GitHub:

Olhe 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 disse 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 rodou um comando no sandbox para verificar. E a tarefa disse "apenas manifestos, não toque em dependências não relacionadas", então a mudança é exatamente uma linha.
O que o runner permite, e o que não permite
Isso é decidido pelo runner, não pelo prompt — que é a parte que vale ler antes de conceder qualquer coisa a um agente:
- Sem shell próprio.
Bash,WebFetch,WebSearche edição de notebook são desabilitados. Comandos rodam 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 runner. O agente nunca faz commit ou push. Quando o trabalho está feito — ou quando você pressiona Finalizar e enviar — o runner 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 na 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 terminou. - 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 workspace. A API nunca o retorna depois de salvo — apenas se ele está lá e se ainda funciona.
Alertas, e corrigir do telefone
O loop acima começa de um achado de dependência. Ele também começa da produção.
Aponte qualquer sistema de monitoramento para a URL de ingestão do workspace e seus alertas caem no Celmis:
POST /webhook/alerts/{workspace_id}.{secret}
O webhook de alertas unificados do Grafana é analisado como está; qualquer outra coisa pode postar
{"title", "body", "severity", "repo"}. A metade secreta é armazenada criptografada com Fernet
por workspace e comparada em tempo constante. O endpoint é não autenticado por
design — sistemas de monitoramento não conseguem fazer OAuth — e limitado ao tenant por
construção: um token só pode escrever no workspace ao qual pertence.
O que isso compra é a rota sem laptop nela:
- Um alerta dispara. Ele chega ao Celmis e vai direto para qualquer canal que o workspace tenha vinculado — Slack, Discord, Google Chat ou um webhook simples — então ele encontra você onde você já está, em vez de em uma página que você precisa lembrar de abrir.
- Você abre. O alerta carrega seu repositório, porque
route_incidentpode pegar um stack trace e dizer a qual repositório e qual dono ele pertence. - Você pressiona Corrigir daqui. A sessão abre já segurando o alerta — não um chat vazio.
- O runner 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 ele.
A mesma página lista todos os alertas que o workspace 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 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 rodar.
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. É.
Peça 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. Trabalho de objeto único permanece nos botões, onde pertence.
Nada roda no primeiro pressionar. Interpretação é um palpite, e esses verbos custam dinheiro e horas — uma construção de vault, 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 pressionar não pode silenciosamente entrar em um conjunto que disse "tudo". A mesma regra que governa o resto do produto — a verificação roda onde o trabalho acontece — governa isso.
Um chamador automatizado nunca pode enfileirar um fan-out ilimitado. Os verbos são limitados, com escopo de workspace, e aceitam um ator explícito: nada aqui lê um contexto de requisição ambiente, porque um conector processando uma fila não tem requisição, e uma função que adivinha um workspace é o jeito de a automação de um tenant alcançar os repositórios de outro tenant.
Três chamadores estão convergindo nesses mesmos verbos — um agente externo via MCP, o agente embutido e um conector de ticket que transforma audite esses quatro serviços em trabalho e posta o resultado de volta. Eles compartilham uma implementação de propósito: um segundo "iniciar auditoria" é um segundo conjunto de regras sobre execuções ao vivo, deduplicação e reinícios forçados, 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 cada superfície ao mesmo tempo — Q&A, grafo, busca de código, documentação gerada, 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 recebem respostas; esses arquivos são recusados na fonte, não filtrados de uma resposta que já os continha.
Linguagens e formatos
Dezessete módulos de grafo, mais um caminho genérico através de consultas de tags tree-sitter para linguagens 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 workflows de CI. Esta é a parte que a maioria das ferramentas de inteligência de código pula, 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ê | Captura |
|---|---|---|
install_script | package.json hooks de ciclo de vida | uma dependência que roda 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 puxada 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, depois repositórios irmãos | uma constante alterada em um repositório e deixada para trás nos outros |
A varredura comum de CVE está deliberadamente fora dessa lista. O OSV-Scanner já
faz isso, é gratuito e é o padrão de facto — o Celmis o executa (mais o
auditor de cada ecossistema: pip-audit, npm audit, govulncheck,
cargo audit) e trata o resultado como uma entrada, em vez de um recurso.
Sobre conformidade. O Celmis produz os artefatos que uma auditoria solicita — 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/):
# A token is per person and names the repositories it may read. The
# superadmin issues it (Administration, MCP tokens) or on the server:
docker compose exec api analyzer mcp issue-token \
--user dev@example.com --workspace acme --repos 'acme/shop-*' --days 30
O token nunca alcança um repositório que sua pessoa não possa ler, e cada chamada é auditada. Um repositório para o qual ninguém tem uma regra é fechado para membros. Como acesso, tokens e a auditoria funcionam está em Acesso MCP.
// ~/.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>" }
}
}
}
Um projeto, para um cliente externo. Na página de um projeto, o superadmin pode
criar um token de projeto (cmcp_..., de uma hora a 90 dias, mostrado uma vez, revogável).
Ele alcança apenas os repositórios daquele projeto, limitado pelo escopo de arquivos do projeto,
por meio de duas ferramentas, search_project e ask_project. Detalhes e o
comando claude mcp add estão em Arquivos e tokens de projeto.
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 51 ferramentas, e um cliente não necessariamente vê todas
elas. Trinta e cinco são somente leitura; as dezesseis que escrevem — add_repo,
start_dep_audit, cancel_dep_audit, generate_docs, set_auto_review,
migrate_consumers, ack_alert, retry_job, cancel_job, set_budget,
update_review_setting, propose_review_rules, generate_review_rules,
review_pr, index_repo, update_issue —
exigem um escopo de escrita (write:repos, write:config, write:reviews) que um
token padrão não carrega, então elas estão ausentes da lista de ferramentas de um cliente somente leitura
e são recusadas se chamadas pelo nome. Isto é o que as somente leitura respondem, e elas são
as perguntas que um grep não consegue:
list_workspace_repos | quais repositórios existem, indexados, documentados, com auto-revisão ativada |
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 é dono de um arquivo; o que está saindo e quem ainda o 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 |
list_reviews · get_review_run | as execuções de revisão mais novas, e as descobertas de uma execução (por id de execução, ou repo + número do PR) |
list_issues | problemas de revisão rastreados, dos piores primeiro, com contagens por status |
ask_code · search_code | uma resposta escrita sobre o código (uma chamada de modelo, registrada como Q&A); pesquisas de símbolo, usos, dono e arquitetura |
O mount HTTP também escreve com review_pr (enfileirar uma revisão de um PR ou de todos os abertos), index_repo e update_issue (definir o status de um problema), tudo atrás de write:repos.
| get_review_settings | as configurações de revisão em vigor e de onde vem cada valor |
| get_spend · get_usage · get_budget | gasto de LLM por modelo, superfície e dia; uso de revisão; o limite mensal |
| list_alerts · list_jobs | alertas recebidos; trabalhos em segundo plano com contagens por status |
| audit_delta · export_sbom | o que mudou desde a auditoria anterior; a URL de download do SBOM |
| list_members | membros do workspace com papéis e equipes |
| howto | como um repositório faz uma coisa (db, auth, config, http_client, logging, messaging, cache): fatias de código, os nomes de env e configuração que ele lê e de onde vêm seus valores, nunca os valores; precisa de read:graph em /mcp/, read:code em /mcp/dev/ |
Os dois transportes 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 35 somente leitura (as duas ferramentas de token de projeto, search_project e ask_project, entre elas). 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 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.
A tabela abaixo é o conjunto em formato de grafo do servidor stdio (analyzer mcp serve, 13 ferramentas).
O mount HTTP em /mcp/ serve 51 ferramentas no total (35 de leitura, 16 de escrita) via Streamable HTTP,
autenticado como /api/, e um token vê apenas as que seus escopos permitem. As consultas
de grafo:
| Ferramenta | Respostas |
|---|---|
list_repos | quais repositórios estão indexados e quão atualizado 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 definição em si, com seu arquivo e intervalo de linhas |
find_callers | o que chama isto — a pergunta que um grep responde mal e um grafo responde exatamente |
find_callees | o que isto chama, um salto para fora |
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 apenas por diff — toda ferramenta na
tabela de benchmark acima, incluindo esta quando o grafo está vazio — pode lhe dizer
que uma assinatura de função mudou. Ele não pode lhe dizer que um serviço em um
repositório diferente ainda chama a forma antiga, porque nunca teve aquele
repositório aberto. Agrupe os repositórios uma vez, e essa pergunta se torna
respondível:
> which services outside this repo call PaymentGateway.charge?
Esta também é a razão pela qual nossa classificação de 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 pode 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 | Classificaçã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 move 4.8 pontos dependendo de quem julga. A classificação não se move — décimo sétimo sob todos 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: suas linhas 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. Eles medem coisas diferentes e os números não são intercambiáveis. Afirmações da forma "ferramenta X é #1 na Martian" geralmente se referem à tabela online, uma métrica diferente ou 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 zero 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.
Dos 79 descobertas pontuadas como falsos positivos, 33 são defeitos reais que o conjunto dourado não contém, 38 estão genuinamente errados e 8 não puderam ser resolvidos a partir do código. Isso coloca a precisão verdadeira desta execução entre 69.7% e 75.0% em vez dos 48.0% medidos — mas essa figura corrigida não pode ser comparada com nada na tabela acima, porque ninguém auditou as outras ferramentas da mesma maneira 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 dos 38 descobertas genuinamente erradas compartilham quatro causas raiz, e nenhuma
delas é "o modelo é fraco" — todas as quatro são sobre o que o modelo viu.
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 permalink 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 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 todos os segredos. O exemplo entrega 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 pelo bloco environment: em
docker-compose.yml — a imagem não carrega .env. Uma variável não nomeada ali
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 precisa acompanhar; um teste garante isso.
| Variável | Padrão | |
|---|---|---|
REVIEW_TIMEOUT_SECONDS | 900 | relógio de parede para uma revisão; além disso, os estágios finais param 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 andamento 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, então uma compilação de documentação em lote não parece um chat.
Implantar em 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
carimba o build ao qual o rodapé AGPL vincula. Nada fora dessa máquina precisa de uma
credencial para isso. Veja
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 o layout do workspace |
analyzer index <path|url> | analisa um repositório no grafo |
analyzer ask "<question>" | uma pergunta, resposta citada |
analyzer chat | sessão interativa |
analyzer review <provider> <repo> <pr> | revisa um pull request; --post publica |
analyzer generate | compila 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, gasto 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 chaves de provedor são usadas diretamente.
Solução de problemas
Um contêiner não inicia. docker compose logs <service>. A API diz
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.
Q&A não cita nada. O repositório provavelmente não está indexado, ou está indexado
sem embeddings. Repositories 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 de uma vez
e é o botão que custa memória. SANDBOX_SLOT_WAIT é quanto tempo um chamador
espera 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 aconteceu em privado antes deste commit, e nada disso é necessário para compilar, 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.
Dois recursos empresariais vivem atrás disso hoje — login único (OIDC /
Keycloak) e análises de revisão, em src/ee/ e web/ee/, ativados por uma
chave de licença offline (veja ee/README.md). O limite foi traçado
antes da primeira tag porque adicioná-lo depois significa perguntar novamente a cada
contribuidor que já enviou trabalho sob um AGPL sem qualificação.
Todo o resto enviado aqui é AGPL, incluindo as partes que parecem comerciais: o console de auditoria, uso e gasto, verificações de conformidade, métricas de instalação. Controles de segurança nunca são exclusivos da versão empresarial — o log de auditoria é escrito sob AGPL e sempre será. Veja CONTRIBUTING.md para onde o novo código vai.