---
name: eval-framework
description: >
  Eval framework para validar y puntuar outputs estructurados de SWL
  (aprendizajes, instintos, observaciones, resúmenes, contextos). Cargar
  cuando un agente produzca un output estructurado y se quiera medir su
  calidad antes de persistir, o cuando se audite la calidad histórica de
  funciones críticas (extractor-de-aprendizajes, perfilador-usuario,
  consolidador, planificador).
version: "1.0.0"
herramientasPermitidas: [Read, Bash]
exclusiones:
  - "No cargar para validación de input de usuario o request HTTP — eso es validación de boundary, usar Pydantic/Zod en el endpoint."
  - "No cargar para auditoría de seguridad — usar `revisor-seguridad-swl` y `escaneo-secretos`."
  - "No cargar para tests unitarios de código — usar `tdd-workflow` y Vitest."
  - "No cargar cuando el output no tiene estructura definida (texto libre): el eval framework requiere schemas declarados o quality scorers específicos."
evolvable: true  # default para skill estandar
---
# Eval Framework — Validación + Calidad de Outputs SWL

## Cuándo cargar

- Tras producir un output estructurado (observación, aprendizaje, resumen,
  resultado de búsqueda) cuando se quiera puntuar su calidad antes de
  persistir.
- Para auditar histórico de calidad de una función crítica (ver métricas
  agregadas en `.planning/evolucion/eval-metrics.json`).
- En tests/CI cuando el contrato del output tenga campos obligatorios y
  quality thresholds.
- En loops de auto-corrección donde un output inválido debe regenerarse
  con un prompt más estricto.

---

## Componentes del framework

| Módulo | Propósito |
|---|---|
| `scripts/lib/eval-schemas.js` | Schemas JSON-lite para outputs (observación, resumen, search input, etc.). |
| `scripts/lib/eval-validator.js` | Validador zero-deps de schemas (sin Zod). |
| `scripts/lib/eval-quality.js` | Funciones de scoring: `scoreObservacion`, `scoreResumen`, `scoreAprendizaje`, `scoreInstinto`, `scoreRelevanciaContexto`. |
| `scripts/lib/eval-self-correct.js` | Loop de retry con sufijo estricto cuando validador falla. |
| `scripts/lib/eval-metrics-store.js` | Persistencia: JSONL append-only (`eval-results.jsonl`) + agregado JSON (`eval-metrics.json`). |
| `scripts/run-eval.js` | CLI para evaluar un output desde archivo JSON. |

---

## Uso típico desde agente o test

### Validar output contra schema

```js
const { validar } = require('./scripts/lib/eval-validator');
const { COMPRESS_OUTPUT_SCHEMA } = require('./scripts/lib/eval-schemas');

const observacion = {
  type: 'discovery',
  title: 'Detalle relevante',
  facts: ['fact 1', 'fact 2'],
  narrative: 'Narrativa con suficiente detalle para evaluar',
  concepts: ['c1'],
  files: ['ruta/al/archivo.js'],
  importance: 7,
};

const r = validar(observacion, COMPRESS_OUTPUT_SCHEMA);
if (!r.valid) {
  console.error('Output inválido:', r.errors);
} else {
  // Persistir
}
```

### Puntuar calidad (independiente de validez)

```js
const { scoreObservacion, scoreAprendizaje, scoreInstinto } = require('./scripts/lib/eval-quality');

const score = scoreObservacion(observacion);
// score ∈ [0, 100]. 100 = todos los campos óptimos.
```

### Loop de auto-corrección

```js
const { compresseConReintento } = require('./scripts/lib/eval-self-correct');

const productor = async (sysPrompt, userPrompt) => {
  // Llamar al LLM o ejecutar Skill que produzca el output
  return await llamarClaude(sysPrompt, userPrompt);
};

const validador = (output) => {
  const parsed = JSON.parse(output);
  return validar(parsed, COMPRESS_OUTPUT_SCHEMA);
};

const r = await compresseConReintento({
  productor, validador,
  sysPrompt: '...', userPrompt: '...',
  maxRetries: 2,
});

if (r.valid) {
  // r.output es válido (puede haber requerido r.intentos retries)
}
```

### Persistir métricas para auditoría histórica

