Ir al contenido

El detector

Las cinco verificaciones

audit corre cinco verificaciones, siempre en el mismo orden. Todas son código puro —git, sistema de archivos y expresiones regulares—, sin modelo, sin conexión y sin API key: la misma entrada da el mismo resultado. Cada una lee una parte de AI-FIRST.md, y si esa parte falta se reporta omitida, nunca aprobada.

# Verificación Sev. Qué detecta Por qué importa Lee de AI-FIRST.md
1 Zona Prohibida tocada P0 Se tocó una ruta declarada como Zona Prohibida Es lo único que corta el flujo aun sin --estricto zonas_prohibidas
2 Decisión sin fila en ADR P1 Señales de decisión arquitectónica sin fila nueva en el ADR La decisión existe en el código y no en el registro superficies_de_decision, artefactos.adr
3 Alcance excedido P1 Más archivos tocados de los que declara la spec activa El alcance creció y nadie lo midió alcance.spec, alcance.tolerancia
4 Artefacto huérfano P2 Un documento declarado, o una ruta que menciona, no existe Un documento apunta a algo que ya nadie mantiene artefactos
5 Inventario de componentes desactualizado P2 Componentes en disco que el inventario no nombra, o al revés El inventario empezó a mentir artefactos.inventario_componentes, artefactos.componentes_dir

Cuatro de las cinco son deterministas y no admiten discusión: una ruta cambió o no cambió, un archivo referenciado existe o no existe, un componente aparece en el inventario o no aparece. La segunda no lo es, porque «se tomó una decisión arquitectónica» no cabe en una expresión regular: se resuelve declarando dónde un cambio se presume decisión, produce falsos positivos y por eso se puede silenciar. Una verificación que no se puede silenciar termina desactivada entera, que es peor que tenerla ruidosa.

Los patrones de ruta que usan las verificaciones 1, 2 y 3 siguen las reglas de Patrones de ruta. Qué es «tocado» depende del modo, árbol o rango: ver Modos.


Severidad: P0 · Identificador: zona-prohibida

Compara cada archivo tocado contra los patrones de zonas_prohibidas. Un hallazgo por zona, no por archivo: tocar tres migraciones es una Zona Prohibida tocada, con los tres archivos en el detalle. Si la zona declara razon, el mensaje la incluye.

  • Lee: zonas_prohibidas[].ruta y zonas_prohibidas[].razon.
  • Se omite si no hay ninguna zona declarada: no hay «zonas_prohibidas» declaradas.
  • No se silencia. Una Zona Prohibida no impide el cambio, lo hace visible: el hallazgo existe para que un humano decida. Si el cambio estaba aprobado, se publica igual; lo que no se hace es quitar la zona para que el detector calle.
✗ Zona Prohibida tocada
P0 Zona Prohibida «.env*» tocada — credenciales
.env.local

Por qué y cómo se eligen las zonas está en Gobierno del contexto.


Severidad: P1 · Identificador: decision-sin-adr

Busca dos señales de que se tomó una decisión arquitectónica:

  • Cambió un archivo que coincide con superficies_de_decision.
  • Un package.json, en cualquier carpeta, ganó o perdió una dependencia de producción. Se comparan las claves de dependencies; devDependencies no cuenta. Esta señal no hay que declararla.

Si hay alguna señal y el archivo de artefactos.adr no ganó una fila en el mismo tramo, emite un P1. Una fila es una línea agregada que empieza con un encabezado de nivel 2 o 3 seguido de ADR- y un número, como ## ADR-007 — La cola de trabajos pasa a SQS.

  • Lee: superficies_de_decision y artefactos.adr.
  • Se omite si no hay artefactos.adr: no hay «artefactos.adr» declarado. Sin superficies declaradas, igual corre: le queda la señal de las dependencias.
  • Se silencia de dos formas:
    1. Agregando la fila al ADR, si de verdad era una decisión.

    2. Con la anotación en el cuerpo de un commit del rango, si no lo era, por ejemplo subir un timeout en un *.config.*:

      <!-- ai-first: sin-decision -->

      La anotación sólo se lee en modo rango, con --base: en modo árbol no hay commits cuyo cuerpo leer. Ahí el P1 queda como advertencia y no corta salvo con --estricto. El hook pre-push corre en modo rango cuando la rama ya existe en el remoto, y el flujo de CI siempre.

