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

Autoalojado: desde la alerta que tu monitoreo ya envió, hasta un pull request que alguien pueda revisar

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

Llega una alerta de un servicio que ejecutas. Celmis ya sabe qué repositorio es ese servicio y quién lo posee, así que la corrección comienza ahí: una sesión integrada de Claude Code — con tu suscripción, dentro de tu instalación — edita el código, y el runner hace commit, sube una rama y abre un pull request. En un hallazgo medido que tomó 220 segundos, desde cualquier dispositivo al que llegó la alerta primero.

Celmis no vigila tus servicios. Grafana, o lo que ya ejecutes, hace eso y lo hace bien. Esta es la parte que viene después de la alerta: el camino desde una regla disparada hasta un diff que alguien pueda revisar.

Para ser exactos, porque la línea es fácil de difuminar. La aplicación no ingiere registros propios — comienza desde la alerta. Pero el repositorio incluye una capa de observabilidad opcional, y esa sí los recopila:

docker compose -f docker-compose.yml -f docker-compose.observability.yml up -d

Eso levanta Prometheus, Loki, Promtail y Grafana. docker-compose.observability.yml es deliberadamente a nivel de plataforma, no solo de Celmis: Promtail sigue los registros de contenedores del daemon de Docker y etiqueta cada flujo por nombre de contenedor, de modo que todos los servicios en ese host — los treinta, si ejecutas treinta — aparecen en el explorador Loki de Grafana, filtrables por contenedor. Prometheus extrae api:8000/metrics para los contadores propios de Celmis junto a eso.

Cada puerto se vincula a 127.0.0.1 y se accede mediante un túnel SSH:

ssh -L 3100:localhost:3100 -L 9090:localhost:9090 you@your-server

Eso no es precaución por precaución. Loki no tiene autenticación, así que POST /loki/api/v1/push acepta escrituras de cualquiera que pueda alcanzarlo, y un almacén de registros al que extraños puedan escribir no es un almacén de registros. GRAFANA_ADMIN_PASSWORD es obligatorio — la pila no arranca sin uno.

Ese bucle es el producto. Se cierra porque todo se apoya en un único índice: un grafo de símbolos que Celmis construye una vez desde tus repositorios. El mismo índice responde preguntas que cruzan límites de repositorios, revisa pull requests, audita dependencias en un SBOM CycloneDX y un paquete de evidencia verificable, genera documentación y sirve cincuenta y una herramientas vía MCP — superficies que valen la pena, y ninguna de ellas el punto por sí sola.

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 la reducción aquí; las superficies son lo que trabaja el resultado.

Lo que eso 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. El gateway y el servicio de pagos son repositorios separados sin código compartido, y la respuesta rastrea 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. El código responde en su lugar, con citas de archivo:línea → Pregunta al código
Dos equipos comparten una integración y ninguno puede leer el repositorio del otroCárgalo, otorga 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 ellos mismos recalculan — contra un hash que publicas donde el paquete no está → Dependencias, SBOM y el paquete de evidencia
Una vulnerabilidad llega a una dependenciaCorregir desde aquí entrega a una sesión integrada el repositorio, el paquete y el hallazgo. Edita, el runner sube una rama y abre un PR → Corregir desde aquí
Un pull request 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 pull requests
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 escritorioAterriza en Celmis y sale al canal de chat del espacio de trabajo, y Corregir desde aquí abre una sesión que ya tiene la alerta. El runner abre el pull request → Alertas y corrección desde un teléfono
Tu propio agente o editor necesita entender el códigoApúntalo a /mcp/. Cincuenta y una herramientas sobre el mismo índice, treinta y cinco de ellas de solo lectura, bajo las mismas reglas de acceso — sin segunda copia de tu código en ningún lugar → 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 pull request revisado, 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 pull requests 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 modelouna 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 real de indexación: 1.1 GB de 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 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

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 — tanto Apple Silicon como un servidor ARM obtienen 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 a cada instalación en lugar de solo a aquella en la que se construyó.

Para trabajar EN Celmis en lugar de ejecutarlo, añade la capa 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 otorga 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

