> ## Documentation Index
> Fetch the complete documentation index at: https://exosphere-auto-translate-docs-20260626-1023.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Arquitectura

> Cómo funcionan internamente el manejador de hooks, la carga de configuración y la evaluación de políticas

Este documento explica cómo funciona failproofai internamente: cómo el sistema de hooks intercepta las llamadas a herramientas del agente, cómo se carga y combina la configuración, cómo se evalúan las políticas y cómo el dashboard monitorea la actividad del agente.

***

## Descripción general

failproofai tiene dos subsistemas independientes:

1. **Manejador de hooks** - Un subproceso CLI rápido que Claude Code invoca en cada llamada a herramienta del agente. Evalúa las políticas y devuelve una decisión.
2. **Monitor de agentes (Dashboard)** - Una aplicación web Next.js para monitorear sesiones de agentes y gestionar políticas.

Ambos subsistemas comparten archivos de configuración en `~/.failproofai/` y en el directorio `.failproofai/` del proyecto, pero se ejecutan como procesos separados y se comunican únicamente a través del sistema de archivos.

***

## Manejador de hooks

### Integración con Claude Code

Cuando ejecutas `failproofai policies --install`, escribe entradas como esta en `~/.claude/settings.json`:

```json theme={null}
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "failproofai --hook PreToolUse"
          }
        ]
      }
    ],
    "PostToolUse": [ ... ]
  }
}
```

Claude Code luego invoca `failproofai --hook PreToolUse` como subproceso antes de cada llamada a herramienta, pasando un payload JSON en stdin.

### Formato del payload

```json theme={null}
{
  "session_id": "abc123",
  "transcript_path": "/home/user/.claude/projects/myproject/sessions/abc123.jsonl",
  "cwd": "/home/user/myproject",
  "permission_mode": "default",
  "hook_event_name": "PreToolUse",
  "tool_name": "Bash",
  "tool_input": { "command": "sudo apt install nodejs" }
}
```

Para los eventos `PostToolUse`, el payload también contiene `tool_result` con la salida de la herramienta.

El manejador impone un límite de 1 MB en stdin. Los payloads que superen este límite se descartan y todas las políticas permiten implícitamente.

### Formato de respuesta

**Deny (PreToolUse):**

```json theme={null}
{
  "hookSpecificOutput": {
    "permissionDecision": "deny",
    "permissionDecisionReason": "Blocked by failproofai: sudo command blocked"
  }
}
```

**Deny (PostToolUse):**

```json theme={null}
{
  "hookSpecificOutput": {
    "additionalContext": "Blocked by failproofai because: API key detected in output"
  }
}
```

**Instruct (cualquier evento excepto Stop):**

```json theme={null}
{
  "hookSpecificOutput": {
    "additionalContext": "Instruction from failproofai: Verify tests pass before committing."
  }
}
```

**Instruct en evento Stop:**

* Código de salida: `2`
* El motivo se escribe en stderr (no en stdout)

**Allow:**

* Código de salida: `0`
* stdout vacío

**Allow con mensaje:**

`allow(message)` permite que una política envíe contexto informativo de vuelta a Claude incluso cuando la operación está permitida. El manejador de hooks escribe el siguiente JSON en **stdout** (no en un archivo de configuración — esta es la respuesta del manejador a Claude Code, igual que las respuestas de deny e instruct anteriores):

```json theme={null}
// Written to stdout by the hook handler process
{
  "hookSpecificOutput": {
    "additionalContext": "All CI checks passed on branch 'feat/my-feature'."
  }
}
```

* Código de salida: `0` (la operación está permitida)
* Cuando múltiples políticas devuelven `allow` con un mensaje, sus mensajes se unen con saltos de línea en una sola cadena `additionalContext`
* Si ninguna política proporciona un mensaje, stdout está vacío (igual que antes)

### Pipeline de procesamiento

`src/hooks/handler.ts` implementa el pipeline completo:

```text theme={null}
stdin JSON
  → analizar payload (máx. 1 MB)
  → extraer metadatos de sesión (session_id, cwd, tool_name, tool_input, etc.)
  → readMergedHooksConfig(cwd)    ← combina config del proyecto + local + global
  → registrar políticas integradas habilitadas con parámetros resueltos
  → cargar políticas personalizadas desde customPoliciesPath (si está definido)
  → registrar políticas personalizadas en el registro de políticas
  → evaluar todas las políticas (primero las integradas, luego las personalizadas)
      → el primer deny cortocircuita la evaluación
      → las decisiones instruct se acumulan
      → los mensajes allow se acumulan
  → escribir decisión JSON en stdout
  → persistir evento en ~/.failproofai/hook-activity.jsonl
  → salir
```