✗ Decisión sin fila en ADR
P1 Hay señales de decisión arquitectónica y ADR.md no ganó ninguna fila
vite.config.ts (superficie «**/*.config.*»)
Silenciar: agregar la fila al ADR, o «<!-- ai-first: sin-decision -->» en el cuerpo del commit.

Una dependencia aparece en el detalle como package.json: +nombre o package.json: -nombre. Qué merece una fila y qué no está en Gobierno del contexto.


Severidad: P1 · Identificador: alcance-excedido

Compara los archivos tocados contra los que declara la spec activa. Si los que quedan fuera superan tolerancia, emite un solo P1 con la lista.

La spec activa es alcance.spec: un archivo, o una carpeta, y entonces cuenta cada .md o .mdx que haya dentro y no empiece por punto. init la apunta a docs/changes/pending/, donde vive el CHG del cambio en curso.

Cómo declara archivos una spec: en una sección cuyo encabezado empiece por «Archivos» o «Alcance», de cualquier nivel y sin importar mayúsculas, con cada ruta entre acentos graves. Se admiten patrones. La sección llega hasta el siguiente encabezado de igual o mayor nivel, y lo que esté dentro de un bloque de código no cuenta.

## Archivos
- `src/lib/cola.ts`
- `src/lib/cola.test.ts`
- `docs/specs/cola.md`

En la 0.5.1, una ruta declarada tiene que llevar una barra o una extensión conocida, como .ts, .md, .json o .yml. Un archivo sin carpeta y sin extensión reconocida —.npmrc, LICENSE, Makefile— no se puede declarar: si lo tocas, cuenta fuera del alcance y sólo lo cubre la tolerancia.

Nunca cuentan como fuera de alcance: AI-FIRST.md, la propia spec o su carpeta, y cualquier archivo declarado en artefactos. Actualizar el registro de sesión al cerrar es parte del protocolo, no un desvío.

  • Lee: alcance.spec y alcance.tolerancia. Sin tolerancia declarada vale 0.
  • Se omite si:
    • no hay alcance.spec: no hay «alcance.spec» declarado;
    • no hay ninguna spec en esa ruta: no hay ninguna spec activa en docs/changes/pending/. Es el caso normal cuando no hay un cambio abierto: no hay alcance que exceder;
    • las specs no listan archivos: las specs en … no listan archivos (sección «Archivos» o «Alcance» con rutas entre acentos graves).
  • Se silencia declarando en la spec lo que de verdad entró en el cambio, o subiendo tolerancia si el proyecto acepta cierto margen.
✗ Alcance excedido
P1 3 archivos fuera del alcance declarado (tolerancia: 0)
src/b.ts
src/c.ts
vite.config.ts

Severidad: P2 · Identificador: artefacto-huerfano

Verifica que existan los documentos declarados en artefactos y que las rutas que mencionan resuelvan. Dos cosas cuentan como huérfano:

  • Un artefacto declarado que no existe. Una clave que apunta a una carpeta sólo tiene que existir; un archivo, además, se recorre.
  • Una ruta mencionada dentro de un artefacto que no existe, sea en un enlace [texto](ruta) o entre acentos graves.

Qué cuenta como mención de ruta: un token con barra o con una extensión conocida, sin espacios. No cuentan las URL, lo que empieza con /, @, - o ~, las refs de git como origin/main, los patrones con * y los marcadores como CHG-XXX o docs/…. Lo que está dentro de un bloque de código tampoco: ahí las rutas son ejemplos.

Cómo resuelve, en orden:

  1. Desde la raíz del repo y desde la carpeta del documento. Lo que .gitignore cubre, como dist/ o .env, cuenta como existente: está ausente a propósito.
  2. Un nombre sin carpeta, como cola.ts, se busca por nombre en todo el repo: la prosa nombra archivos, no siempre los ubica.
  3. Una ruta con carpeta cuyo primer segmento no existe en la raíz se asume de otro árbol —otro proyecto, un ejemplo, un repo externo— y no se reporta. Un check que no puede verificar algo no lo cuenta como fallo.
  • Lee: todas las claves de artefactos, las conocidas y las que agregues.
  • Se omite si no hay ningún artefacto declarado: no hay «artefactos» declarados. Tras init nunca pasa, porque declara adr y agents.
  • Se silencia corrigiendo la ruta o quitando la mención. Si la ruta es de otro repositorio, escríbela con su carpeta o sin acentos graves; si es un ejemplo, dentro de un bloque de código.
✗ Artefacto huérfano
P2 docs/ARQUITECTURA.md menciona src/lib/queue.ts, que no existe

Un artefacto que no existe se reporta como «artefactos.clave» apunta a ruta, que no existe. En JSON, la ruta del hallazgo lleva el número de línea: docs/ARQUITECTURA.md:3.


Severidad: P2 · Identificador: inventario-componentes

Compara los componentes que hay en componentes_dir con lo que nombra el documento de inventario_componentes. Es por nombre, así que un renombre sin actualizar el inventario también lo caza. Mira en las dos direcciones:

  • Un componente en disco que el inventario no menciona. Basta con que el nombre aparezca como palabra entera en cualquier parte del documento.
  • Un encabezado del inventario que nombra un componente que ya no existe. Sólo cuentan los encabezados de nivel 2 o más con forma de componente: entre acentos graves o ángulos, como `Boton` o <Boton>, o con mayúscula interna, como BotonPrimario. Una palabra capitalizada suelta, como «Formularios», es un título de sección y se deja pasar.

Qué cuenta como componente: cada archivo .astro, .tsx, .jsx, .vue, .svelte, .ts o .js dentro de la carpeta, a cualquier profundidad. Boton/index.tsx es el componente Boton; un index en la raíz de la carpeta es el barril de exportación y no cuenta. Se ignoran los archivos de prueba, de stories y de declaración de tipos, lo que empieza por . o _, y node_modules.

  • Lee: artefactos.inventario_componentes y artefactos.componentes_dir. Hacen falta las dos.
  • Se omite si:
    • falta alguna de las dos claves: faltan «artefactos.inventario_componentes» o «artefactos.componentes_dir». Es lo que pasa en un repo recién iniciado: init las deja comentadas;
    • la carpeta no existe o no es una carpeta;
    • el inventario no existe. Eso ya lo reporta la verificación de artefactos huérfanos.
  • Se silencia agregando el componente al inventario, o quitando el encabezado del que ya se borró.
✗ Inventario de componentes desactualizado
P2 Tarjeta existe en src/components/ y no aparece en COMPONENTES.md
P2 COMPONENTES.md documenta Modal, que ya no existe en src/components/