Una cuenta promovida (o una que un OIDC_ADMIN_ROLE haga administradora) es un administrador global: estado del sistema, operaciones, cumplimiento, todos los espacios de trabajo visibles. No es el superadministrador — ese es solo la cuenta maestra, y es la única que puede otorgar los roles de abajo que llevan poder.

Roles de espacio de trabajo

Cada espacio de trabajo es un inquilino, y una persona puede tener un rol diferente en cada uno — administrador de uno, editor de otros dos.

RolPuede hacer en ese espacio de trabajo
viewerleer
memberleer; cambiar el estado de un problema de revisión
editorel editor de prompts: prompts de sistema de agentes, políticas de revisión por repositorio (plantilla de prompt, reglas de carpetas, anulaciones de prompt por agente), analíticas. Sin miembros, invitaciones, equipos, claves de LLM, conexiones de git ni licencia
adminderechos de editor, más miembros y espectadores, invitaciones, equipos, claves de LLM, conexiones de git
ownerderechos de administrador, más eliminar el espacio de trabajo

Quién puede otorgar qué:

  • owner, admin, editor — solo el superadmin: otorgar, cambiar a o desde, eliminar. Un administrador de workspace no puede degradar al owner ni a otro admin.
  • member, viewer — el superadmin, o el owner/admin de ese workspace (directamente o por invitación; una invitación solo conlleva un rol que su creador podía otorgar, verificado de nuevo al aceptarla). Una cuenta existente se añade directamente solo si ya compartes un workspace con ella; cualquier otra persona recibe una invitación que debe aceptar.
  • Enlaces de restablecimiento de contraseña que emite un administrador de workspace están limitados por todos los workspaces a los que pertenece la persona, no solo este. En modo multi-tenant, todos poseen un workspace personal, así que en la práctica solo el superadmin puede emitir uno. Ninguna ruta emite uno para la cuenta maestra.
  • Los workspaces compartidos los crea el superadmin. Todos reciben un workspace personal propio al registrarse y son su owner.

El superadmin gestiona a las personas entre workspaces en Administración → Usuarios (/admin/users); cada cambio de membresía, desde cualquier página, se escribe en el registro de auditoría con el actor, la persona, el workspace y el rol anterior y nuevo. Su filtro Sin acceso de equipo lista las cuentas cuyo único workspace es su personal — sin importar cómo inicien sesión — de más reciente a más antiguo.

Cómo entrar: invitaciones y solicitudes de acceso

Una invitación para alguien sin cuenta. Invitar una dirección de correo que no tiene cuenta crea un enlace de un solo uso vinculado al correo. El invitador lo ve (cópialo desde la página de Equipo); cuando SMTP está configurado, también se envía por correo. Abierto sin sesión iniciada, /invite/<token> muestra el workspace, el rol y quién lo envió, y ofrece iniciar sesión, registrarse (cuando AUTH_PASSWORD_LOGIN lo permite), Google y SSO de empresa (cuando están habilitados); cada uno regresa al enlace para aceptar. Aceptar vuelve a verificar que el invitador aún puede otorgar ese rol, se audita y consume el enlace. Un enlace caducado, revocado o usado indica cuál es el caso; un enlace vinculado a un correo abierto por otra cuenta es rechazado.

Canje automático — solo direcciones verificadas. Cuando alguien inicia sesión con Google o SSO de empresa y el proveedor de identidad marca el correo como verificado (email_verified), las invitaciones pendientes dirigidas a ese correo se canjean al iniciar sesión, a través de la misma ruta de aceptación (regla de concesión verificada de nuevo, auditada, de un solo uso). Una cuenta de contraseña nunca se canjea por su dirección: Celmis no verifica la dirección escrita al registrarse, así que cualquiera puede registrar el correo de otra persona. Una cuenta de contraseña abre el enlace que se le envió y acepta allí — y también lo hace una cuenta que tiene contraseña y Google/SSO vinculado, ya que quien estableció esa contraseña aún puede iniciar sesión. La ruta automática solo añade a alguien: una invitación para un workspace en el que la persona ya está se consume sin tocar su rol, y eliminar a un miembro revoca las invitaciones activas dirigidas a esa persona para ese workspace.

