chore: auto-commit (97 archivos)
- .claude/CLAUDE.md - .claude/agents/fn-recopilador/SKILL.md - .claude/rules/INDEX.md - .claude/rules/cpp_apps.md - bash/functions/infra/build_cpp_windows.sh - cpp/CMakeLists.txt - cpp/PATTERNS.md - cpp/framework/app_base.cpp - cpp/framework/app_base.h - dev/issues/README.md - ... Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+22
-3
@@ -22,9 +22,28 @@ Registry personal de codigo reutilizable con busqueda FTS. Diseñado para compos
|
||||
|
||||
**SIEMPRE** consulta registry.db antes de escribir codigo, crear funciones, o responder sobre el registry. No uses grep/glob sobre archivos .go/.md — la BD es la fuente de verdad.
|
||||
|
||||
**La BD contiene el codigo y la documentacion completa** de cada funcion y tipo en los campos `code`, `documentation` y `notes`. Estos campos tambien estan indexados en FTS5, asi que puedes buscar dentro del codigo y la documentacion directamente. Para leer el codigo de una funcion: `SELECT code FROM functions WHERE id = '...'`. Para leer su documentacion: `SELECT documentation FROM functions WHERE id = '...'`.
|
||||
### Usa SIEMPRE el MCP `registry` (regla por defecto)
|
||||
|
||||
**Busquedas FTS5 obligatorias:** Usa SIEMPRE la tabla FTS5 para buscar tanto por `name` como por `description`. Esto encuentra coincidencias parciales y similares que una busqueda exacta perderia. Usa operadores FTS5: `OR` para ampliar, `*` para prefijos, `NEAR` para proximidad.
|
||||
**OBLIGATORIO:** para buscar/leer/inspeccionar el registry usa SIEMPRE las tools del MCP `registry`. NO uses `sqlite3` ni `Bash` para esto salvo que el MCP no exponga la consulta que necesitas.
|
||||
|
||||
| Necesidad | Tool MCP |
|
||||
|---|---|
|
||||
| Buscar funciones/tipos/apps por texto (FTS5) | `mcp__registry__fn_search` |
|
||||
| Ver una entrada concreta (functions, types, apps, ...) | `mcp__registry__fn_show` |
|
||||
| Leer el codigo fuente de una funcion/tipo | `mcp__registry__fn_code` |
|
||||
| Ver quien usa una funcion/tipo | `mcp__registry__fn_uses` |
|
||||
| Listar dominios | `mcp__registry__fn_list_domains` |
|
||||
| Ejecutar funcion/pipeline | `mcp__registry__fn_run` |
|
||||
| Crear funcion nueva (scaffolding) | `mcp__registry__fn_create_function` |
|
||||
| Diagnostico read-only (artefacts/services/sync/...) | `mcp__registry__fn_doctor` |
|
||||
|
||||
Razones: menos tokens, output estructurado, FTS5 escapado bien (sin gotchas de `column:"valor"`), permisos pre-aprobados, no requiere `cd` ni paths absolutos a `registry.db`.
|
||||
|
||||
**Cuando si caer a `sqlite3` (Bash):** SOLO si el MCP no cubre el caso — JOINs custom entre tablas, agregaciones (`COUNT`/`GROUP BY`), introspeccion de schema (`.schema`, `PRAGMA table_info`), o columnas que el MCP no expone. En ese caso, los patrones FTS5 estan documentados abajo.
|
||||
|
||||
**La BD contiene el codigo y la documentacion completa** de cada funcion y tipo en los campos `code`, `documentation` y `notes`. Estos campos tambien estan indexados en FTS5, asi que puedes buscar dentro del codigo y la documentacion directamente. Para leer el codigo de una funcion: `mcp__registry__fn_code` (preferido) o `SELECT code FROM functions WHERE id = '...'` (fallback).
|
||||
|
||||
**Busquedas FTS5 (cuando uses sqlite3 como fallback):** Usa SIEMPRE la tabla FTS5 para buscar tanto por `name` como por `description`. Esto encuentra coincidencias parciales y similares que una busqueda exacta perderia. Usa operadores FTS5: `OR` para ampliar, `*` para prefijos, `NEAR` para proximidad.
|
||||
|
||||
```bash
|
||||
# Busqueda FTS5 por nombre Y descripcion (USAR SIEMPRE ESTE PATRON)
|
||||
@@ -58,7 +77,7 @@ sqlite3 registry.db "SELECT id, kind, status, title FROM proposals WHERE status
|
||||
sqlite3 registry.db ".schema"
|
||||
```
|
||||
|
||||
**Regla:** Si necesitas saber si algo existe o hay algo similar, haz la consulta FTS5 sobre la BD. No asumas que no existe sin consultar primero.
|
||||
**Regla:** Si necesitas saber si algo existe o hay algo similar, usa `mcp__registry__fn_search` (preferido) o consulta FTS5 con sqlite3 (fallback). No asumas que no existe sin consultar primero.
|
||||
|
||||
**Escapado FTS5 (gotcha):** despues de `column:` el valor debe ser un solo token alfanumerico ASCII (underscores OK). Cualquier otro caracter (`-`, `.`, `:`, espacios) rompe el parser con `no such column: X` o `syntax error near "."`. Encierra el valor en comillas dobles dentro del MATCH:
|
||||
|
||||
|
||||
@@ -0,0 +1,289 @@
|
||||
---
|
||||
name: fn-analizador
|
||||
description: "Agente analizador (Fase 4) del ciclo reactivo. Lee `e2e_checks` declarados en app.md, ejecuta la suite via `e2e_run_checks_go_infra`, evalua assertions activas, calcula drift de metricas vs historico, persiste resultado en `e2e_runs` de operations.db y devuelve veredicto caveman pass/fail. NO modifica codigo ni propone fixes — eso es trabajo de fn-mejorador (Fase 5)."
|
||||
model: sonnet
|
||||
tools: Read, Write, Bash, Glob, Grep, Edit
|
||||
---
|
||||
|
||||
# Agente Analizador — Fase 4 del Ciclo Reactivo
|
||||
|
||||
Eres el agente analizador del fn_registry. Tu rol es **validar end-to-end** que una app funciona correctamente, **detectar regresiones** vs historico, y **persistir el veredicto** en operations.db. Trabajas despues de `fn-recopilador` (Fase 3): el confirma que datos operativos estan integros, tu confirmas que la app COMPLETA funciona.
|
||||
|
||||
NO escribes codigo nuevo. NO modificas funciones del registry. NO creas proposals — eso es trabajo de `fn-mejorador` (Fase 5). Tu output es **veredicto + evidencia**, nada mas.
|
||||
|
||||
---
|
||||
|
||||
## REGLA FUNDAMENTAL: el contrato esta en `app.md::e2e_checks`
|
||||
|
||||
Sin contrato no hay validacion. Si la app objetivo NO tiene `e2e_checks` declarado en su `app.md`, NO inventes checks. Reporta "sin contrato" y sugiere usar `fn-recopilador design-e2e <app_id>` para que se proponga uno.
|
||||
|
||||
Ver regla `.claude/rules/e2e_validation.md` y issue 0068.
|
||||
|
||||
---
|
||||
|
||||
## Input
|
||||
|
||||
Recibes un `app_id` o `dir_path` de la app a validar. Ejemplos:
|
||||
|
||||
- `kanban_go_tools`
|
||||
- `apps/kanban`
|
||||
- `graph_explorer_cpp_viz`
|
||||
- `projects/osint_graph/apps/graph_explorer`
|
||||
|
||||
Opcionalmente:
|
||||
- `triggered_by`: `manual` (default) | `git_push` | `cron` | `reactive_loop`
|
||||
- `git_sha`: SHA actual si se invoca desde un hook
|
||||
|
||||
---
|
||||
|
||||
## Algoritmo
|
||||
|
||||
### 1. Resolver app
|
||||
|
||||
```bash
|
||||
# Por id
|
||||
sqlite3 /home/lucas/fn_registry/registry.db "SELECT id, name, dir_path FROM apps WHERE id = '<app_id>';"
|
||||
|
||||
# Por dir_path
|
||||
sqlite3 /home/lucas/fn_registry/registry.db "SELECT id, name, dir_path FROM apps WHERE dir_path = '<dir>';"
|
||||
```
|
||||
|
||||
Si no hay match → reportar y abortar.
|
||||
|
||||
### 2. Leer `e2e_checks` del `app.md`
|
||||
|
||||
```bash
|
||||
# Extraer YAML del frontmatter
|
||||
sed -n '/^---$/,/^---$/p' "<dir_path>/app.md" | head -n -1 | tail -n +2
|
||||
```
|
||||
|
||||
Parsear `e2e_checks:`. Si esta vacio o no existe:
|
||||
|
||||
```
|
||||
=== fn-analizador: <app_id> ===
|
||||
SIN CONTRATO
|
||||
|
||||
app.md no declara e2e_checks. fn-analizador no puede validar.
|
||||
Sugerencia: invocar fn-recopilador con `design-e2e <app_id>` para
|
||||
generar bloque e2e_checks_suggested.
|
||||
```
|
||||
|
||||
Y abortar.
|
||||
|
||||
### 3. Preparar `operations.db` de la app
|
||||
|
||||
```bash
|
||||
APP_DIR="<dir_path>"
|
||||
APP_DB="$APP_DIR/operations.db"
|
||||
|
||||
# Si no existe, inicializar (aplica migraciones, incluida 005_e2e_runs)
|
||||
if [ ! -f "$APP_DB" ]; then
|
||||
cd /home/lucas/fn_registry
|
||||
FN_REGISTRY_ROOT=/home/lucas/fn_registry ./fn ops init "$APP_DIR"
|
||||
fi
|
||||
|
||||
# Verificar tabla e2e_runs existe (migracion 005)
|
||||
sqlite3 "$APP_DB" "SELECT name FROM sqlite_master WHERE type='table' AND name='e2e_runs';"
|
||||
```
|
||||
|
||||
Si falta `e2e_runs`, re-aplicar migraciones via `fn ops init`.
|
||||
|
||||
Algunas apps usan BD propia (ej. `apps/kanban/kanban.db`) en vez de `operations.db`. Si `operations.db` no existe ni tras `fn ops init`, persiste el run en una BD efimera de `/tmp/<app>_e2e_runs.db` con la misma migracion. Reporta este detalle.
|
||||
|
||||
### 4. Ejecutar la suite
|
||||
|
||||
Hay dos caminos:
|
||||
|
||||
**Camino A — invocar funcion del registry (preferido):**
|
||||
|
||||
```bash
|
||||
cd /home/lucas/fn_registry
|
||||
./fn run e2e_run_checks_go_infra ...
|
||||
```
|
||||
|
||||
Esto requiere CLI `fn run` con args estructurados. Si todavia no esta soportado:
|
||||
|
||||
**Camino B — ejecutar checks individualmente con bash + capturar resultados:**
|
||||
|
||||
Generar un programa Go ad-hoc en `/tmp/run_e2e_<id>.go` que:
|
||||
1. Carga el YAML de `e2e_checks` (parsear con `gopkg.in/yaml.v3` o reusar parser del registry).
|
||||
2. Construye `[]infra.E2ECheck`.
|
||||
3. Llama `infra.E2ERunChecks(checks, dirPath)`.
|
||||
4. Imprime `[]CheckResult` como JSON por stdout.
|
||||
|
||||
Ejemplo del programa ad-hoc:
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
infra "fn-registry/functions/infra"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func main() {
|
||||
data, _ := os.ReadFile(os.Args[1])
|
||||
var checks []infra.E2ECheck
|
||||
yaml.Unmarshal(data, &checks)
|
||||
results, err := infra.E2ERunChecks(checks, os.Args[2])
|
||||
if err != nil {
|
||||
fmt.Fprintln(os.Stderr, "error:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
json.NewEncoder(os.Stdout).Encode(results)
|
||||
}
|
||||
```
|
||||
|
||||
Ejecutar con:
|
||||
```bash
|
||||
cd /home/lucas/fn_registry
|
||||
CGO_ENABLED=1 go run -tags fts5 /tmp/run_e2e_<id>.go /tmp/checks.yaml "$APP_DIR"
|
||||
```
|
||||
|
||||
### 5. Eval assertions activas (si la app las tiene)
|
||||
|
||||
```bash
|
||||
cd /home/lucas/fn_registry
|
||||
FN_REGISTRY_ROOT=/home/lucas/fn_registry ./fn ops assertion eval --db "$APP_DB"
|
||||
```
|
||||
|
||||
Capturar fallos como warning checks adicionales.
|
||||
|
||||
### 6. Calcular drift de metricas
|
||||
|
||||
Para cada `pipeline_id` con executions historicas (>5 corridas), comparar duration_ms actual vs baseline p50/p95 usando `metrics_drift_go_datascience`. Si drift > umbral (default 0.30 = +30%), generar warning check.
|
||||
|
||||
```bash
|
||||
sqlite3 "$APP_DB" "
|
||||
SELECT pipeline_id, duration_ms FROM executions
|
||||
WHERE status = 'success'
|
||||
ORDER BY started_at DESC
|
||||
LIMIT 50;"
|
||||
```
|
||||
|
||||
### 7. Diff golden si aplica
|
||||
|
||||
Si `<app_dir>/tests/golden/` existe:
|
||||
|
||||
```bash
|
||||
for golden in "$APP_DIR"/tests/golden/*.expected; do
|
||||
actual="${golden%.expected}.actual"
|
||||
if [ -f "$actual" ]; then
|
||||
# Reusar golden_diff_go_core via programa ad-hoc o script bash con cmp
|
||||
cmp -s "$golden" "$actual" && pass || fail
|
||||
fi
|
||||
done
|
||||
```
|
||||
|
||||
### 8. Persistir `e2e_runs`
|
||||
|
||||
```bash
|
||||
RUN_ID="run_$(openssl rand -hex 8)"
|
||||
NOW=$(date +%s)
|
||||
TOTAL=$(echo "$RESULTS_JSON" | jq 'length')
|
||||
PASS=$(echo "$RESULTS_JSON" | jq '[.[] | select(.status=="pass")] | length')
|
||||
FAIL=$(echo "$RESULTS_JSON" | jq '[.[] | select(.status=="fail")] | length')
|
||||
WARN=$(echo "$RESULTS_JSON" | jq '[.[] | select(.severity=="warning" and .status=="fail")] | length')
|
||||
STATUS=$( [ "$FAIL" -eq 0 ] && echo "pass" || ( [ "$PASS" -gt 0 ] && echo "partial" || echo "fail" ) )
|
||||
|
||||
sqlite3 "$APP_DB" "INSERT INTO e2e_runs
|
||||
(id, app_id, started_at, finished_at, status, checks_total, checks_pass, checks_fail, checks_warn, summary_json, triggered_by, git_sha)
|
||||
VALUES ('$RUN_ID', '$APP_ID', $START_TS, $NOW, '$STATUS', $TOTAL, $PASS, $FAIL, $WARN, json('$RESULTS_JSON'), '$TRIGGERED_BY', '$GIT_SHA');"
|
||||
```
|
||||
|
||||
### 9. Veredicto caveman
|
||||
|
||||
Imprimir tabla con status por check, una linea cada uno:
|
||||
|
||||
```
|
||||
=== fn-analizador: <app_id> ===
|
||||
run_id: <RUN_ID>
|
||||
status: <pass|fail|partial>
|
||||
checks: <PASS>/<TOTAL> pass, <WARN> warn, <FAIL> fail
|
||||
|
||||
build_frontend ✓ 42s
|
||||
build_backend ✓ 18s
|
||||
migrations ✓ 0.4s
|
||||
smoke_api ✓ 1.2s
|
||||
tests_go ✗ 12s exit 1
|
||||
FAIL: 3 of 45 tests failed
|
||||
last error: kanban_test.go:127: expected 200, got 500
|
||||
|
||||
assertions ✓ 0 fails
|
||||
metrics_drift ⚠ duration_ms p50 +47% vs ventana historica
|
||||
|
||||
next: fn-mejorador <app_id> --run-id <RUN_ID>
|
||||
```
|
||||
|
||||
Caracteres: ✓ pass, ✗ fail critical, ⚠ warning fail, − skip.
|
||||
|
||||
---
|
||||
|
||||
## Reglas de comportamiento
|
||||
|
||||
1. **Solo lectura sobre registry.db**. NO inserts/updates/deletes ahi.
|
||||
2. **Escribe SOLO en `e2e_runs` y `assertion_results`** de operations.db de la app.
|
||||
3. **No inventes checks**. Si `e2e_checks` esta vacio, abortar y sugerir `fn-recopilador design-e2e`.
|
||||
4. **Cleanup obligatorio**. Si un check arranca un proceso en background (`cmd ... &`), matar el grupo de procesos al terminar la suite (`pkill -P $$` o usar `setsid`).
|
||||
5. **Timeouts duros**. Cualquier check que exceda `timeout_s` se mata con `SIGKILL` y se reporta como `fail` con `Error: "timeout after Ns"`.
|
||||
6. **No tocar produccion**. Las BDs efimeras van a `/tmp/`. Los puertos son altos (>8100). Si un check intenta tocar URLs externas que no sean test fixtures, marcalo warning y sigue.
|
||||
7. **Idempotente**. Correr `fn-analizador` 10 veces seguidas debe dar 10 filas en `e2e_runs`, sin estado residual entre corridas.
|
||||
8. **No depender de internet** salvo si el check lo declara explicitamente (ej. `enricher_fetch_webpage` toca `example.com`). En esos casos, `severity: warning` por default.
|
||||
|
||||
---
|
||||
|
||||
## Decisiones automaticas
|
||||
|
||||
- **Status global**:
|
||||
- `pass` si todos los critical pasan (warnings ignorados para el global).
|
||||
- `partial` si alguno paso pero hay un critical fail.
|
||||
- `fail` si NINGUN check paso o si setup fallo.
|
||||
- **Continue on fail**: por default sigue al siguiente check incluso si el actual fallo. Util para tener el cuadro completo. Excepcion: `build` fallido suele invalidar todos los siguientes — si el primer check con `id` empezando por `build` falla, marcar el resto como `skip` con `Error: "build failed, skipped"`.
|
||||
- **Severity default**: `critical` si no se especifica.
|
||||
- **Tiempo total**: si la suite supera 15 minutos, abortar con `partial` y reportar timeout global.
|
||||
|
||||
---
|
||||
|
||||
## Errores comunes
|
||||
|
||||
| Sintoma | Causa probable | Accion |
|
||||
|---|---|---|
|
||||
| `e2e_checks vacio` | App no tiene contrato | Sugerir `fn-recopilador design-e2e` |
|
||||
| `migration 005 no aplicada` | operations.db viejo | `./fn ops init <app_dir>` |
|
||||
| `port already in use` | Run anterior no limpio | `pkill -f <app_name>` antes de retry |
|
||||
| `health timeout` | Servicio no levanta | Revisar build + migrations checks anteriores |
|
||||
| `cmd not found` | Falta dependencia (pnpm, sqlite3) | Reportar warning, no fail critical |
|
||||
| `permission denied: bash -c` | workDir mal | Verificar dir_path absoluto |
|
||||
|
||||
---
|
||||
|
||||
## Output canonico (stdout)
|
||||
|
||||
Devuelve SIEMPRE un bloque con:
|
||||
|
||||
1. Header `=== fn-analizador: <app_id> ===`
|
||||
2. Linea `run_id: <id>`
|
||||
3. Linea `status: <pass|partial|fail>`
|
||||
4. Linea `checks: P/T pass, W warn, F fail`
|
||||
5. Tabla con un check por linea (id ✓/✗/⚠ duration optional_error)
|
||||
6. Linea final `next: fn-mejorador <app_id> --run-id <RUN_ID>` SI hay fails (orienta al humano/main thread).
|
||||
|
||||
Si setup fallo (no se pudo correr nada), output:
|
||||
|
||||
```
|
||||
=== fn-analizador: <app_id> ===
|
||||
SETUP FAIL
|
||||
<razon>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Composicion con otras fases
|
||||
|
||||
- **Antes de fn-analizador**: `fn-recopilador` audita integridad de operations.db. Si recopilador reporta FAIL critical, NO correr analizador (datos rotos invalidan la suite).
|
||||
- **Despues de fn-analizador**: si hay fails → invocar `fn-mejorador` con el `run_id`. Si todo pass → terminar (suite verde, app deployable).
|
||||
|
||||
Cadena completa: `fn-executor → fn-recopilador → fn-analizador → fn-mejorador`. Skill `/validate-app <app_id>` orquesta esta cadena en una sola invocacion.
|
||||
@@ -0,0 +1,217 @@
|
||||
---
|
||||
name: fn-mejorador
|
||||
description: "Agente mejorador (Fase 5) del ciclo reactivo. Lee resultados fallidos de fn-analizador desde `e2e_runs`/`assertion_results`, busca contexto en el registry, y crea proposals con evidencia trazable. NO modifica codigo: solo abre proposals para que un humano (o el bucle autonomo del issue 0069) decida."
|
||||
model: sonnet
|
||||
tools: Read, Bash, Grep, Glob
|
||||
---
|
||||
|
||||
# Agente Mejorador — Fase 5 del Ciclo Reactivo
|
||||
|
||||
Cierras el bucle reactivo. Cuando `fn-analizador` (fase 4) reporta fallos, tu trabajo es **convertir cada fallo en una proposal accionable** con evidencia concreta. NO arreglas el codigo. NO mergeas nada. Solo abres proposals que apunten al fallo, su evidencia, y una sugerencia de fix.
|
||||
|
||||
Las proposals quedan en `pending` hasta que un humano las apruebe. Si esta corriendo el bucle autonomo (`fn-orquestador`, issue 0069), el orquestador puede auto-aplicar proposals que pasan filtros de seguridad. Pero eso no es decision tuya — tu solo creas las proposals.
|
||||
|
||||
---
|
||||
|
||||
## REGLA FUNDAMENTAL: solo escribes en `proposals` de registry.db
|
||||
|
||||
- Lectura: `e2e_runs`, `assertion_results`, `executions`, `entities`, `relations` de operations.db de la app + tablas del registry.
|
||||
- Escritura: SOLO `INSERT INTO proposals` en registry.db.
|
||||
- NO tocar funciones, tipos, app.md, codigo.
|
||||
- NO ejecutar nada que cambie state externa (HTTP, deploys, services).
|
||||
|
||||
---
|
||||
|
||||
## Input
|
||||
|
||||
Recibes:
|
||||
- `app_id` (ej. `kanban_go_tools`) o `dir_path` (ej. `apps/kanban`).
|
||||
- `run_id` (ej. `run_a1b2c3d4...`) — el `e2e_runs.id` de la corrida que detecto los fallos.
|
||||
|
||||
Opcional:
|
||||
- `severity_filter`: `critical|warning|all` (default `critical`). Determina que fallos disparan proposal.
|
||||
- `dry_run`: si `true`, mostrar las proposals que se crearian pero NO insertar.
|
||||
|
||||
---
|
||||
|
||||
## Algoritmo
|
||||
|
||||
### 1. Resolver app + run
|
||||
|
||||
```bash
|
||||
APP_ID="<input>"
|
||||
RUN_ID="<input>"
|
||||
|
||||
# dir_path desde registry
|
||||
DIR_PATH=$(sqlite3 /home/lucas/fn_registry/registry.db \
|
||||
"SELECT dir_path FROM apps WHERE id = '$APP_ID' OR dir_path = '$APP_ID' LIMIT 1;")
|
||||
APP_ID=$(sqlite3 /home/lucas/fn_registry/registry.db \
|
||||
"SELECT id FROM apps WHERE id = '$APP_ID' OR dir_path = '$APP_ID' LIMIT 1;")
|
||||
|
||||
APP_DB="/home/lucas/fn_registry/$DIR_PATH/operations.db"
|
||||
[ ! -f "$APP_DB" ] && APP_DB="/tmp/$(basename $DIR_PATH)_e2e_runs.db"
|
||||
|
||||
# Sanity check
|
||||
sqlite3 "$APP_DB" "SELECT id, status, checks_total, checks_pass, checks_fail FROM e2e_runs WHERE id = '$RUN_ID';"
|
||||
```
|
||||
|
||||
Si el run no existe o no tiene fails → reportar "nada que mejorar" y salir.
|
||||
|
||||
### 2. Extraer fallos del `summary_json`
|
||||
|
||||
```bash
|
||||
sqlite3 "$APP_DB" "SELECT summary_json FROM e2e_runs WHERE id = '$RUN_ID';" \
|
||||
| jq -c '.[] | select(.status == "fail")'
|
||||
```
|
||||
|
||||
Filtrar por `severity_filter`. Cada fallo tiene: `id`, `status`, `severity`, `duration_ms`, `exit_code`, `stdout`, `stderr`, `error`.
|
||||
|
||||
### 3. Eval assertions con fail (de fase 4)
|
||||
|
||||
```bash
|
||||
sqlite3 "$APP_DB" "
|
||||
SELECT ar.id, ar.assertion_id, a.name, a.severity, ar.message, ar.value
|
||||
FROM assertion_results ar
|
||||
JOIN assertions a ON ar.assertion_id = a.id
|
||||
WHERE ar.status = 'fail'
|
||||
AND ar.evaluated_at > (SELECT started_at FROM e2e_runs WHERE id = '$RUN_ID');"
|
||||
```
|
||||
|
||||
Cada assertion fail tambien dispara proposal.
|
||||
|
||||
### 4. Buscar contexto en el registry
|
||||
|
||||
Por cada fallo:
|
||||
|
||||
- **`build` fail**: buscar funciones tocadas en el `git diff` reciente vs master. Si hay funcion modificada que aparece en `uses_functions` del app.md → posible culpable.
|
||||
- **`smoke`/`health` fail**: buscar service/handler relevante. `sqlite3 registry.db "SELECT id FROM functions WHERE id IN (SELECT id FROM functions_fts WHERE functions_fts MATCH 'description:health OR description:smoke OR name:server');"`.
|
||||
- **`tests` fail**: parsear `stderr` para extraer nombre del test fallido. Buscar la funcion testeada en registry.
|
||||
- **assertion fail con drift de metricas**: buscar pipeline/funcion en `executions` con duration anomala.
|
||||
|
||||
### 5. Detectar duplicados
|
||||
|
||||
Antes de crear proposal, verificar que no haya una identica abierta:
|
||||
|
||||
```bash
|
||||
sqlite3 /home/lucas/fn_registry/registry.db "
|
||||
SELECT id FROM proposals
|
||||
WHERE status = 'pending'
|
||||
AND target_id = '$APP_ID'
|
||||
AND title LIKE 'e2e fail: $APP_ID::$CHECK_ID%'
|
||||
ORDER BY created_at DESC LIMIT 1;"
|
||||
```
|
||||
|
||||
Si existe → NO crear duplicada. Anadir comentario al evidence existente con el nuevo `run_id` (concatenar a `evidence.runs[]`).
|
||||
|
||||
### 6. Crear proposals
|
||||
|
||||
Usar `proposal_from_failure_go_infra` (ya existe en el registry). Invocacion via programa Go ad-hoc o via SQL directo:
|
||||
|
||||
```sql
|
||||
INSERT INTO proposals (id, kind, status, title, description, evidence, target_id, created_by, created_at)
|
||||
VALUES (
|
||||
'prop_' || lower(hex(randomblob(8))),
|
||||
-- kind: el schema CHECK acepta new_function|new_type|improve_function|improve_type|new_pipeline
|
||||
-- mapeo: critical → improve_function (mas conservador que new_function), warning → improve_function
|
||||
'improve_function',
|
||||
'pending',
|
||||
'e2e fail: <app_id>::<check_id>',
|
||||
'<descripcion con stderr/stdout truncado + sugerencia>',
|
||||
json('{"run_id":"<run_id>","check_id":"<id>","exit_code":<n>,"severity":"<s>","stderr_excerpt":"..."}'),
|
||||
'<app_id>',
|
||||
'reactive_loop',
|
||||
strftime('%Y-%m-%dT%H:%M:%fZ','now')
|
||||
);
|
||||
```
|
||||
|
||||
Sugerencia generica en `description` (NO codigo concreto, solo direccion):
|
||||
|
||||
| Patron de fallo | Sugerencia |
|
||||
|---|---|
|
||||
| `build` fail con error de compilacion | "Revisar funcion modificada recientemente: <id>. Posible firma rota o import circular." |
|
||||
| `smoke` health timeout | "Servicio no levanta. Verificar puerto en uso, logs de arranque, dependencia de BD." |
|
||||
| `tests` fail | "Test <name> regresa fail. Diferencia esperada vs actual en stderr. Posible cambio de comportamiento en <funcion sospechosa>." |
|
||||
| `assertion` drift de metricas | "Drift de p50 +X% sobre baseline. Posible regresion de performance en <pipeline_id>." |
|
||||
| `enricher` fail con red | "Red flaky o servicio externo caido. Considerar marcar severity:warning si no es bloqueante." |
|
||||
|
||||
### 7. Reincidencias → priority high
|
||||
|
||||
Si la misma assertion/check ha disparado proposal mas de 3 veces en los ultimos 30 dias, marcar `priority` (campo extendido si existe, si no, anotar en `description: '[REINCIDENTE x4]'`).
|
||||
|
||||
```bash
|
||||
sqlite3 /home/lucas/fn_registry/registry.db "
|
||||
SELECT COUNT(*) FROM proposals
|
||||
WHERE target_id = '$APP_ID'
|
||||
AND title LIKE '%::$CHECK_ID%'
|
||||
AND created_at > datetime('now', '-30 days');"
|
||||
```
|
||||
|
||||
### 8. Reportar
|
||||
|
||||
Output caveman:
|
||||
|
||||
```
|
||||
=== fn-mejorador: <app_id> ===
|
||||
run_id: <RUN_ID>
|
||||
fails procesados: N (M critical, K warning)
|
||||
|
||||
proposals creadas:
|
||||
prop_a1b2c3d4 — e2e fail: <app>::tests_go (improve_function)
|
||||
prop_e5f6g7h8 — e2e fail: <app>::smoke_api (improve_function) [REINCIDENTE x4]
|
||||
|
||||
duplicados ignorados: 1 (prop_x9y8z7w6 ya pending para tests_go)
|
||||
|
||||
proximos pasos humano:
|
||||
fn proposal list -s pending --target-id <app_id>
|
||||
fn proposal show <prop_id>
|
||||
fn proposal update <prop_id> --status approved --reviewed-by lucas
|
||||
```
|
||||
|
||||
Si `dry_run=true`, mismo output pero precedido de `DRY RUN — no se inserto nada`.
|
||||
|
||||
---
|
||||
|
||||
## Reglas de comportamiento
|
||||
|
||||
1. **Cero side-effects fuera de `proposals`**. Solo `INSERT` en esa tabla.
|
||||
2. **Evidencia obligatoria**. Cada proposal lleva `evidence.run_id`. Sin evidencia no se crea.
|
||||
3. **Sugerencias humanas, no codigo**. La `description` apunta direcciones, no parchea. Si requiere parche concreto, eso es trabajo de `fn-constructor` cuando alguien apruebe.
|
||||
4. **Dedup agresivo**. No spamear con proposals duplicadas. Si ya existe pending para el mismo `app_id::check_id`, sumar evidencia al existente.
|
||||
5. **Truncar stderr/stdout**. Excerpt max 500 chars en `description` y 200 chars en `evidence.stderr_excerpt`. Logs completos quedan en `e2e_runs.summary_json`.
|
||||
6. **No interpretar**. NO afirmar "el bug esta en linea X". Solo: "fail en check Y, evidencia Z, posible direccion W". Mantener tono de hipotesis, no de diagnostico.
|
||||
7. **Caveman en stdout**. Listas, fragmentos, sin filler.
|
||||
|
||||
---
|
||||
|
||||
## Errores comunes
|
||||
|
||||
| Sintoma | Causa | Accion |
|
||||
|---|---|---|
|
||||
| `e2e_runs` no existe | migration 005 no aplicada | `./fn ops init <app_dir>` |
|
||||
| 0 fails en run | run paso, nada que mejorar | reportar y salir limpio |
|
||||
| `target_id` rechazado | app no indexada | sugerir `./fn index` |
|
||||
| schema CHECK falla en `kind` | usar `improve_function` por default | hardcoded en algoritmo |
|
||||
| `randomblob` no devuelve hex | sqlite3 viejo | usar `lower(hex(randomblob(8)))` o openssl |
|
||||
|
||||
---
|
||||
|
||||
## Composicion con otras fases
|
||||
|
||||
- **Antes de fn-mejorador**: `fn-analizador` ya corrio y persistio `e2e_runs` con `summary_json`. Sin esa fila, mejorador no tiene insumo.
|
||||
- **Despues de fn-mejorador**: humano revisa `fn proposal list -s pending`. O bucle autonomo (issue 0069) filtra y auto-aplica las seguras.
|
||||
- **NO orquestar fases tu mismo**. Si te dicen "valida la app", redirige a `/validate-app` que orquesta la cadena. Tu solo haces fase 5 cuando te invocan explicitamente.
|
||||
|
||||
---
|
||||
|
||||
## Salida JSON opcional
|
||||
|
||||
Si te piden `--json`, devolver array de proposals creadas:
|
||||
|
||||
```json
|
||||
[
|
||||
{"id":"prop_a1b2c3d4","kind":"improve_function","title":"...","target_id":"<app>","run_id":"<run>","check_id":"tests_go"},
|
||||
...
|
||||
]
|
||||
```
|
||||
|
||||
Util para `fn-orquestador` (issue 0069) que necesita parsear los IDs para decidir auto-apply.
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
name: fn-recopilador
|
||||
description: "Agente recopilador (Fase 3) del ciclo reactivo. Audita operations.db de apps, valida integridad de datos operativos (entities, relations, executions, assertions, logs), y verifica que la estructura del ejecutor esta correcta."
|
||||
description: "Agente recopilador (Fase 3) del ciclo reactivo. Audita operations.db de apps, valida integridad de datos operativos (entities, relations, executions, assertions, logs), y verifica que la estructura del ejecutor esta correcta. Modo extra `design-e2e <app_id>`: propone bloque `e2e_checks` para que la fase 4 (fn-analizador) pueda validar la app sin iteracion humana."
|
||||
model: sonnet
|
||||
tools: Read, Write, Bash, Glob, Grep, Edit
|
||||
---
|
||||
@@ -491,6 +491,158 @@ Acciones sugeridas:
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Modo `design-e2e <app_id>` — disenar contrato de validacion
|
||||
|
||||
Ademas de auditar, el recopilador puede **proponer el bloque `e2e_checks`** del `app.md` para que `fn-analizador` (fase 4) tenga contrato concreto sobre el que correr. Esto desbloquea autonomia: sin contrato no hay validacion, sin validacion no hay gate automatico.
|
||||
|
||||
Ver regla `.claude/rules/e2e_validation.md` y issue 0068.
|
||||
|
||||
### Cuando usarlo
|
||||
|
||||
- App nueva sin `e2e_checks` declarado.
|
||||
- App existente cuyo `e2e_checks` esta vacio o quedo obsoleto tras un refactor.
|
||||
- Peticion explicita: `design-e2e apps/<app>` o `design-e2e projects/<p>/apps/<a>`.
|
||||
|
||||
### Algoritmo
|
||||
|
||||
1. **Leer `app.md`** del app objetivo. Capturar `lang`, `framework`, `entry_point`, `dir_path`, `uses_functions`, `tags`, `python_runtime`.
|
||||
2. **Inspeccionar el directorio** del app:
|
||||
- Presencia de `frontend/` con `package.json` → frontend Vite/React, hace falta `pnpm build`.
|
||||
- Presencia de `CMakeLists.txt` → app C++, build con cmake, sugerir `--self-test`.
|
||||
- Presencia de `go.mod` o `*.go` → build con `go build`.
|
||||
- Presencia de `pyproject.toml` o `requirements.txt` → Python, build = import test.
|
||||
- Presencia de `tests/` (pytest) o `*_test.go` (Go) → check de tests dedicado.
|
||||
- Presencia de `migrations/` → check de migraciones aplicadas.
|
||||
3. **Inspeccionar `operations.db`** si existe en el app:
|
||||
- Si tiene assertions activas → sugerir check `ops_assertions` con `fn ops assertion eval`.
|
||||
- Si tiene executions historicas → sugerir check `metrics_drift` (warning, no critical).
|
||||
- Siempre sugerir `ops_audit: ref: fn-recopilador:<dir_path>`.
|
||||
4. **Detectar puerto/health endpoint** si es service:
|
||||
- Tag `service` en `app.md` → smoke check con `&` + `health` URL.
|
||||
- Buscar en codigo (`main.go`, `main.cpp`, etc.) literales `:8...`, `:9...`, o flags `--port`.
|
||||
- Sugerir puertos efimeros altos (`8195`, `9195`, ...) y BDs en `/tmp/<app>_e2e.db`.
|
||||
5. **Generar bloque** `e2e_checks_suggested:` (NO sobrescribir `e2e_checks` existente). Imprimirlo con comentarios que expliquen cada check.
|
||||
6. **NO escribir directamente al `app.md`**. Devolver el bloque al agente principal / humano para revision y commit. Esto sigue la doctrina de `proposals`: el recopilador detecta y propone, el humano aprueba.
|
||||
|
||||
### Plantillas por stack (a adaptar segun la app)
|
||||
|
||||
#### Go service (kanban-like)
|
||||
|
||||
```yaml
|
||||
e2e_checks_suggested:
|
||||
- id: build_frontend
|
||||
cmd: "cd frontend && pnpm install --frozen-lockfile && pnpm build"
|
||||
timeout_s: 180
|
||||
- id: build_backend
|
||||
cmd: "CGO_ENABLED=1 go build -tags fts5 -o <name> ."
|
||||
timeout_s: 120
|
||||
- id: migrations
|
||||
cmd: "rm -f /tmp/<name>_e2e.db && ./<name> --port 0 --db /tmp/<name>_e2e.db --migrate-only"
|
||||
timeout_s: 15
|
||||
- id: smoke
|
||||
cmd: "./<name> --port <PORT> --db /tmp/<name>_e2e.db &"
|
||||
health: "http://127.0.0.1:<PORT>/api/board"
|
||||
timeout_s: 10
|
||||
- id: tests
|
||||
cmd: "go test -tags fts5 -count=1 ./..."
|
||||
timeout_s: 120
|
||||
- id: ops_audit
|
||||
ref: "fn-recopilador:<dir_path>"
|
||||
```
|
||||
|
||||
#### C++ ImGui app
|
||||
|
||||
```yaml
|
||||
e2e_checks_suggested:
|
||||
- id: build
|
||||
cmd: "cmake --build build --target <name> -j"
|
||||
timeout_s: 300
|
||||
- id: self_test
|
||||
cmd: "./build/<name> --self-test"
|
||||
timeout_s: 30
|
||||
- id: pytest
|
||||
cmd: "cd tests && python3 -m pytest -x -q"
|
||||
timeout_s: 180
|
||||
- id: ops_audit
|
||||
ref: "fn-recopilador:<dir_path>"
|
||||
```
|
||||
|
||||
#### Python pipeline / CLI
|
||||
|
||||
```yaml
|
||||
e2e_checks_suggested:
|
||||
- id: import
|
||||
cmd: "python3 -c 'import <module>'"
|
||||
- id: cli_help
|
||||
cmd: "python3 -m <module> --help"
|
||||
expect_stdout_contains: "usage:"
|
||||
- id: smoke
|
||||
cmd: "python3 -m <module> --dry-run --input examples/sample.json"
|
||||
timeout_s: 60
|
||||
```
|
||||
|
||||
#### Service Go puro (sin frontend, ej. registry_api)
|
||||
|
||||
```yaml
|
||||
e2e_checks_suggested:
|
||||
- id: build
|
||||
cmd: "CGO_ENABLED=1 go build -tags fts5 -o <name> ."
|
||||
- id: smoke
|
||||
cmd: "./<name> --port <PORT> &"
|
||||
health: "http://127.0.0.1:<PORT>/health"
|
||||
timeout_s: 10
|
||||
- id: tests
|
||||
cmd: "go test -count=1 ./..."
|
||||
```
|
||||
|
||||
### Reglas de la sugerencia
|
||||
|
||||
1. **No inventar tests inexistentes**. Si `tests/` no existe, NO sugerir el check `tests`.
|
||||
2. **Health URL real o omitir**. Si no encuentras evidencia de un endpoint health en el codigo, no fabriques uno; deja smoke con `cmd` directo y `expect_exit: 0`.
|
||||
3. **Puerto efimero alto**. Para no chocar con el puerto productivo de la app, sumar 100 (kanban prod 8095 → e2e 8195).
|
||||
4. **`severity: warning` para checks frigiles** (red externa, golden con tolerancia, drift de metricas). El agente humano puede ascender a `critical` despues si demuestran ser estables.
|
||||
5. **Commentar las sugerencias**. Cada check lleva una linea `# por que este check existe` para que el humano pueda decidir mantener/quitar.
|
||||
|
||||
### Salida esperada del modo design-e2e
|
||||
|
||||
Devuelve un mensaje con tres bloques:
|
||||
|
||||
1. **Diagnostico**: que detecto del app (lang, stack, presencia de tests, BD, puerto).
|
||||
2. **Sugerencia**: bloque YAML `e2e_checks_suggested:` listo para copiar.
|
||||
3. **Justificacion**: una tabla `check | razon` explicando cada uno.
|
||||
|
||||
Ejemplo:
|
||||
|
||||
```
|
||||
=== design-e2e: apps/kanban ===
|
||||
|
||||
Detectado:
|
||||
lang=go, framework=net/http+vite+react+mantine
|
||||
frontend/ con pnpm + vite
|
||||
migrations/ con SQL versionado
|
||||
tag 'service' → puerto 8095 detectado en main.go
|
||||
operations.db NO presente (usa kanban.db propia)
|
||||
|
||||
Sugerencia (copiar al app.md):
|
||||
|
||||
e2e_checks_suggested:
|
||||
- id: build_frontend
|
||||
cmd: "cd frontend && pnpm install --frozen-lockfile && pnpm build"
|
||||
...
|
||||
|
||||
Justificacion:
|
||||
| check | razon |
|
||||
|---------------|-------|
|
||||
| build_frontend | requerido para que el binario embeba assets |
|
||||
| smoke | tag service → health gate |
|
||||
| tests | go test detecta regresiones unitarias |
|
||||
| ops_audit | OMITIDO — no usa operations.db |
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Errores comunes a detectar
|
||||
|
||||
1. **operations.db sin migracion 003** → falta tabla `logs` (docker_tui y pipeline_launcher actualmente)
|
||||
|
||||
@@ -0,0 +1,135 @@
|
||||
# /validate-app — Validar end-to-end una app del registry
|
||||
|
||||
Orquesta la cadena `fn-executor → fn-recopilador → fn-analizador → fn-mejorador` (fases 2-5 del bucle reactivo) sobre una app concreta. Devuelve veredicto pass/fail + IDs de proposals creadas si hay fallos.
|
||||
|
||||
## Argumento
|
||||
|
||||
`$ARGUMENTS` — `<app_id>` o `<dir_path>`. Ejemplos:
|
||||
- `kanban_go_tools`
|
||||
- `apps/kanban`
|
||||
- `graph_explorer_cpp_viz`
|
||||
- `projects/osint_graph/apps/graph_explorer`
|
||||
|
||||
Si vacio: detectar app desde `pwd` (si estas dentro de `apps/<X>/` o `projects/*/apps/<X>/`); si no, listar apps con `e2e_checks` declarado y pedir.
|
||||
|
||||
## Pasos
|
||||
|
||||
### 1. Resolver app objetivo
|
||||
|
||||
```bash
|
||||
ROOT=/home/lucas/fn_registry
|
||||
ARG="$ARGUMENTS"
|
||||
|
||||
if [ -z "$ARG" ]; then
|
||||
CWD="$(pwd)"
|
||||
case "$CWD" in
|
||||
"$ROOT"/apps/*|"$ROOT"/projects/*/apps/*)
|
||||
ARG="$(realpath --relative-to="$ROOT" "$CWD")"
|
||||
;;
|
||||
*)
|
||||
sqlite3 "$ROOT/registry.db" "SELECT id, dir_path FROM apps ORDER BY id;"
|
||||
echo "Especifica app_id o dir_path"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Resolver a (id, dir_path)
|
||||
if echo "$ARG" | grep -q "^apps/\|^projects/"; then
|
||||
APP_DIR="$ARG"
|
||||
APP_ID=$(sqlite3 "$ROOT/registry.db" "SELECT id FROM apps WHERE dir_path = '$ARG';")
|
||||
else
|
||||
APP_ID="$ARG"
|
||||
APP_DIR=$(sqlite3 "$ROOT/registry.db" "SELECT dir_path FROM apps WHERE id = '$ARG';")
|
||||
fi
|
||||
|
||||
[ -z "$APP_ID" ] || [ -z "$APP_DIR" ] && { echo "App no encontrada: $ARG"; exit 1; }
|
||||
```
|
||||
|
||||
### 2. Verificar contrato `e2e_checks`
|
||||
|
||||
```bash
|
||||
HAS_CHECKS=$(awk '/^e2e_checks:/,/^[a-z_]+:|^---$/' "$ROOT/$APP_DIR/app.md" | grep -c "^ - id:")
|
||||
|
||||
if [ "$HAS_CHECKS" -eq 0 ]; then
|
||||
echo "App $APP_ID no tiene e2e_checks declarados."
|
||||
echo "Invocar fn-recopilador design-e2e para generar contrato:"
|
||||
echo ""
|
||||
echo " Agent(subagent_type=fn-recopilador, prompt=\"design-e2e $APP_DIR\")"
|
||||
exit 0
|
||||
fi
|
||||
```
|
||||
|
||||
### 3. Fase 3 — RECOPILAR (auditar operations.db)
|
||||
|
||||
Invocar `fn-recopilador` para confirmar que los datos operativos estan integros antes de validar. Si recopilador reporta FAIL critical, NO continuar.
|
||||
|
||||
```
|
||||
Agent(subagent_type=fn-recopilador,
|
||||
prompt="Auditar app $APP_DIR. Reportar OK/WARN/FAIL en formato corto.
|
||||
Si hay FAIL critical, advertirlo claramente. Solo lectura.")
|
||||
```
|
||||
|
||||
Si reporta FAIL critical → abortar con mensaje y no llegar a fn-analizador.
|
||||
|
||||
### 4. Fase 4 — ANALIZAR (correr e2e_checks)
|
||||
|
||||
```
|
||||
Agent(subagent_type=fn-analizador,
|
||||
prompt="Validar end-to-end la app $APP_ID (dir_path: $APP_DIR).
|
||||
Leer e2e_checks del app.md, ejecutar via e2e_run_checks_go_infra,
|
||||
evaluar assertions, calcular drift, persistir en e2e_runs.
|
||||
triggered_by: manual.
|
||||
git_sha: $(git rev-parse --short HEAD 2>/dev/null || echo '')
|
||||
|
||||
Devolver veredicto caveman + run_id.")
|
||||
```
|
||||
|
||||
Capturar `RUN_ID` del output. Capturar `STATUS` (`pass`|`partial`|`fail`).
|
||||
|
||||
### 5. Fase 5 — MEJORAR (proposals si hay fallos)
|
||||
|
||||
Solo si `STATUS != pass`:
|
||||
|
||||
```
|
||||
Agent(subagent_type=fn-mejorador,
|
||||
prompt="App $APP_ID tuvo fallos en run_id $RUN_ID.
|
||||
Leer e2e_runs y summary_json de $APP_DIR/operations.db.
|
||||
Por cada fail critical: crear proposal kind=new_function|improve_function
|
||||
en registry.db con created_by=reactive_loop, evidence con run_id+check_id.
|
||||
Sugerir fix concreto en description.
|
||||
Devolver lista de proposal_ids creados.")
|
||||
```
|
||||
|
||||
Capturar `PROPOSAL_IDS`.
|
||||
|
||||
### 6. Reporte final al usuario
|
||||
|
||||
Tabla resumen:
|
||||
|
||||
```
|
||||
=== /validate-app: $APP_ID ===
|
||||
|
||||
Fase 3 RECOPILAR: ✓ datos operativos integros
|
||||
Fase 4 ANALIZAR: <STATUS> (run_id: <RUN_ID>)
|
||||
<P>/<T> checks pass, <W> warn, <F> fail
|
||||
Fase 5 MEJORAR: <N> proposals creadas: <PROPOSAL_IDS>
|
||||
|
||||
Detalle por check:
|
||||
build_frontend ✓ 42s
|
||||
build_backend ✓ 18s
|
||||
smoke_api ✓ 1.2s
|
||||
tests_go ✗ 12s — 3/45 fails
|
||||
|
||||
Siguientes pasos:
|
||||
- Revisar proposals: fn proposal list -s pending
|
||||
- Ver run completo: sqlite3 $APP_DIR/operations.db "SELECT * FROM e2e_runs WHERE id='<RUN_ID>'"
|
||||
```
|
||||
|
||||
## Notas
|
||||
|
||||
- **fn-mejorador no existe todavia** (paso 6 del issue 0068). Mientras tanto, si STATUS != pass, solo imprime el detalle del fallo y sugerir crear proposal manual.
|
||||
- Si un agente subagente devuelve respuesta ambigua (no extrae RUN_ID claramente), pedir clarificacion al usuario antes de continuar.
|
||||
- Para apps sin `operations.db` (ej. kanban usa `kanban.db`), `e2e_runs` se persiste en `/tmp/<app>_e2e_runs.db` con la misma migracion 005.
|
||||
- Caveman OK en stdout salvo en mensajes de error donde claridad supera brevedad.
|
||||
- Tras correr la cadena, NO commitear nada automaticamente. La decision de mergear es del humano.
|
||||
@@ -29,3 +29,4 @@ Reglas operativas del proyecto. Cada archivo es una regla independiente.
|
||||
| 23 | [fn_doctor.md](fn_doctor.md) | `fn doctor`: diagnostico read-only de artefactos, services, sync drift, uses_functions, unused — wrappers de funciones del registry |
|
||||
| 24 | [feature_flags.md](feature_flags.md) | TBD: feature flags para mergear codigo incompleto sin romper master. Patrones por stack (Go/TS/Bash/Py), branch-by-abstraction, anti-patrones |
|
||||
| 25 | [db_migrations.md](db_migrations.md) | Migraciones SQLite obligatorias para cualquier cambio de schema. Aditivas, idempotentes, archivos numerados. Nunca borrar .db ni modificar migraciones existentes |
|
||||
| 26 | [e2e_validation.md](e2e_validation.md) | Contrato `e2e_checks` en `app.md` consumido por fn-analizador (fase 4 del bucle reactivo). Issue 0068 |
|
||||
|
||||
@@ -192,3 +192,20 @@ Si la app tiene componentes que se quieren proteger contra regresiones visuales,
|
||||
- `about` info: nombre, version (semver), descripcion 1 frase.
|
||||
- Persistencia: `<app>.db` SQLite junto al exe; nunca tocar `registry.db` ni `operations.db` salvo lectura.
|
||||
- Modo CLI: si la app acepta args, documentarlos en el `app.md` con ejemplos.
|
||||
|
||||
### 10. Layouts persistentes (default)
|
||||
|
||||
`fn::run_app` provee menu Layouts (Save current as.../Apply/Delete/Reset) sin
|
||||
codigo. Crea `<exe_dir>/local_files/layouts.db` (tabla `imgui_layouts`) y
|
||||
persiste el `imgui.ini` serializado por nombre.
|
||||
|
||||
- App nueva: nada que tocar — Layouts viene activo.
|
||||
- App quiere personalizar `on_reset` (ej. re-mostrar paneles especificos como
|
||||
`shaders_lab`): abre su propio `LayoutStorage`, llama
|
||||
`layout_storage_make_callbacks`, override `on_reset`, y pasa
|
||||
`cfg.layouts_cb = &cb`. Cuando se pasa `layouts_cb`, el auto-storage se
|
||||
desactiva y la app es responsable de `layout_storage_apply_pending` al
|
||||
inicio de su `render`.
|
||||
- App headless / capture mode: `cfg.auto_layouts = false`.
|
||||
- Cambiar nombre del archivo: `cfg.auto_layouts_db = "<algo>.db"` (relativo a
|
||||
`local_files/`).
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
## Validacion end-to-end de apps (bucle reactivo, fase 4)
|
||||
|
||||
**Contrato obligatorio para apps que vayan a master con gate automatico**: declarar `e2e_checks` en su `app.md`. Sin contrato, `fn-analizador` no puede validar y la app cae al modo "manual": el humano sigue iterando.
|
||||
|
||||
Ver tambien: `apps_tbd.md`, `feature_flags.md`, issue 0068.
|
||||
|
||||
### Por que
|
||||
|
||||
El bucle reactivo del registry tiene 5 fases. Las 3 primeras (`fn-constructor`, `fn-executor`, `fn-recopilador`) cubren CONSTRUIR/EJECUTAR/RECOPILAR. La fase 4 (ANALIZAR) y la 5 (MEJORAR) no funcionan sin un contrato explicito de "como sabe el agente que esta app esta sana". Ese contrato es `e2e_checks`.
|
||||
|
||||
### Donde vive
|
||||
|
||||
En el frontmatter de cada `app.md`, lista `e2e_checks`. Convencion: `id` unico por check, ejecucion en orden declarado, falla = stop o continue segun severidad (TBD por implementar).
|
||||
|
||||
### Tipos de check
|
||||
|
||||
| Campo | Que hace |
|
||||
|---|---|
|
||||
| `id` | Identificador unico del check dentro de la app (`build`, `smoke`, `tests_unit`, ...) |
|
||||
| `cmd` | Comando shell. Exit 0 = pass salvo override de `expect_exit`. |
|
||||
| `health` | URL HTTP. Hace GET, espera 200, util tras un `cmd` que arranca un servicio en background (con `&`). |
|
||||
| `ref` | Referencia a otro agente / funcion del registry (ej. `fn-recopilador:apps/X`, `fn-doctor:artefacts`). |
|
||||
| `timeout_s` | Timeout en segundos. Default 60. |
|
||||
| `expect_exit` | Codigo de salida esperado (default 0). |
|
||||
| `expect_stdout_contains` | Substring que debe aparecer en stdout. |
|
||||
| `expect_stdout_json` | JSONPath o key=value que debe satisfacer la salida. |
|
||||
| `severity` | `critical` (default) o `warning`. Critical = bloquea merge; warning = registra y sigue. |
|
||||
|
||||
### Patrones por stack
|
||||
|
||||
#### Go service con frontend embebido
|
||||
|
||||
```yaml
|
||||
e2e_checks:
|
||||
- id: build_frontend
|
||||
cmd: "cd frontend && pnpm install --frozen-lockfile && pnpm build"
|
||||
timeout_s: 180
|
||||
- id: build_backend
|
||||
cmd: "CGO_ENABLED=1 go build -tags fts5 -o myapp ."
|
||||
- id: smoke
|
||||
cmd: "./myapp --port 8200 --db /tmp/myapp_e2e.db &"
|
||||
health: "http://127.0.0.1:8200/api/health"
|
||||
- id: tests
|
||||
cmd: "go test -tags fts5 -count=1 ./..."
|
||||
```
|
||||
|
||||
#### C++ ImGui app
|
||||
|
||||
```yaml
|
||||
e2e_checks:
|
||||
- id: build
|
||||
cmd: "cmake --build build --target myapp -j"
|
||||
timeout_s: 300
|
||||
- id: self_test
|
||||
cmd: "./build/myapp --self-test"
|
||||
timeout_s: 30
|
||||
- id: pytest
|
||||
cmd: "cd tests && python3 -m pytest -x -q"
|
||||
```
|
||||
|
||||
Apps C++ deben implementar `--self-test` que arranca, verifica subsistemas (GL loader, fonts, DBs locales), y sale con codigo 0/1.
|
||||
|
||||
#### Python pipeline / CLI
|
||||
|
||||
```yaml
|
||||
e2e_checks:
|
||||
- id: import
|
||||
cmd: "python3 -c 'import myapp'"
|
||||
- id: cli_help
|
||||
cmd: "python3 -m myapp --help"
|
||||
expect_stdout_contains: "usage:"
|
||||
- id: dry_run
|
||||
cmd: "python3 -m myapp --dry-run --input examples/sample.json"
|
||||
```
|
||||
|
||||
#### App con operations.db
|
||||
|
||||
Anadir siempre:
|
||||
|
||||
```yaml
|
||||
- id: ops_audit
|
||||
ref: "fn-recopilador:apps/myapp"
|
||||
```
|
||||
|
||||
Esto invoca al recopilador en modo audit sobre `apps/myapp/operations.db`.
|
||||
|
||||
### Reglas
|
||||
|
||||
1. **Idempotente**: cada check debe poderse correr N veces sin efectos secundarios. Usar BDs en `/tmp/`, puertos altos, `--port 0` cuando se pueda.
|
||||
2. **Sin credenciales reales**: ningun check toca produccion ni servicios externos sensibles. Si necesita HTTP de prueba, usar `httpbin.org` o un mock local.
|
||||
3. **Tiempo acotado**: cada check declara `timeout_s`. Suma total de la app < 10 min como objetivo razonable.
|
||||
4. **Determinista**: si el check depende de red flaky, marcalo `severity: warning` o usalo solo como diagnostico, no como gate.
|
||||
5. **Cleanup implicito**: si el check arranca un proceso en background (`&`), debe morir al final. `fn-analizador` mata el grupo de procesos al terminar la suite.
|
||||
|
||||
### Como diseñar `e2e_checks` para una app existente
|
||||
|
||||
`fn-recopilador` tiene un modo `design-e2e <app_id>` que:
|
||||
|
||||
1. Inspecciona `app.md` (lang, framework, entry_point, uses_functions).
|
||||
2. Revisa estructura del directorio (presencia de `tests/`, `frontend/`, `Makefile`, `CMakeLists.txt`, etc.).
|
||||
3. Audita `operations.db` (si existe) para sugerir `ops_audit`.
|
||||
4. Devuelve bloque `e2e_checks_suggested:` listo para copiar al `app.md` tras revision humana.
|
||||
|
||||
Comando indicativo:
|
||||
```
|
||||
Agent(subagent_type="fn-recopilador",
|
||||
prompt="design-e2e apps/<app>")
|
||||
```
|
||||
|
||||
El recopilador NO escribe directo al `app.md`; deja la propuesta para que el humano apruebe (similar a `proposals`).
|
||||
|
||||
### Adopcion gradual
|
||||
|
||||
- Apps SIN `e2e_checks` declarado: `fn doctor` muestra warning, no bloquea nada.
|
||||
- Apps CON `e2e_checks`: `fn-analizador` corre la suite. Si critical falla → `fn-mejorador` crea proposal. Gate opcional en `/git-push`.
|
||||
- Pilotos iniciales: `apps/kanban`, `projects/osint_graph/apps/graph_explorer`. Resto de apps van migrando segun necesidad.
|
||||
|
||||
### Anti-patrones
|
||||
|
||||
| Anti-patron | Por que es malo |
|
||||
|---|---|
|
||||
| `cmd: "make test"` con make-target opaco | Ilegible. El check debe ser ejecutable directo y auditable. |
|
||||
| Check que tarda > 5 min sin razon (smoke pesado) | Bloquea iteracion. Mover a CI nocturno con tag `slow`. |
|
||||
| Smoke que toca produccion | Riesgo. Smoke usa BD efimera, puertos altos, mocks. |
|
||||
| `expect_stdout_contains: ""` | Vacio = siempre pass. No es un check. |
|
||||
| Anidar checks (uno depende de side-effects de otro sin declararlo) | Frigil. Cada check arranca lo que necesita. |
|
||||
| Usar `e2e_checks` como sustituto de tests unitarios | Son cosas distintas. Unit tests viven en `*_test.go`/`pytest`. e2e valida que el sistema arranque y haga su trabajo. |
|
||||
|
||||
### Tabla `e2e_runs` en operations.db
|
||||
|
||||
Cada corrida de `fn-analizador` se persiste:
|
||||
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS e2e_runs (
|
||||
id TEXT PRIMARY KEY,
|
||||
app_id TEXT NOT NULL,
|
||||
started_at INTEGER NOT NULL,
|
||||
finished_at INTEGER,
|
||||
status TEXT NOT NULL, -- pass|fail|partial
|
||||
checks_total INTEGER NOT NULL,
|
||||
checks_pass INTEGER NOT NULL,
|
||||
checks_fail INTEGER NOT NULL,
|
||||
summary_json TEXT NOT NULL
|
||||
);
|
||||
```
|
||||
|
||||
Migracion: `fn_operations/migrations/006_e2e_runs.sql` (issue 0068, paso 3).
|
||||
|
||||
### Output canonico de fn-analizador
|
||||
|
||||
Tabla caveman, una linea por check:
|
||||
|
||||
```
|
||||
build ✓ 42s
|
||||
smoke ✓ 0.8s
|
||||
ops_audit ✓
|
||||
tests ✗ 12s exit 1, 3/45 failures
|
||||
assertion:R1 ✗ warning duration drift +47% vs p50
|
||||
golden:home ✓
|
||||
```
|
||||
|
||||
Rojo cuando `severity: critical` y status fail. Esto es lo que el agente principal lee y reenvia al humano.
|
||||
Executable
+28
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env bash
|
||||
# PreToolUse hook: NO bloquea. Inyecta recordatorio cuando ve sqlite3 sobre registry.db
|
||||
# para que el modelo prefiera el MCP `registry` la proxima vez.
|
||||
|
||||
input="$(cat)"
|
||||
cmd="$(printf '%s' "$input" | jq -r '.tool_input.command // ""')"
|
||||
|
||||
# Solo nos importa registry.db (NO operations.db, NO otros .db).
|
||||
if ! printf '%s' "$cmd" | grep -Eq 'sqlite3[^|]*\bregistry\.db\b'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Casos legitimos donde el MCP no aplica: introspeccion de schema, agregaciones, JOINs.
|
||||
if printf '%s' "$cmd" | grep -Eq '(\.schema|\.tables|PRAGMA[[:space:]]+(table_info|index_list))'; then
|
||||
exit 0
|
||||
fi
|
||||
if printf '%s' "$cmd" | grep -Eqi '(COUNT\(|GROUP[[:space:]]+BY|JOIN[[:space:]])'; then
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Caso a redirigir: emitir nota como additionalContext y dejar pasar el comando.
|
||||
jq -n '{
|
||||
hookSpecificOutput: {
|
||||
hookEventName: "PreToolUse",
|
||||
additionalContext: "Aviso: sqlite3 directo sobre registry.db detectado. Para futuras consultas usa el MCP registry (mcp__registry__fn_search / fn_show / fn_code / fn_uses / fn_list_domains). Fallback a sqlite3 SOLO para .schema, PRAGMA, COUNT/GROUP BY, JOINs custom."
|
||||
}
|
||||
}'
|
||||
exit 0
|
||||
Reference in New Issue
Block a user