---
name: benchmark-memoria
description: >
  Benchmark de retrieval para `hooks/lib/memory-search` que mide R@5, R@10,
  MRR y nDCG@10 contra un dataset de queries con respuestas conocidas
  (gold_ids). Útil para detectar regresión de calidad de búsqueda al cambiar
  RRF weights, scoring o fuentes. Cargar cuando se modifique
  `memory-search.js`, `rrf-fusion.js` o `session-fts.js` para verificar que
  no degrada retrieval. NO cargar para uso operacional ni para evaluar
  outputs de agentes (eso es `eval-framework`).
version: "1.0.0"
herramientasPermitidas: [Read, Bash]
exclusiones:
  - "No cargar para evaluar outputs de agentes (aprendizajes, observaciones, resúmenes); eso es `eval-framework`."
  - "No cargar como gate de release sin un dataset ≥30 entries con status='real' — las métricas con dataset placeholder no son estadísticamente significativas."
  - "No cargar para benchmark de performance (latencia, throughput); eso es `performance-baseline`."
  - "No cargar si el repo no tiene `.planning/APRENDIZAJES.md` ni sesiones — sin contenido no hay nada que recuperar."
evolvable: true  # default para skill estandar
---
# Benchmark de Memoria — LongMemEval-S adaptado a SWL

## Cuándo cargar esta skill

- Tras modificar `hooks/lib/memory-search.js`, `scripts/lib/rrf-fusion.js`,
  pesos de fusión o algoritmo de scoring de relevancia.
- Tras agregar fuentes de búsqueda nuevas (ej. evals, instintos globales).
- Antes de un release que toque la capa de memoria, para confirmar que no
  hay regresión en R@5.
- Como parte opcional de `/swl:status salud` cuando `SWL_BENCHMARK_MEMORIA=1`.

---

## Componentes del benchmark

| Módulo | Propósito |
|---|---|
| `scripts/lib/benchmark-metrics.js` | Funciones puras: `recallAt`, `precisionAt`, `mrr`, `ndcgAt`, `dcg`, `calcularMetricas`, `promediar`. |
| `scripts/lib/longmemeval-runner.js` | Adapter que ejecuta queries contra `memory-search` y compara con gold. |
| `scripts/benchmark-memoria.js` | CLI runner principal. |
| `.planning/benchmark/dataset.jsonl` | Dataset (placeholder por defecto, debe expandirse). |

---

## Estado del dataset (CRÍTICO leer antes de usar)

El dataset por defecto en `.planning/benchmark/dataset.jsonl` es **placeholder**
con 10 entries marcadas explícitamente como `"status": "placeholder"`. Las
métricas calculadas con este dataset son **indicativas, no estadísticamente
significativas**.

### Limitaciones del dataset placeholder

1. **IDs volátiles**: los `gold_ids` referencian `apr-N` (índice de entrada en
   `APRENDIZAJES.md`). Si se agregan/borran entradas, los índices cambian y
   el dataset queda desincronizado. Para dataset real considerar IDs estables
   (sesiones tienen timestamp; instintos tienen `id` propio).
2. **N=10 es ruido estadístico**: para que R@5=80% sea significativo
   estadísticamente (vs 70% baseline), se requieren al menos 30 queries
   con N=10 random. Por debajo, las métricas reflejan suerte.
3. **Cobertura limitada**: el placeholder cubre solo categorías técnicas
   (patrones, gotchas, decisiones). Falta cobertura de:
   - Bug fixes históricos
   - Workflow questions ("qué hicimos antes de X")
   - Cross-session ("cuándo se decidió Y")
   - Negative queries (preguntas cuya respuesta es "no aplica")

### Cómo expandir a dataset real

```bash
# 1. Identifica una pregunta real de uso
QUERY="qué hicimos sobre force push a main protegida"

# 2. Ejecuta búsqueda y anota top-5 IDs
node -e "console.log(require('./hooks/lib/memory-search').search('.', '$QUERY').slice(0, 5).map(r => r.id + ' / ' + r.titulo).join('\n'))"

# 3. Verifica manualmente qué IDs son CORRECTOS (revisión humana,
#    no se inventa). Solo esos van en gold_ids.

# 4. Agrega la entry al dataset:
cat >> .planning/benchmark/dataset.jsonl << 'EOF'
{"question_id": "q-real-001", "question": "qué hicimos sobre force push a main protegida", "gold_ids": ["apr-313"], "category": "decision", "status": "real"}
EOF
```

