Celmis

Dieciocho herramientas sobre un grafo de símbolos de tus repositorios: búsqueda de símbolos entre repositorios, consumidores, superficies de API, hallazgos de dependencias y revisiones, bajo las mismas reglas de acceso por repositorio que el resto del producto. Autoalojado, AGPL-3.0.

Documentación

Celmis

Inteligencia de código autoalojada: consulta tus bases de código, revisa solicitudes de extracción y produce la evidencia que un auditor pide

celmis-labs.github.io · Documentación · Inicio rápido · Resultados

Celmis lee tus repositorios una vez y mantiene un grafo de símbolos de ellos. Todo lo demás —preguntas, revisiones, auditorías de dependencias, documentación generada— es una forma distinta de leer ese grafo. Se ejecuta en una sola máquina bajo docker compose, con el proveedor de modelos que elijas detrás, y nada sale de tu red excepto las llamadas que configures.

En la narración más antigua, Kelmis era el fundidor —uno de los tres Dáctilos Ideos, junto a Damnameneo el martillo y Acmón el yunque, a quienes se atribuía el trabajo del hierro. El índice hace aquí la reducción; las superficies son las que trabajan el resultado.

Lo que esto te da que una herramienta solo de diff no puede

Haz una pregunta que abarque dos repositorios, y la respuesta cita ambos:

Ask the code, answering across two repositories

Eso no es un resultado de búsqueda. La pasarela y el servicio de pagos son repositorios separados sin código compartido, y la respuesta traza la cadena de llamadas entre ellos —luego nota, sin que se lo pidan, que el nombre del tema de Kafka está codificado en ambos y que cambiar uno rompe silenciosamente el otro.

Un revisor que solo lee el diff estructuralmente no puede decir eso. Nunca tuvo el otro repositorio abierto.


Nueve cosas que la gente hace con él

Eres un PM, un líder de entrega o el cliente y quieres saber en qué estado está un grupo de proyectos, o cómo funciona algo realmentePregúntale. Desde cualquier dispositivo, en cualquier lugar, sin reservar tiempo de un ingeniero y sin una reunión cuyo único resultado sea un párrafo → Pregunta al código
Un ingeniero nuevo tiene una pregunta que un senior tendría que responderCada una de esas saca a alguien experimentado de su flujo, justo en el momento en que ya está cubriendo. La base de código responde en su lugar, con citas archivo:línea → Pregunta al código
Dos equipos comparten una integración y ninguno puede leer el repositorio del otroCárgalo, concede el derecho a preguntar y deniega las rutas que deben permanecer privadas. Ellos obtienen respuestas; las credenciales se rechazan en el origen → Quién puede ver qué
Un cliente o un auditor pide tu SBOMUn botón, CycloneDX, más un paquete de evidencia cuyo manifiesto les permite verificarlo sin confiar en ti → Dependencias, SBOM y el paquete de evidencia
Una vulnerabilidad llega a una dependenciaFix with Claude entrega a una sesión integrada el repositorio, el paquete y el hallazgo. Edita, el runner empuja una rama y abre un PR → Fix with Claude
Una solicitud de extracción necesita revisiónLos agentes leen el diff —y, donde el grafo está construido, quién más llama a lo que se está cambiando, incluso desde otro repositorio → Revisión de solicitudes de extracción
Cuarenta servicios necesitan que se les haga lo mismoEscribe la frase. Celmis muestra a qué repositorios resuelve y espera una segunda pulsación, en lugar de encontrarlos entre cuarenta y pulsar un botón cuarenta veces → Pide trabajo entre repositorios
Una alerta se dispara a las 02:00 y no estás en un escritorioLlega a Celmis, una notificación web llega a tu teléfono, y Fix with Claude abre una sesión que ya tiene la alerta. El runner abre la solicitud de extracción → Alertas y arreglos desde el teléfono
Tu propio agente o editor necesita entender la base de códigoApúntalo a /mcp/. Dieciocho herramientas sobre el mismo índice, bajo las mismas reglas de acceso —sin segunda copia de tu código en ningún sitio → Conecta Claude Code y otros clientes MCP

Las tres primeras son las que una herramienta de revisión de código no hace en absoluto, y son la razón por la que esto es una plataforma en lugar de un revisor: indexa una vez, luego lee ese índice desde el lado del trabajo en el que estés.

Tres números

197 segundosdesde git clone hasta seis servicios saludables, medido en un servidor limpio
$0.118por solicitud de extracción revisada, con el modelo con el que se envía
17 de 50en el conjunto offline de Martian Code Review Bench, bajo los tres jueces

Ese último es deliberadamente poco halagador, y se queda. Mide una de las superficies de abajo —revisión de solicitudes de extracción en PRs aislados de un solo repositorio— y ese conjunto no tiene un servicio hermano para que un símbolo tenga consumidores, así que lo que este producto construye no está en el número en absoluto. La tabla, la auditoría de cada hallazgo que puntuó como falso, y el comando que reproduce ambos están en Resultados.

Tabla de contenidos

Inicio rápido

Lo que necesitas