Todo el proceso se ejecuta en menos de 100 ms para payloads típicos, sin llamadas a LLM.

***

## Carga de configuración

`src/hooks/hooks-config.ts` implementa la carga de configuración en tres ámbitos.

```text theme={null}
[1] {cwd}/.failproofai/policies-config.json        ← proyecto  (mayor prioridad)
[2] {cwd}/.failproofai/policies-config.local.json  ← local
[3] ~/.failproofai/policies-config.json             ← global   (menor prioridad)
```

Lógica de combinación:

* `enabledPolicies` - unión deduplicada de los tres archivos
* `policyParams` - por clave de política, gana completamente el primer archivo que la defina
* `customPoliciesPath` - gana el primer archivo que lo defina
* `llm` - gana el primer archivo que lo defina

El dashboard web usa `readHooksConfig()` (solo global) para leer y escribir, ya que no se invoca con un cwd de proyecto.

***

## Evaluación de políticas

`src/hooks/policy-evaluator.ts` ejecuta las políticas en orden.

Para cada política:

1. Buscar el esquema `params` de la política (si tiene uno).
2. Leer `policyParams[policy.name]` desde la configuración combinada.
3. Combinar los valores proporcionados por el usuario sobre los valores predeterminados del esquema para producir `ctx.params`.
4. Llamar a `policy.fn(ctx)` con el contexto resuelto.
5. Si el resultado es `deny`, detener inmediatamente y devolver esa decisión.
6. Si el resultado es `instruct`, acumular el mensaje y continuar.
7. Si el resultado es `allow`, continuar con la siguiente política.

Después de que se ejecuten todas las políticas:

* Si se devolvió algún `deny`, emitir la respuesta de denegación.
* Si se recopilaron respuestas `instruct`, emitir una única respuesta instruct con todos los mensajes unidos.
* En caso contrario, emitir una respuesta allow (stdout vacío, salida 0).

***

## Políticas integradas

`src/hooks/builtin-policies.ts` define las 39 políticas integradas como objetos `BuiltinPolicyDefinition`:

```typescript theme={null}
interface BuiltinPolicyDefinition {
  name: string;
  description: string;
  fn: (ctx: PolicyContext) => PolicyResult;
  match: {
    events: HookEventType[];
    tools?: string[];
  };
  defaultEnabled: boolean;
  category: string;
  beta?: boolean;
  params?: PolicyParamsSchema;
}
```

Las políticas que aceptan `params` declaran un `PolicyParamsSchema` con tipos y valores predeterminados para cada parámetro. El evaluador de políticas inyecta los valores resueltos en `ctx.params` antes de llamar a `fn`. Las funciones de política leen `ctx.params` sin necesidad de verificar nulos, porque los valores predeterminados siempre se aplican primero.

La coincidencia de patrones dentro de las políticas usa tokens de comando analizados (argv), no comparación de cadenas sin procesar. Esto evita la evasión mediante inyección de operadores de shell (por ejemplo, un patrón para `sudo systemctl status *` no puede ser eludido agregando `; rm -rf /` al comando).

***

## Políticas personalizadas

`src/hooks/custom-hooks-registry.ts` implementa un registro respaldado por `globalThis`:

```typescript theme={null}
const REGISTRY_KEY = "__failproofai_custom_hooks__";

export const customPolicies = {
  add(hook: CustomHook): void { ... }
};

export function getCustomHooks(): CustomHook[] { ... }
export function clearCustomHooks(): void { ... }  // used in tests
```

`src/hooks/custom-hooks-loader.ts` carga el archivo de políticas del usuario:

1. Leer `customPoliciesPath` desde la configuración; omitir si está ausente.
2. Resolver a ruta absoluta; verificar que el archivo existe.
3. Reescribir todas las importaciones `from "failproofai"` a la ruta dist real para que `customPolicies` resuelva al mismo registro `globalThis`.
4. Reescribir recursivamente las importaciones locales transitivas para garantizar compatibilidad con ESM.
5. Escribir archivos `.mjs` temporales e importar el archivo de entrada con `import()`.
6. Llamar a `getCustomHooks()` para recuperar los hooks registrados.
7. Limpiar todos los archivos temporales en un bloque `finally`.

Ante cualquier error (archivo no encontrado, error de sintaxis, fallo de importación), el error se registra en `~/.failproofai/hook.log` y el cargador devuelve un array vacío. Las políticas integradas no se ven afectadas.