Repetir hasta tener ≥30 entries con `status: "real"`. Solo entonces el
benchmark es gate de release.

---

## Uso

### CLI básico

```bash
# Ejecutar benchmark con dataset por defecto
node scripts/benchmark-memoria.js

# Output esperado:
#   Recall @ 5:     85.0%
#   Recall @ 10:    92.0%
#   MRR:            0.741
#   nDCG @ 10:      0.812
#   Precision @ 5:  41.3%
```

### Opciones

```bash
# Dataset alternativo
node scripts/benchmark-memoria.js --dataset .planning/benchmark/custom.jsonl

# Top-k personalizado (default 20)
node scripts/benchmark-memoria.js --limit 30

# Output JSON (para scripts)
node scripts/benchmark-memoria.js --json

# Detalle por query (útil para debugging)
node scripts/benchmark-memoria.js --verbose
```

### Tracking histórico opcional

Si se setea `SWL_BENCHMARK_PERSIST=1`, el benchmark escribe el resumen
agregado a `.planning/evolution/benchmark-memoria.jsonl` (append-only)
para detectar regresión entre releases:

```bash
SWL_BENCHMARK_PERSIST=1 node scripts/benchmark-memoria.js
```

Comparar entre releases:
```bash
tail -5 .planning/evolution/benchmark-memoria.jsonl | jq -c '{ts: .timestamp, r5: .promedio.recall_at_5}'
```

---

## Métricas explicadas

| Métrica | Significado | Rango |
|---|---|---|
| **Recall @ k** | ¿El sistema recuperó al menos un gold ID en los primeros k? | 0 o 1 por query, promediado en [0, 1] |
| **Precision @ k** | ¿Qué porcentaje de los primeros k son gold? | [0, 1] |
| **MRR** | 1 / posición del primer gold encontrado | [0, 1] — alto = gold cerca del top |
| **nDCG @ k** | DCG normalizado: penaliza gold en posiciones bajas | [0, 1] — mide ranking quality |

**Interpretación**:
- R@5 alto + MRR bajo → encuentra gold pero no en posición 1.
- R@5 bajo + R@10 alto → necesita expandir top-k para alcanzar.
- nDCG@10 alto → ranking respeta orden de relevancia.

---

## Anti-patrones (qué NO hacer)

- **Usar el dataset placeholder como gate de CI**: no significativo, da
  falsa sensación de seguridad. Marcar el job como informativo solamente
  hasta tener dataset real ≥30.
- **Inventar gold_ids "que se ven correctos"**: el dataset SOLO sirve si
  los gold_ids son verificados manualmente como correctos por un humano
  con conocimiento del proyecto.
- **Optimizar el algoritmo solo para que las métricas suban**: si el
  dataset es placeholder, "mejorar" R@5 puede ser overfitting al
  placeholder. Dataset real primero, optimización después.
- **Borrar entries que dan métricas bajas**: una query con R@5=0 puede
  estar revelando un bug real del sistema de búsqueda, no un problema
  del dataset. Debug antes de borrar.

---

## Gotchas / Errores comunes no obvios

- **Dataset JSONL acepta comentarios `//`**: las líneas que empiezan con
  `//` son ignoradas. Útil para documentar inline. NO es JSON estándar
  pero el parser de `longmemeval-runner` lo respeta.
- **Si la query no matchea ningún token mínimo (<= 3 chars), `search()`
  devuelve `[]`**: las stop words están filtradas en
  `hooks/lib/memory-search.js`. Asegurar que cada query tenga ≥2
  términos significativos.
- **Los `gold_ids` deben usar el formato `apr-N`/`ses-YYYYMMDD-HHmm`/etc.
  EXACTO** que devuelve `memory-search`. Si se anota `apr-14` cuando el
  search devuelve `apr-014`, el match falla silenciosamente.
- **El benchmark mide `memory-search` específicamente, no toda la memoria
  SWL**: instintos globales no entran si solo se buscan locales; sesiones
  archivadas no entran. Documentar el scope de cada query.