Docker24+ con Compose v2Docker Desktop en macOS/Windows, el motor nativo en Linux
Una clave de API de modelouno deGoogle Gemini, Anthropic, OpenAI, OpenRouter, Groq o Mistral. Una clave gratuita de Gemini es suficiente para evaluar: https://aistudio.google.com/app/apikey
RAM~4 GB libresMedido en una ejecución de indexación real: 1.1 GB pico en los cinco contenedores, 565 MB en reposo

Postgres y Qdrant están incluidos —sin clúster externo que aprovisionar. No se necesita instalar Python ni Node.js para el flujo con Docker.

Inícialo

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

Abre http://localhost.

Aquí no se construye nada. Las tres imágenes se extraen del registro nombrado por CELMIS_REGISTRY en la etiqueta de CELMIS_TAG, para linux/amd64 y linux/arm64 —Apple Silicon y un servidor ARM obtienen ambos una imagen nativa. Construirlas en la máquina que las ejecuta se midió en 485 segundos y 4.2 GB de disco solo para api, que es por lo que instalar ya no significa compilar.

Puerto 80, no 3000: un proxy inverso pone la aplicación y su API en un solo origen y sirve la API bajo /backend. Eso no es una preferencia de despliegue —el paquete del navegador pide una ruta relativa, que es la única forma en que una imagen publicada puede servir cada instalación en lugar de solo aquella en la que se construyó.

Para trabajar EN Celmis en lugar de ejecutarlo, añade la superposición de desarrollo y recuperas las compilaciones locales:

docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --build

init-env.sh --check informa de lo que aún está vacío sin escribir nada.

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

Eso es una representación de la sesión capturada, no una grabación de pantalla —las cifras son las que la ejecución produjo el 26 de agosto de 2026, y la salida de compose es verbatim de logs/03-up.log en el informe de instalación. Está dibujada en lugar de fotografiada porque no se puede levantar una segunda pila junto a una en ejecución: docker-compose.yml fija container_name, así que los nombres chocan.

Detener

docker compose down       # stop, keep your data
docker compose down -v    # stop and DELETE every volume

Primer usuario y administrador

El formulario de registro en /login funciona en cuanto la pila está saludable. Esa cuenta es un usuario normal —registrarse no concede derechos de administrador, ni siquiera a la primera persona que entre.

El administrador global viene del entorno en su lugar: inicia sesión con CELMIS_MASTER_EMAIL y CELMIS_MASTER_KEY (como contraseña), ambos en .env. Quien ejecuta la caja es el administrador, que es el modelo que una instalación autoalojada quiere en lugar de quien llegó primero al formulario. La ruta no existe a menos que ambas variables estén configuradas, y cada uso de ella se escribe en el registro de auditoría.

Para promover una cuenta normal:

docker compose exec api analyzer auth make-admin you@example.com

Conecta un repositorio

  1. Configuración → Configuración de LLM — pega una clave de proveedor. Se cifra con CREDENTIAL_MASTER_KEY antes de tocar la base de datos, y la interfaz solo te vuelve a mostrar el primer y el último cuatro caracteres.
  2. Conexiones — añade un token de GitHub, GitLab o Bitbucket. Usa una cuenta de máquina, no la tuya: un token personal llega a cada repositorio que puedes ver, y los tokens terminan en copias de seguridad, registros y capturas de pantalla.
  3. Repositorios → Añadir — elige repositorios del proveedor, o pega una URL de clonación. La indexación se pone en cola; el trabajo se muestra en la misma página.

La indexación construye dos cosas desde el mismo checkout: un grafo de símbolos (definiciones, llamadas, importaciones —sobre lo que razonan los agentes de revisión) y embeddings en Qdrant (lo que recupera Q&A). Un repositorio de 120k símbolos tarda aproximadamente un minuto en cuatro núcleos.

Veintitrés lenguajes se analizan en el grafo. Un archivo en un lenguaje sin analizador se dice en voz alta en lugar de omitirse silenciosamente —analyzer graph-stats lista lo que se leyó y lo que no.


Pregunta al código

Una pregunta en un chat, respondida con citas archivo:línea de tantos repositorios como le señales. Las respuestas se transmiten mientras se escriben.

Agrupa repositorios en un proyecto, y la pregunta se hace al grupo:

A project holding several repositories

Las respuestas citan código real, y solo el código que el que pregunta tiene permitido ver —que es lo que hace seguro entregar la pregunta a alguien fuera del equipo que posee el repositorio. Ver Quién puede ver qué.

Revisión de solicitudes de extracción

Los agentes leen el diff y publican hallazgos en GitHub, GitLab o Bitbucket. En lugar de mostrar eso en una captura de esta interfaz, las revisiones se dejan donde se publicaron —cincuenta solicitudes de extracción en proyectos reales, con los comentarios aún adjuntos a las líneas sobre las que se escribieron. Están listadas bajo Repositorios de prueba, y la salida allí no está editada, incluidos los hallazgos que la auditoría de abajo marca como incorrectos.

Donde el grafo está construido, la revisión también lleva lo que el diff no muestra: quién más llama al símbolo que se está cambiando, incluso desde otro repositorio. Donde no está construido, la revisión aún se ejecuta —solo responde la pregunta más estrecha, que es lo que midió el benchmark.