Solicitudes de acceso. Una persona con sesión iniciada cuyo único workspace es su personal ve "Aún no tienes acceso a workspaces de equipo — envía una solicitud" en el panel y en /access-request, con un comentario opcional (hasta 1000 caracteres). Una solicitud pendiente a la vez; se puede cancelar y enviar una nueva después de un rechazo. La solicitud no nombra ningún workspace, y al solicitante no se le muestra ninguno hasta que se apruebe. Solo el superadmin decide, en Administración → Solicitudes de acceso (/admin/access-requests): aprobar con uno o más pares (workspace, rol) — aplicados todo o nada, cada membresía auditada con via="access_request" — o rechazar con una razón que el solicitante lee. El acceso otorgado por otra vía (la página de Usuarios, una invitación aceptada) cierra una solicitud pendiente como aprobada. Las solicitudes decididas permanecen listadas como historial. El solicitante ve la decisión en /access-request y como una notificación en su próxima visita, y un workspace aprobado aparece en el selector sin cerrar sesión. Con SMTP configurado, el solicitante también recibe la decisión por correo, y la dirección maestra una nueva solicitud; no hay otro canal de notificación.

POST /api/access-requests {comment?}solicitar (409 si hay una pendiente, o la cuenta ya tiene acceso de equipo)
GET /api/access-requests/meelegibilidad y la solicitud más reciente
DELETE /api/access-requests/mecancelar la pendiente
GET /api/admin/access-requests?status=superadmin: listar, pendientes primero
POST /api/admin/access-requests/{id}/approve {grants:[{workspace_id, role}]}superadmin: uno o más, únicos, existentes, no personales
POST /api/admin/access-requests/{id}/reject {reason}superadmin: razón obligatoria

Decidir una solicitud que ya no está pendiente responde 409.


Conectar 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 los últimos 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.


Preguntar al código

Una pregunta en un chat, respondida con citas archivo:línea de tantos repositorios como le indiques. 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 solicitante 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 pull requests

Los agentes leen el diff y publican hallazgos en GitHub, GitLab o Bitbucket. En lugar de mostrar eso en una captura de pantalla de esta interfaz, las revisiones se dejan donde se publicaron — cincuenta pull requests 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 el resultado allí no está editado, incluidos los hallazgos que la auditoría a continuación marca como incorrectos.

Donde se construye el grafo, 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 se construye, la revisión aún se ejecuta — solo responde la pregunta más limitada, que es lo que midió el benchmark.

Hablar con el revisor, y lo que aprende

Más allá de la primera revisión, el revisor trabaja dentro del pull request:

  • Comandos de comentario. @celmis help, start-review, review --force, remember: <rule>, y una pregunta de texto libre respondida en el mismo hilo (Comandos de PR).
  • Revisión incremental y cadencia. Un nuevo push se revisa desde el último commit revisado; un repositorio puede pausar revisiones automáticas después de una ráfaga de pushes, omitir títulos por palabra clave y escribir el resumen en la descripción (Configuración de revisión).
  • Memorias de equipo y aprendizaje. Las reglas que el equipo establece y los hallazgos que descarta dan forma a revisiones posteriores, con un modo sombra para juzgar el efecto primero (Memorias y aprendizaje).
  • Backlog de problemas. Los hallazgos se siguen a través de los pushes y después del merge, y se resuelven solos cuando el código muestra que el defecto ha desaparecido (Backlog de problemas).
  • Contexto de tareas de Jira. El agente de lógica de negocio verifica el cambio contra la tarea que nombra un pull request, con una lista de verificación de criterios de aceptación (Jira).
  • Métricas de productividad. Tiempo de ciclo y las cuatro medidas de DORA de tu historial de pull requests, para owners y admins (Productividad).
  • Quién puede ver qué. Roles y acceso. Cada una de estas funciones lee un repositorio solo para personas que pueden leerlo, por la misma regla que MCP (Acceso MCP); las respuestas de chat y la verificación de Jira nunca citan código que el solicitante no pueda abrir.
  • Un perfil MCP compacto para asistentes de codificación. /mcp/dev sirve nueve herramientas de solo lectura dimensionadas para un editor, con howto para "cómo hace autenticación este servicio" y un plugin de Claude Code (El perfil de desarrollador, cómo).

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 con 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 lugar, 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 de 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 expediente: cada SBOM, cada hallazgo, la línea de tiempo de ejecuciones pasadas y un sha256 de cada archivo. Esos hashes prueban que el archivo es internamente consistente; no prueban por sí solos que es el archivo que salió de aquí, porque el manifiesto no registra un hash para sí mismo y cualquiera que edite un archivo puede reescribir su entrada. La exportación devuelve el sha256 del propio manifiesto en X-Celmis-Manifest-SHA256 — publica eso donde el paquete no esté, y un tercero puede entonces verificarlo sin tener que confiar en nosotros, contra un valor que no le entregaste con el archivo. Una carpeta cuyo contenido se puede cambiar después no prueba nada; el manifiesto es lo que lo convierte en evidencia.

    Esa última frase solía ser una afirmación sobre la que nadie podía actuar: verificar un paquete significaba clonar este repositorio e instalar cuarenta dependencias bajo Python 3.13 — toda la cadena de herramientas del productor, para verificar el resultado del productor. La verificación ahora es su propio paquete, sin dependencias y sin llamadas de red:

    pip install celmis
    celmis verify evidence-pack.zip
    

    Cualquier Python desde 3.9, sin conexión, en una máquina que nunca ha ejecutado Celmis. Recalcula cada sha256 que el manifiesto lista e informa listado-pero-ausente, no coincidente y presente-pero-no-listado; salida 0 limpia, 1 problemas encontrados. Este es solo el verificador — la plataforma en sí son seis servicios bajo docker compose y no se instala con pip, y el paquete lo dice cuando se ejecuta sin argumentos.

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

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