Las políticas personalizadas se evalúan después de todas las políticas integradas. Un `deny` de una política personalizada sigue cortocircuitando las políticas personalizadas restantes (pero todas las integradas ya se habrán ejecutado en ese punto).

***

## Registro de actividad

Después de cada evento de hook, el manejador agrega una línea JSONL a `~/.failproofai/hook-activity.jsonl`:

```json theme={null}
{
  "timestamp": "2026-04-06T12:34:56.789Z",
  "sessionId": "abc123",
  "eventType": "PreToolUse",
  "toolName": "Bash",
  "policyName": "block-sudo",
  "decision": "deny",
  "reason": "sudo command blocked by failproofai",
  "durationMs": 12
}
```

Una línea por política que tomó una decisión distinta de allow. Las decisiones allow no se registran (para mantener el archivo pequeño).

***

## Arquitectura del dashboard

El dashboard es una aplicación **Next.js 16** que utiliza App Router con React Server Components y Server Actions.

```text theme={null}
app/
  layout.tsx                  ← Layout raíz (tema, telemetría, navegación)
  projects/page.tsx           ← Componente servidor: listar todos los proyectos Claude
  project/[name]/page.tsx     ← Componente servidor: listar sesiones en un proyecto
  project/[name]/session/
    [sessionId]/page.tsx      ← Componente servidor: renderizar visor de sesión
  policies/page.tsx           ← Componente cliente: gestión de políticas + registro de actividad
  actions/
    get-hooks-config.ts       ← Leer configuración + lista de políticas
    update-hooks-config.ts    ← Activar/desactivar política
    update-policy-params.ts   ← Actualizar parámetros de política
    get-hook-activity.ts      ← Paginar/buscar en el registro de actividad
    install-hooks-web.ts      ← Instalar/eliminar hooks desde el navegador
  api/
    download/[project]/[session]/route.ts   ← Exportación de sesión CLI (JSONL o JSON)
```

**Flujo de datos:**

* Los componentes de página llaman a `lib/projects.ts` y `lib/log-entries.ts` para leer datos de proyectos/sesiones directamente desde el sistema de archivos (sin capa API para lecturas).
* La página de políticas usa Server Actions para todas las mutaciones (activar/desactivar, actualizar parámetros, instalar/eliminar).
* El visor de sesión analiza el formato de transcripción JSONL de Claude y renderiza una línea de tiempo de mensajes y llamadas a herramientas.

**Decisiones de diseño clave:**

* Sin base de datos: todo el estado persistente está en archivos planos (`~/.failproofai/`, `~/.claude/projects/`).
* Server Actions para mutaciones: no se necesita API REST para operaciones CRUD.
* React Server Components para páginas de lectura: carga inicial más rápida, sin bundle de cliente para la obtención de datos.
* Componentes cliente solo donde se necesita interactividad (toggles de políticas, búsqueda de actividad, visor de logs).

***

## Estructura de archivos

```text theme={null}
failproofai/
├── bin/
│   └── failproofai.mjs           # Enrutador CLI (hook / dashboard / install / etc.)
├── src/hooks/
│   ├── handler.ts                # Pipeline de eventos de hook
│   ├── builtin-policies.ts       # 39 definiciones de políticas
│   ├── policy-evaluator.ts       # Motor de ejecución de políticas
│   ├── policy-registry.ts        # Registro y búsqueda de políticas
│   ├── policy-types.ts           # Interfaces TypeScript
│   ├── hooks-config.ts           # Carga de configuración multi-ámbito
│   ├── custom-hooks-registry.ts  # Registro de hooks respaldado por globalThis
│   ├── custom-hooks-loader.ts    # Cargador ESM para hooks JS del usuario
│   ├── manager.ts                # Operaciones de instalación / eliminación / listado
│   ├── install-prompt.ts         # Prompt interactivo de selección de políticas
│   ├── hook-logger.ts            # Registro en hook.log
│   ├── hook-activity-store.ts    # Persistir actividad en hook-activity.jsonl
│   └── llm-client.ts             # Cliente API LLM (para políticas potenciadas por IA)
├── app/                          # Dashboard Next.js (páginas + server actions)
├── lib/                          # Utilidades compartidas
│   ├── projects.ts               # Enumerar proyectos Claude desde el sistema de archivos
│   ├── log-entries.ts            # Analizar formato JSONL de transcripción Claude
│   ├── paths.ts                  # Resolver rutas del sistema
│   └── ...
├── components/                   # Componentes React UI compartidos
├── contexts/                     # Proveedores de contexto React (tema, auto-refresco, telemetría)
├── examples/                     # Archivos de hooks personalizados de ejemplo
└── __tests__/                    # Pruebas unitarias y E2E
```