Cada hallazgo que el benchmark puntuó como falso se abrió en la fuente y se publicó con un veredicto. Treinta y tres de setenta y nueve resultaron ser defectos reales que el conjunto dorado no contiene. Ese trabajo está en Auditoría de los falsos positivos, con el código y un enlace permanente para cada uno, para que puedas discrepar de cualquiera de ellos.

Dependencias, SBOM y el paquete de evidencia

La auditoría de dependencias es determinista: auditores nativos donde la herramienta está instalada, OSV en cualquier otro caso, sin modelo involucrado. Un modelo de lenguaje, si le das una clave, escribe el resumen —no decide qué es vulnerable.

Compliance artefacts: SBOM, evidence pack, technical documentation

Dos archivos salen de cada auditoría, y ninguno necesita una clave de LLM:

  • SBOM — un inventario CycloneDX de cada dependencia, su versión, URL del paquete y las vulnerabilidades conocidas contra ella. Este es el archivo que la gente quiere decir cuando dice "envíanos tu SBOM".
  • Paquete de evidencia — la auditoría como presentación: cada SBOM, cada hallazgo, la cronología de ejecuciones anteriores y un sha256 de cada archivo, para que un tercero pueda verificar que nada fue editado después sin tener que confiar en nosotros. Una carpeta cuyo contenido puede cambiarse más tarde no prueba nada; el manifiesto es lo que la convierte en evidencia.

Junto a ellos, la documentación técnica generada — PRDs de módulos, documentos de características y guías de integración escritas desde el código — que es tuya para conservar y sigue funcionando después de que termine cualquier suscripción.

Por qué existe esto ahora. A partir del 11 de septiembre de 2026, la Ley de Resiliencia Cibernética de la UE requiere que un fabricante informe una vulnerabilidad explotada activamente a ENISA dentro de las 24 horas. El mandato formal de SBOM llega en diciembre de 2027, pero no puedes responder la pregunta de las 24 horas sin visibilidad a nivel de componentes primero — para informar qué está afectado, tienes que saber qué hay dentro.

Celmis no reclama cumplimiento, y no lo hará. Produce los artefactos que una presentación necesita. Si una presentación es adecuada es un juicio de abogados, y una herramienta que implique lo contrario está vendiendo una falsa sensación de seguridad.

Una cosa más que la página de auditoría dice en voz alta, porque es el fallo que nadie busca: un ecosistema que nadie escaneó reporta cero vulnerabilidades exactamente igual que uno limpio. La cobertura se muestra junto a los hallazgos — qué auditor produjo cada resultado y, más útilmente, qué quedó sin verificar y por qué.

Corregir con Claude

Encontrar algo es la mitad de un bucle. Una sesión integrada de Claude Code se ejecuta dentro de la instalación, edita el checkout, y el ejecutor hace commit, empuja una rama y abre una solicitud de extracción.

Se ejecuta con tu suscripción, en tu máquina

No hay clave que comprarnos ni modelo incluido. Conectas tu propia suscripción de Claude, como lo hace Cursor: ejecuta claude setup-token una vez en tu propio portátil y pega el token en Configuración. Se almacena cifrado en el almacén de credenciales, y la sesión que se ejecuta después lo usa.

Dos ranuras, resueltas en ese orden:

Personaltu propia suscripción, invisible para nadie más
Espacio de trabajouna suscripción que un administrador comparte con el espacio de trabajo — aceptación explícita

La ranura del espacio de trabajo está desactivada a menos que alguien la active, y la interfaz dice por qué antes de guardar: compartir la suscripción de una persona entre varias personas puede violar los términos de consumo de Anthropic. Esa es una decisión para quien tenga la suscripción, y el producto no la tomará en silencio.

Y la sesión no se ejecuta en la nube de alguien. Se ejecuta en tu propia instalación, en un espacio de trabajo aislado por sesión. Lo que eso te compra no es "acceso a la nube" — es que la máquina que contiene tu código es una que controlas, y la alcanzas desde un portátil en una oficina o un teléfono en un tren por la misma razón que alcanzas cualquier servicio que ejecutas: porque es tuyo y está activo.

Eso es todo de Alertas, y corregir desde un teléfono — llega una alerta, y la corrección comienza desde dondequiera que la leíste.

Una vulnerabilidad en la auditoría de dependencias lleva un botón Corregir con Claude. No abre un chat vacío — entrega a la sesión el repositorio, el paquete, ambas versiones y los límites del trabajo, ya escritos:

The session, pre-filled from a dependency finding

Aquí hay uno de esos bucles, de principio a fin, sobre un hallazgo real — lodash 4.17.11 con una vulnerabilidad conocida contra él. 220 segundos desde Iniciar sesión hasta una solicitud de extracción abierta, en 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."

La rama que empujó y la solicitud de extracción que abrió, en GitHub:

The pull request the agent opened, one line changed

Mira lo que no está en ese diff. axios 0.21.1, minimist 1.2.0, node-fetch 2.6.0 se encuentran en las líneas directamente arriba y abajo — todas desactualizadas, todas señaladas en la misma auditoría — y todas intactas. La tarea decía solo manifiestos, y un agente que hubiera ordenado tres más de paso habría sido un peor resultado de revisar, no uno mejor.