Celmis no afirma cumplimiento, y no lo hará. Produce los artefactos que un expediente necesita. Si un expediente es adecuado es un juicio de abogados, y una herramienta que implica 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 útil aún, qué quedó sin revisar y por qué.

Corregir desde aquí

Encontrar algo es la mitad del ciclo. Una sesión de Claude Code integrada se ejecuta dentro de la instalación, edita el checkout, y el runner hace commit, empuja una rama y abre un pull request.

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, visible 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 explica el motivo antes de guardar: compartir la suscripción de una persona entre varias 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 llegas a ella desde un portátil en una oficina o un teléfono en un tren por la misma razón por la que llegas a 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 hayas leído.

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

The session, pre-filled from a dependency finding

Aquí hay uno de esos ciclos, de principio a fin, sobre un hallazgo real: lodash 4.17.11 con una vulnerabilidad conocida en su contra. 220 segundos desde Iniciar sesión hasta un pull request abierto, 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 el pull request 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 marcadas 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 un pull request 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 comprobarlo. Y la tarea decía "solo manifiestos, no toques dependencias no relacionadas", así que el cambio es exactamente una línea.

Lo que el runner permite y lo que no

Esto lo decide el runner, 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 deshabilitados. 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 runner. El agente nunca hace commit ni empuja. Cuando el trabajo está hecho, o cuando presionas Finalizar y empujar, el runner 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 propio 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 se quedó en lugar de empezar 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 corrección desde un teléfono

El ciclo anterior comienza desde un hallazgo de dependencias. 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 tenant 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 sale directamente al canal que el espacio de trabajo haya vinculado — Slack, Discord, Google Chat o un webhook simple — para encontrarte donde ya estás en lugar de en una página que tengas que recordar abrir.
  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 desde aquí. La sesión se abre ya con la alerta, no un chat vacío.
  4. El runner hace commit, empuja una rama y abre el pull request.

An alert, ingested and bound to its repository

Ninguno de esos cuatro pasos necesita un checkout, una terminal o una máquina de confianza. 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 queda registrado 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 archivados.
  • Historial de recursos — muestras y agregados sobre la propia instalación, 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 debe poder reconstruirse después. Lo es.

Solicitar trabajo entre repositorios

Cada superficie anterior actúa sobre una cosa a la vez: este repositorio, este pull request, 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 frase. 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 frase.

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

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 bóveda, una flota de agentes de revisión, una auditoría en un grupo. Así que la respuesta a una frase no es el trabajo; es a qué repositorios se resuelve esto, listados, con un segundo botón debajo. Confirmas el conjunto, no la intención.