```js
const ms = require('./scripts/lib/eval-metrics-store');

ms.registrar(process.cwd(), {
  functionId: 'extractor-de-aprendizajes::scorer',
  latencyMs: 42,
  success: true,
  qualityScore: 85,
});

// Lectura agregada
const m = ms.obtener(process.cwd(), 'extractor-de-aprendizajes::scorer');
// → { totalCalls, successCount, failureCount, avgLatencyMs, avgQualityScore, ... }
```

### CLI desde Bash (para CI o manual)

```bash
# Crear archivo de eval
cat > /tmp/eval.json << 'EOF'
{
  "functionId": "memoria-busqueda::search",
  "schemaName": "MEMORY_SEARCH_RESULT_SCHEMA",
  "qualityScorer": null,
  "expectedKeys": ["id", "tipo", "titulo", "fecha", "relevancia"],
  "output": { ... }
}
EOF

# Ejecutar
node scripts/run-eval.js /tmp/eval.json
# Exit 0 si valid, 1 si inválido. Persiste métricas automáticamente.

# Reconstruir agregado desde JSONL si se corrompe
node scripts/run-eval.js --rebuild-aggregate
```

---

## Schemas disponibles

- `COMPRESS_OUTPUT_SCHEMA` — observación comprimida con type, title, facts,
  narrative, concepts, files, importance.
- `SUMMARY_OUTPUT_SCHEMA` — resumen de sesión con title, narrative,
  keyDecisions, filesModified, concepts.
- `SEARCH_INPUT_SCHEMA` — input de búsqueda { query, limit? }.
- `REMEMBER_INPUT_SCHEMA` — input de "remember" { content, type?,
  concepts?, files? }.
- `EVAL_RESULT_SCHEMA` — resultado de evaluación { valid, errors?,
  qualityScore, latencyMs, functionId, metadata? }.
- `MEMORY_SEARCH_RESULT_SCHEMA` — resultado de `hooks/lib/memory-search`
  con id, tipo, titulo, fecha, relevancia, combinedScore?, confidence?.

Agregar más schemas en `scripts/lib/eval-schemas.js` siguiendo el formato
JSON Schema-lite (subset documentado en `eval-validator.js`).

---

## Quality scorers disponibles

- `scoreObservacion(obs)` — observación con type/title/facts/narrative/concepts/importance.
- `scoreResumen(summary)` — resumen con title/narrative/keyDecisions/filesModified/concepts.
- `scoreAprendizaje(aprendizaje)` — aprendizaje SWL con titulo/contenido/tipo (específico de SWL).
- `scoreInstinto(instinto)` — instinto con pattern/confidence/status/source_*/evidence_count (específico de SWL).
- `scoreRelevanciaContexto(context, project)` — contexto inyectado en sesión.

Cada scorer devuelve un número en [0, 100]. Los criterios están documentados
en cada función en `scripts/lib/eval-quality.js`.

---

## Diferencias con tests unitarios

| Eval framework | Tests unitarios |
|---|---|
| Mide calidad subjetiva sobre estructura | Mide correctitud lógica |
| Score graduado [0, 100] | Pass/fail binario |
| Persiste histórico para auditoría | No persiste (corre en CI) |
| Para outputs estructurados de agentes | Para funciones puras / API |
| Permite retry con prompt estricto | No aplicable |

Los dos son complementarios: tests unitarios para `scoring-instintos.js`,
eval framework para "¿el aprendizaje que extrajo el hook es de calidad?".

---

## Gotchas / Errores comunes no obvios

- **Validez estructural ≠ calidad**: un aprendizaje con `titulo: "X"` y
  `contenido: "trivial"` puede pasar `expectedKeys` pero tener
  `qualityScore: 0`. El framework los distingue. En CI gates, considerar
  ambos: `valid && qualityScore >= 60`.
- **Métricas agregadas se reescriben atómicamente**: si dos procesos
  llaman `registrar()` en paralelo sobre el mismo `functionId`, el último
  gana (race condition en agregado). Para alta concurrencia usar
  `reconstruirAgregado()` periódicamente desde el JSONL append-only.
- **`run-eval.js` exit code**: 0 = valid, 1 = inválido o error de I/O,
  2 = error de uso. No confundir con quality threshold — el CLI no
  bloquea por quality bajo, solo por validez.
- **`compresseConReintento` no reintenta indefinidamente**: respeta
  `maxRetries`. Tras agotar reintentos devuelve el último output con
  `valid: false`. El caller decide qué hacer.