Es una solicitud de extracción en vivo, no una captura de pantalla: celmis-demo-gateway#6 — rama celmis-agent/b8960e01, un commit, +1/-1.

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

Dos detalles en esa transcripción valen más que el diff. El agente no asumió que no había otros manifiestos — ejecutó un comando en el sandbox para verificarlo. Y la tarea decía "solo manifiestos, no toques dependencias no relacionadas", así que el cambio es exactamente una línea.

Lo que el ejecutor permite y lo que no

Esto lo decide el ejecutor, no el prompt — que es la parte que vale la pena leer antes de concederle algo a un agente:

  • Sin shell propio. Bash, WebFetch, WebSearch y la edición de notebooks están deshabilitadas. Los comandos se ejecutan a través del contenedor sandbox, que es un servicio separado con su propio uid y un sistema de archivos raíz de solo lectura.
  • Git es trabajo del ejecutor. El agente nunca hace commit ni empuja. Cuando el trabajo está hecho — o cuando presionas Finalizar y empujar — el ejecutor hace el commit, empuja la rama y abre el PR. Nunca a la rama predeterminada.
  • Un límite del proveedor es una pausa, no una pérdida. El primer intento de la ejecución anterior alcanzó un límite semanal de cuenta a mitad de sesión. La sesión no murió: se movió a paused, mantuvo su trabajo reanudable durante catorce días y mostró el mensaje del proveedor en lugar de un fallo genérico. Una segunda clave lo terminó.
  • La sesión es observable. La salida se transmite por SSE con reproducción, así que una reconexión retoma donde quedó en lugar de comenzar en blanco.

La conexión es un token de configuración, mantenido por usuario o por espacio de trabajo. La API nunca lo devuelve una vez guardado — solo si está allí y si sigue funcionando.

Alertas, y corregir desde un teléfono

El bucle anterior comienza desde un hallazgo de dependencia. También comienza desde producción.

Apunta cualquier sistema de monitoreo a la URL de ingesta del espacio de trabajo y sus alertas llegan a Celmis:

POST /webhook/alerts/{workspace_id}.{secret}

El webhook de alertas unificadas de Grafana se analiza tal cual; cualquier otra cosa puede publicar {"title", "body", "severity", "repo"}. La mitad secreta se almacena cifrada con Fernet por espacio de trabajo y se compara en tiempo constante. El endpoint está sin autenticación por diseño — los sistemas de monitoreo no pueden hacer OAuth — y limitado al inquilino por construcción: un token solo puede escribir en el espacio de trabajo al que pertenece.

Lo que eso compra es la ruta sin portátil:

  1. Se dispara una alerta. Llega a Celmis y una notificación push web llega a tu teléfono — push web real, VAPID y un service worker, así que llega esté o no la pestaña abierta.
  2. La abres. La alerta lleva su repositorio, porque route_incident puede tomar un stack trace y decir a qué repositorio y a qué propietario pertenece.
  3. Presionas Corregir con Claude. La sesión se abre ya teniendo la alerta — no un chat vacío.
  4. El ejecutor hace commit, empuja una rama y abre la solicitud de extracción.

An alert, ingested and bound to its repository

Ninguno de esos cuatro pasos necesita un checkout, una terminal o una máquina en la que confíes. El trabajo ocurre dentro de tu propia instalación; el teléfono es una pantalla para ello.

La misma página lista cada alerta que el espacio de trabajo ha recibido, así que una alerta que nadie atendió es visible en lugar de perderse en un canal.

Lo que se registra mientras esto ocurre. Dos registros separados, y responden preguntas diferentes:

  • El rastro de auditoría — JSONL de solo añadir, rotado por tamaño, filtrable por tiempo, modo, operación y repositorio, exportable como CSV. Responde quién hizo qué, a qué repositorio, cuándo. La retención predeterminada es de 90 días y el archivo activo nunca se elimina, solo se rotan los archivos.
  • Historial de recursos — muestras y agregados sobre la instalación misma, exportable como CSV para una hoja de dimensionamiento. Responde cuánto costó ejecutar esto.

Una sesión de agente que edita un repositorio a las dos de la mañana desde el teléfono de alguien es exactamente el tipo de evento que tiene que poder reconstruirse después. Lo es.

Pedir trabajo entre repositorios

Cada superficie anterior actúa sobre una cosa a la vez: este repositorio, esta solicitud de extracción, este hallazgo. Esa es la forma correcta para un botón, y la forma incorrecta para un conjunto definido por una condición.

Generar documentación para cada servicio que no tenga ninguna es una oración. A través de la interfaz, es encontrarlos entre cuarenta y presionar un botón cuarenta veces. Auditar todo bajo acme-ai que no se haya auditado en treinta días necesita filtros, selecciones guardadas y operaciones masivas — un subsistema — o necesita una oración.

Así que hay una caja de oraciones y un catálogo deliberadamente corto de verbos detrás de ella. El trabajo de un solo objeto permanece en los botones, donde pertenece.

Nada se ejecuta en la primera pulsación. La interpretación es una suposición, y estos verbos cuestan dinero y horas — una compilación de vault, una flota de agentes de revisión, una auditoría en un grupo. Así que la respuesta a una oración no es el trabajo; es a qué repositorios esto se resuelve, listado, con un segundo botón debajo. Confirmas el conjunto, no la intención.