El alcance se vuelve a verificar en el momento de confirmar, 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 ocurre 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 tenant llega a los repositorios de otro tenant.

Tres llamadores convergen en estos mismos verbos: un agente externo a través de 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 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 de código, documentación generada, 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 CI. Esta es la parte que la mayoría de las herramientas de inteligencia de código omiten, y es por lo que 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, no por ajuste.

Esa distinción es el punto completo. 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. Aquí se usa un modelo para explicar y priorizar, nunca para detectar.

VerificaciónLeeDetecta
install_scriptpackage.json hooks de ciclo de vidauna 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 extraída de una URL de git o 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, luego repositorios hermanosuna constante cambiada en un repositorio y dejada atrás 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 (más el auditor de cada ecosistema: pip-audit, npm audit, govulncheck, cargo audit) y trata el resultado como una entrada en lugar de 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.

Sobre HTTP (la pila en ejecución lo sirve en /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

El token nunca llega a un repositorio que su persona no pueda leer, y cada llamada es auditada. Un repositorio para el que nadie tiene una regla está cerrado para los miembros. Cómo funcionan el acceso, los tokens y la auditoría está en Acceso 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>" }
    }
  }
}

Un proyecto, para un cliente externo. En la página de un proyecto, el superadministrador puede crear un token de proyecto (cmcp_..., de una hora a 90 días, mostrado una vez, revocable). Alcanza solo los repositorios de ese proyecto, limitado por el alcance de archivos del proyecto, a través de dos herramientas, search_project y ask_project. Los detalles y el comando claude mcp add están en Archivos y tokens de proyecto.

Sobre stdio, sin el salto HTTP:

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

Qué puede preguntar un agente

El montaje HTTP sirve 51 herramientas, y un cliente no necesariamente ve todas. Treinta y cinco son de solo lectura; las dieciséis que escriben — 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 — requieren un alcance de escritura (write:repos, write:config, write:reviews) que un token predeterminado no lleva, por lo que están ausentes de la lista de herramientas de un cliente de solo lectura y se rechazan si se llaman por nombre. Esto es lo que responden las de solo lectura, y son las preguntas que un grep no puede responder:

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_walkqué necesita un cliente para llamar al servicio de otro equipo
get_dep_audit · list_dep_findingsla última auditoría y sus hallazgos, los peores primero
get_review · get_review_policyla revisión más reciente de un PR, y qué agentes se ejecutan dónde
list_reviews · get_review_runlas ejecuciones de revisión más recientes y los hallazgos de una ejecución (por ID de ejecución, o repo + número de PR)
list_issuesproblemas de revisión rastreados, los peores primero, con conteos por estado
ask_code · search_codeuna respuesta escrita sobre el código (una llamada de modelo, registrada como Q&A); búsquedas de símbolo, usos, propietario y arquitectura

El montaje HTTP también escribe con review_pr (poner en cola una revisión de un PR o de todos los abiertos), index_repo y update_issue (establecer el estado de un problema), todo detrás de write:repos. | get_review_settings | la configuración de revisión vigente y de dónde proviene cada valor | | get_spend · get_usage · get_budget | gasto de LLM por modelo, superficie y día; uso de revisión; el límite mensual | | list_alerts · list_jobs | alertas entrantes; trabajos en segundo plano con conteos por estado | | audit_delta · export_sbom | qué cambió desde la auditoría anterior; la URL de descarga del SBOM | | list_members | miembros del espacio de trabajo con roles y equipos | | howto | cómo un repositorio hace una cosa (db, auth, config, http_client, logging, messaging, cache): fragmentos de código, los nombres de entorno y configuración que lee y de dónde provienen sus valores, nunca los valores; necesita read:graph en /mcp/, read:code en /mcp/dev/ |

Los dos transportes no son el mismo conjunto. analyzer mcp serve sobre stdio sirve 13 herramientas más antiguas con forma de grafo (find_symbol, find_callers, query_graph); el montaje HTTP sirve las 35 de solo lectura (las dos herramientas de token de proyecto, search_project y ask_project, entre ellas). 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 recoge automáticamente cuando este repositorio está abierto.