El alcance se vuelve a verificar en el momento en que confirmas, no cuando preguntaste. Un repositorio registrado en los segundos entre la pregunta y la pulsación no puede unirse silenciosamente a un conjunto que dijo "todo". La misma regla que gobierna el resto del producto — la verificación se ejecuta donde ocurre el trabajo — gobierna esto.

Un llamador automatizado nunca puede poner en cola una expansión ilimitada. Los verbos están limitados, con alcance al espacio de trabajo y toman un actor explícito: nada aquí lee un contexto de solicitud ambiental, porque un conector que procesa una cola no tiene solicitud, y una función que adivina un espacio de trabajo es la forma en que la automatización de un inquilino alcanza los repositorios de otro inquilino.

Tres llamadores convergen en estos mismos verbos — un agente externo sobre MCP, el agente integrado y un conector de tickets que convierte auditar estos cuatro servicios en trabajo y publica el resultado de vuelta. Comparten una implementación a propósito: un segundo "iniciar una auditoría" es un segundo conjunto de reglas sobre ejecuciones en vivo, deduplicación y reinicios forzados, y la copia que nadie mantiene es la que corrompe la cola.

Quién puede ver qué

El acceso se resuelve por repositorio, por equipo, y gobierna cada superficie a la vez — Q&A, gráfico, búsqueda, MCP:

configuraciónefecto
visibility: noneel repositorio no existe para la investigación
visibility: metadatasolo documentación y notas de arquitectura
visibility: codeel código fuente es legible
deny_globsgana incluso en code — credenciales, criptografía, conexiones de base de datos, verificación de secretos
allow_globsuna lista de permitidos cuando se establece; denegar aún resta de ella

Esto es lo que hace que el caso del equipo vecino funcione en lugar de ser una promesa: carga el repositorio, concede al otro equipo el derecho de preguntar y deniega las rutas que no deben leerse. Obtienen respuestas; esos archivos se rechazan en la fuente, no se filtran de una respuesta que ya los contenía.

Idiomas y formatos

Diecisiete módulos de gráfico, más una ruta genérica a través de consultas de etiquetas tree-sitter para idiomas sin uno:

Código — Python, TypeScript, JavaScript, Go, Java, C#, C++, PHP, Vue y más a través de la ruta genérica.

Infraestructura — Dockerfile, docker-compose, Helm, manifiestos de Kubernetes, Terraform y flujos de trabajo de CI. Esta es la parte que la mayoría de las herramientas de inteligencia de código omiten, y es por qué una pregunta puede cruzar de una función a la definición de servicio que la ejecuta.

Verificaciones deterministas — sin modelo, sin falsos positivos

Cada verificación a continuación se decide leyendo archivos. Ningún modelo de lenguaje participa en decidir que algo está mal, así que la tasa de falsos positivos es cero por construcción en lugar de por ajuste.

Esa distinción es el punto. Alrededor del veinte por ciento de falsos positivos es donde los desarrolladores dejan de leer los comentarios de una herramienta por completo — uno cuesta segundos de atención, mil te cuestan un equipo que ha aprendido a saltarse todo lo que la herramienta dice. Un modelo se usa aquí para explicar y priorizar, nunca para detectar.

VerificaciónLecturasDetecta
install_scriptHooks del ciclo de vida de package.jsonuna dependencia que ejecuta código en el momento de la instalación
python_build_hookspyproject.toml / setup.pyejecución de código en tiempo de compilación en un paquete de Python
cargo_build_scriptCargo.tomluna crate con un build.rs
non_registrymanifiestos y archivos de bloqueouna dependencia obtenida de una URL de git o un tarball en lugar de un registro
suspect_namela lista de dependenciastyposquats: un nombre a una edición de distancia de un paquete popular
lock_driftmanifiesto vs. archivo de bloqueoun archivo de bloqueo que ya no coincide con lo que declara el manifiesto
cross_repo_driftel diff del PR y luego los repositorios hermanosuna constante cambiada en un repositorio y olvidada en los demás

El escaneo ordinario de CVE está deliberadamente no en esa lista. OSV-Scanner ya lo hace, es gratuito y es el estándar de facto: Celmis lo ejecuta (además del auditor propio de cada ecosistema: pip-audit, npm audit, govulncheck, cargo audit) y trata el resultado como una entrada, no como una característica.

Sobre el cumplimiento. Celmis produce los artefactos que una auditoría solicita: un SBOM CycloneDX, un inventario de dependencias, un historial de hallazgos con marcas de tiempo y la evidencia en la que se basa cada hallazgo. No afirma que su presentación sea adecuada, y ninguna herramienta puede hacerlo honestamente: lo que un auditor acepta depende de su sector, su jurisdicción y sus propios controles. Produzca los artefactos; deje que las personas cuyo trabajo es evaluarlos lo hagan.


Conecte Claude Code y otros clientes MCP

Celmis expone su índice a través de MCP, para que un agente pueda buscar símbolos, leer superficies de API y encontrar consumidores en lugar de hacer grep en un checkout que no tiene.

A través de HTTP (la pila en ejecución lo sirve en /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>" }
    }
  }
}