Qué puede pedir el agente

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. El límite que un diff nunca cruza es el que esto hace ordinario.

La tabla siguiente es el conjunto con forma de grafo del servidor stdio (analyzer mcp serve, 13 herramientas). El montaje HTTP en /mcp/ sirve 51 herramientas en total (35 de lectura, 16 de escritura) sobre Streamable HTTP, autenticado como /api/, y un token solo ve las que sus alcances permiten. Las búsquedas de grafo:

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 cada repositorio indexado
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 nuestro puesto 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 juzga. El rango no se mueve en absoluto: decimoséptimo bajo los tres.

La ejecución completa 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 publica sus propias evaluaciones de 49 herramientas en el repositorio de referencia, producidas por los mismos tres jueces en los mismos 50 PR contra los mismos dorados. No volvimos a puntuar a nadie: sus filas se toman como se publicaron y la nuestra se añade. Reproduzca la tabla completa 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 PR 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 PR (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 afirmamos desde esta tabla. Vea Repositorios de prueba para verlo funcionar en código real en su lugar.

Auditoría de los falsos positivos

La puntuación de 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 tratan sobre 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ódigo 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#11 — forEach 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 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 incluye cada secreto vacío a propósito: una versión anterior ponía el comando generador junto a la variable, los archivos dotenv no tienen comentarios en línea, y cada instalación que lo copiaba se ejecutaba con una contraseña maestra impresa en el repositorio.

Los ajustes llegan a los contenedores solo a través del bloque environment: en docker-compose.yml — la imagen no lleva .env. Una variable que no se nombre allí toma su valor predeterminado del código sin importar lo que diga tu .env. GET /healthz informa los relojes de revisión tal como el proceso los resolvió realmente, que es como compruebas 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

Sube uno y el otro tiene que seguirlo; un test lo hace cumplir.

VariablePredeterminado
REVIEW_TIMEOUT_SECONDS900reloj de pared para una revisión; pasado este, las etapas finales se detienen y el comentario lo dice
REVIEW_LLM_TIMEOUT_SECONDS300una llamada de modelo. Súbelo a ~600 para un modelo de razonamiento lento
REVIEW_LLM_TIMEOUT_RETRY_FACTOR2.0cuánto más largo se vuelve el reintento tras 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 falso positivo del LLM
REVIEW_AGENT_CONCURRENCY3llamadas de proveedor en vuelo por revisión
CELMIS_JOB_LEASE_SECONDS600techo de silencio del trabajador antes de que un trabajo pueda ser reclamado
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 → Monitoring muestra la profundidad de la cola, el gasto por espacio de trabajo y la configuración de modelo por agente. Usage & cost desglosa el gasto por superficie, de modo que una compilación de documentación por lotes no se lea como un chat.

Desplegar a 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 generatecompila 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-servesolo el receptor de webhook

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 guarda trabajos, políticas, historial de ejecuciones, gasto y el registro de auditoría. La cola de trabajos duradera es una tabla — la descolocació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 guarda embeddings, 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. Configura 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 de proveedor se usan directamente.

Solución de problemas

Un contenedor no arranca. 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. Comprueba 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 — sube REVIEW_LLM_TIMEOUT_SECONDS. Deliberadamente no se informa como una falla del proveedor.

Q&A no cita nada. El repositorio probablemente no está indexado, o está indexado sin embeddings. Repositories 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 espera un llamador 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 una única confirmación raíz de alrededor de 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 establece la posición de licencia y el origen del código — el desarrollo ocurrió en privado antes de esta confirmación, y nada de ello se necesita 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 establece el límite en su totalidad — LICENSE en sí es el texto AGPL sin modificar, porque un archivo de licencia con un preámbulo delante no se reconoce como esa licencia. Dos características empresariales viven detrás de él hoy — inicio de sesión único (OIDC / Keycloak) y análisis de revisiones, en src/ee/ y web/ee/, activadas por una clave de licencia sin conexión (consulta ee/README.md). El límite se trazó antes de la primera etiqueta porque añadirlo después significa volver a preguntar a cada contribuyente que ya ha enviado trabajo bajo un AGPL sin calificar.

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