A través de stdio, sin el salto HTTP:

{
  "mcpServers": {
    "celmis": {
      "command": "docker",
      "args": ["compose", "exec", "-T", "api", "analyzer", "mcp", "serve"]
    }
  }
}

Lo que un agente puede preguntar

El montaje HTTP sirve 18 herramientas. Responden a las preguntas que un grep no puede:

list_workspace_reposqué repositorios existen, indexados, documentados, con auto-revisión activada
search_symbolsdónde se define una función o endpoint, en todo un proyecto
find_consumersqué repositorios llaman a un símbolo, incluidos los que nunca clonó
get_api_surfacelos manejadores HTTP que un servicio realmente expone
get_owner · list_deprecationsquién es dueño de un archivo; qué está en camino de salir y quién aún lo usa
route_incidentdado un stack trace, a qué repositorio y propietario pertenece
bootstrap_client · start_integration_walklo que un cliente necesita para llamar al servicio de otro equipo
get_dep_audit · list_dep_findingsla última auditoría y sus hallazgos, de peor a mejor
get_review · get_review_policyla revisión más reciente de un PR y qué agentes se ejecutan dónde

Los dos transportes no son el mismo conjunto. analyzer mcp serve a través de stdio sirve 13 herramientas más antiguas, con forma de grafo (find_symbol, find_callers, query_graph); el montaje HTTP sirve las 18 anteriores. Ninguno es un subconjunto del otro: elija el transporte según las herramientas que desee.

Una guía paso a paso, con los alcances que necesita cada herramienta y los modos de fallo, está en .claude/skills/celmis-mcp/SKILL.md. Claude Code la toma automáticamente cuando este repositorio está abierto.


Lo que el agente puede pedir

An MCP client querying two repositories in one call

Una llamada a search_symbols, un símbolo de contrato, y regresa desde dos repositorios en dos idiomas, para un cliente que no ha hecho checkout de ninguno. La frontera que un diff nunca cruza es la que esto hace ordinaria.

Dieciocho herramientas, servidas a través de Streamable HTTP en /mcp/ y autenticadas con el mismo token de portador que /api/:

HerramientaResponde
list_reposqué repositorios están indexados y qué tan fresco está cada índice
list_groupsqué repositorios están agrupados, para que las preguntas entre repositorios tengan un alcance
find_symboldónde se define un nombre, en todos los repositorios indexados
get_symbolla definición en sí, con su archivo y rango de líneas
find_callersqué llama a esto: la pregunta que un grep responde mal y un grafo responde con exactitud
find_calleesqué llama esto, un salto hacia afuera
cross_repo_edgesllamadas que cruzan un límite de repositorio
query_graphCypher de solo lectura, para preguntas que las siete anteriores no forman

cross_repo_edges es la que vale la pena entender, porque es la razón por la que este producto lleva un grafo de símbolos. Un revisor solo con diff (cada herramienta en la tabla de referencia anterior, incluida esta cuando el grafo está vacío) puede decirle que una firma de función cambió. No puede decirle que un servicio en un repositorio diferente aún llama a la forma antigua, porque nunca tuvo ese repositorio abierto. Agrupe los repositorios una vez, y esa pregunta se vuelve respondible:

> which services outside this repo call PaymentGateway.charge?

Esta es también la razón por la que nuestra posición en la referencia subestima el producto en lugar de describirlo: el conjunto de referencia son pull requests aislados de un solo repositorio, por lo que no hay un repositorio hermano para que un borde lo cruce. La capacidad es real y la referencia no puede verla, lo cual es una declaración sobre la referencia, no una afirmación que deba tomar por fe. Apunte un cliente MCP a su propio grupo y compruébelo.

Resultados

Celmis se ejecutó en el conjunto fuera de línea de Martian Code Review Bench: 50 pull requests seleccionados, 173 comentarios dorados escritos por humanos, puntuados contra el conjunto dorado por un juez LLM. Medido en e0db376 con gemini-3.6-flash a temperatura 0.1, sin tokens de razonamiento.

JuezF1PrecisiónRecuperaciónRango
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

El F1 se mueve 4.8 puntos dependiendo de quién juzgue. El rango no se mueve en absoluto: decimoséptimo bajo los tres.

Toda la ejecución costó $5.88 — $0.118 por pull request — y produjo 153 hallazgos, 3.06 por PR (defecto 114, seguridad 27, contrato 6, estructural 6).

Por qué esta comparación es justa. Martian envía sus propias evaluaciones de 49 herramientas en el repositorio de referencia, producidas por los mismos tres jueces en los mismos 50 PRs contra los mismos dorados. No volvimos a puntuar a nadie: sus filas se toman como se publicaron y la nuestra se añade. Reproduzca toda la tabla con:

python3 autoloop/offline_table.py anthropic_claude-sonnet-4-5-20250929

Fuera de línea no es la tabla de clasificación pública. Martian ejecuta dos referencias. La tabla de clasificación pública es la en línea: 200,000 pull requests reales puntuados por lo que los desarrolladores realmente corrigieron. Esta tabla es la fuera de línea: 50 PRs seleccionados puntuados contra un conjunto dorado. Miden cosas diferentes y los números no son intercambiables. Afirmaciones de la forma "la herramienta X es #1 en Martian" generalmente se refieren a la tabla en línea, una métrica diferente o un juez diferente.

Lo que este número no contiene. El grafo estaba vacío para los 50 PRs (graph_status nulo, deriva vacía en todos), porque el conjunto de referencia son pull requests aislados de un solo repositorio: no hay un servicio hermano para que un símbolo tenga consumidores. La deriva entre repositorios, la razón por la que este producto lleva un grafo de símbolos, contribuyó exactamente con nada a la puntuación anterior. No es medible aquí, y no lo estamos afirmando desde esta tabla. Vea Repositorios de prueba para verlo funcionar en código real.

Auditoría de los falsos positivos

La puntuación de la referencia tiene un piso estructural: el juez compara nuestro comentario contra una lista finita de dorados escritos por humanos, por lo que un hallazgo correcto que el anotador nunca escribió se cuenta como falso por construcción. Abrimos los 79 nuestros en la fuente en el commit medido y asignamos un veredicto a cada uno.

De 79 hallazgos puntuados como falsos positivos, 33 son defectos reales que el conjunto dorado no contiene, 38 son genuinamente incorrectos y 8 no pudieron resolverse desde el código. Eso coloca la precisión verdadera de esta ejecución entre 69.7% y 75.0% en lugar del 48.0% medido — pero esa cifra corregida no puede compararse con nada en la tabla anterior, porque nadie ha auditado las otras herramientas de la misma manera y sus falsos positivos casi con certeza contienen una proporción similar de defectos reales; para comparación con otras herramientas, el 48.0% medido es el número honesto, porque es el mismo método aplicado a todos.

Veinticuatro de los 38 hallazgos genuinamente incorrectos comparten cuatro causas raíz, y ninguna de ellas es "el modelo es débil": las cuatro se refieren a lo que se le mostró al modelo. La más grande es un identificador declarado en el mismo archivo pero fuera del extracto que recibió el agente: un parámetro de método 26 líneas arriba, una importación en la línea 3, un attr_reader en la línea 18.

El informe completo da la afirmación, el código en ese commit, el veredicto, el razonamiento y un enlace permanente para cada uno de los 79, para que cualquier veredicto pueda disputarse con la misma evidencia frente a usted.

Repositorios de prueba

Cada revisión en la ejecución anterior sigue viva y pública. Estos son pull requests reales de proyectos reales, bifurcados con su historial, que llevan los comentarios en línea que Celmis escribió:

BifurcaciónPRs
celmis-bench/keycloak9
celmis-bench/grafana10
celmis-bench/discourse-graphite10
celmis-bench/cal.diy10
celmis-bench/sentry6
celmis-bench/sentry-greptile4

Vale la pena abrir primero:

  • keycloak#17 — una desreferencia nula y una pregunta de indexación de códigos de recuperación en el proveedor de almacenamiento de prueba de Keycloak
  • grafana#16 — una falla de Storage registrada contra la métrica Legacy, una de tres instancias del mismo error en ese archivo
  • cal.diy#11forEach con un callback asíncrono, por lo que las eliminaciones son de fuego y olvido y el try circundante no captura nada
  • sentry#11 — siete comentarios en línea en un solo PR de consumidor de Kafka

Está leyendo salida sin editar, incluidos los hallazgos que la auditoría anterior marca como incorrectos. Nada se eliminó después de la puntuación.

Configuración

./scripts/init-env.sh escribe .env desde .env.example y genera cada secreto. El ejemplo envía cada secreto vacío a propósito: una versión anterior colocaba el comando generador junto a la variable, los archivos dotenv no tienen comentarios en línea, y cada instalación que lo copió se ejecutó con una contraseña maestra impresa en el repositorio.

La configuración llega a los contenedores solo a través del bloque environment: en docker-compose.yml — la imagen no lleva .env. Una variable no nombrada allí toma su valor predeterminado de código sin importar lo que diga su .env. GET /healthz informa los relojes de revisión tal como el proceso realmente los resolvió, que es cómo verifica lo que llegó.

Los relojes están documentados como un conjunto en .env.example, con el invariante que los une:

REVIEW_LLM_TIMEOUT_SECONDS × (1 + RETRY_FACTOR)  ≤  REVIEW_TIMEOUT_SECONDS

Eleve uno y el otro tiene que seguirlo; una prueba lo hace cumplir.

VariablePredeterminado
REVIEW_TIMEOUT_SECONDS900reloj de pared para una revisión; pasado este, las etapas finales se retiran y el comentario lo dice
REVIEW_LLM_TIMEOUT_SECONDS300una llamada de modelo. Eleve a ~600 para un modelo de razonamiento lento
REVIEW_LLM_TIMEOUT_RETRY_FACTOR2.0cuánto más largo es el reintento después de un tiempo de espera; 1.0 desactiva la ampliación
REVIEW_MAX_DIFF_SIZE_BYTES500000los diffs más grandes se rechazan, no se truncan
REVIEW_VERIFIER_ENABLEDfalseel veto de falsos positivos del LLM
REVIEW_AGENT_CONCURRENCY3llamadas de proveedor en vuelo por revisión
CELMIS_JOB_LEASE_SECONDS600límite de silencio del trabajador antes de que un trabajo pueda reclamarse
CELMIS_DEPLOYMENT_MODEsingle_tenantmulti_tenant aísla los espacios de trabajo entre sí

Operaciones

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 → Monitoreo muestra la profundidad de la cola, el gasto por espacio de trabajo y la configuración de modelo por agente. Uso y costo desglosa el gasto por superficie, para que una compilación de documentación por lotes no se lea como chat. Desplegar en un servidor es ./scripts/deploy-on-server.sh v0.1.0, ejecútalo en el servidor: extrae las imágenes publicadas, levanta la pila detrás de Caddy y sella la compilación a la que enlaza el pie de página de AGPL. Nada fuera de esa máquina necesita una credencial para ello. Consulta docs/ORACLE_CICD.md, o docs/HETZNER.md para una VM simple.


Desarrollo 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

Referencia de CLI

analyzer se instala mediante pip install -e .; dentro de Docker usa docker compose exec api analyzer …. Cada comando acepta --help.

analyzer initcrea la estructura del espacio de trabajo
analyzer index <path|url>analiza un repositorio en el grafo
analyzer ask "<question>"una pregunta, respuesta citada
analyzer chatsesión interactiva
analyzer review <provider> <repo> <pr>revisa una solicitud de extracción; --post publica
analyzer generateconstruye la bóveda de documentación
analyzer refreshreindexa lo que cambió
analyzer graph-stats <repo>qué se analizó, por lenguaje
analyzer servela API sin Docker
analyzer review-serveel receptor de webhook solo

Subcomandos agrupados: analyzer repo, analyzer group, analyzer auth, analyzer mcp, analyzer scip.


Arquitectura

                      ┌──────────────┐
   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 almacena trabajos, políticas, historial de ejecución, gastos y el registro de auditoría. La cola de trabajos duradera es una tabla — la desencolación es SELECT … FOR UPDATE SKIP LOCKED, y un trabajador renueva su concesión mientras trabaja en lugar de adivinar una duración de antemano.
  • Qdrant almacena incrustaciones, una colección por instalación con aislamiento de espacio de trabajo aplicado en el filtro.
  • sandbox ejecuta cualquier cosa no confiable — un conjunto de pruebas, una compilación — como su propio uid en su propia red, sin base de datos, sin claves y con una raíz de solo lectura.
  • LiteLLM es opcional. Establece LITELLM_PROXY_URL y LITELLM_MASTER_KEY juntos y cada llamada se enruta a través de la puerta de enlace; deja cualquiera vacío y las claves del proveedor se usan directamente.

Solución de problemas

Un contenedor no se inicia. docker compose logs <service>. La API dice al inicio qué características opcionales no están disponibles y por qué, en lugar de fallar silenciosamente.

Las revisiones no producen nada. Verifica GET /healthz para los relojes resueltos, luego docker compose logs api | grep agent_. Cada agente registra su tiempo transcurrido, su modelo y su código de fallo.

Un tiempo de espera, no una interrupción. local_timeout significa que el plazo de esta instalación transcurrió antes de que el proveedor respondiera — aumenta REVIEW_LLM_TIMEOUT_SECONDS. Deliberadamente no se informa como una falla del proveedor.

Preguntas y respuestas no cita nada. El repositorio probablemente no está indexado, o está indexado sin incrustaciones. Repositorios muestra el estado de cada uno; analyzer graph-stats <repo> muestra qué se analizó.

El sandbox está siempre ocupado. SANDBOX_SLOTS es cuántos trabajos se ejecutan a la vez y es el control que cuesta memoria. SANDBOX_SLOT_WAIT es cuánto tiempo un llamador espera en la cola antes de que se le diga que vuelva.


Estructura del proyecto

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

Procedencia y derechos

Este repositorio tiene un único commit raíz de aproximadamente cien mil líneas — la forma que un volcado de código de origen poco claro tiene para un escáner de procedencia, y una que necesita una explicación en lugar de un encogimiento de hombros. Tiene una: PROVENANCE.md declara la posición de licencia y el origen de el código — el desarrollo ocurrió en privado antes de este commit, y nada de ello es necesario para compilar, auditar o bifurcar lo que está aquí.

Ese archivo es un registro de hechos, no la licencia. La licencia es AGPL-3.0, con una excepción: cualquier cosa bajo ee/, y cualquier archivo cuyo nombre contenga .ee., está cubierto por LICENSE_EE en su lugar. LICENSING.md declara el límite en su totalidad — LICENSE en sí mismo es el texto AGPL sin modificar, porque un archivo de licencia con un preámbulo delante no se reconoce como esa licencia. ee/ no contiene código de producto hoy — el límite se trazó antes de la primera etiqueta porque agregarlo después significa volver a preguntar a cada contribuyente que ya ha enviado trabajo bajo un AGPL sin calificar.

Todo lo enviado aquí es AGPL, incluidas las partes que parecen comerciales: la consola de auditoría, uso y gastos, verificaciones de cumplimiento, métricas de instalación. Los controles de seguridad nunca son solo para empresas — el registro de auditoría se escribe bajo AGPL y siempre lo será. Consulta CONTRIBUTING.md para saber dónde va el código nuevo.