@saulwade/swl-ses 1.1.4 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +2 -2
- package/README.md +3 -3
- package/bin/swl-mcp-server.js +187 -0
- package/habilidades/benchmark-memoria/SKILL.md +186 -0
- package/habilidades/contenedores-docker/SKILL.md +8 -1
- package/habilidades/datos-etl/SKILL.md +18 -1
- package/habilidades/eval-framework/SKILL.md +212 -0
- package/habilidades/memoria-busqueda/SKILL.md +24 -1
- package/habilidades/planear-fase/SKILL.md +299 -269
- package/habilidades/postgresql-experto/SKILL.md +24 -1
- package/habilidades/verificar-trabajo/SKILL.md +7 -1
- package/hooks/lib/evolution-tracker.js +65 -11
- package/hooks/lib/memory-search.js +44 -13
- package/hooks/sugerir-contribuir.js +226 -0
- package/manifiestos/hooks-config.json +9 -0
- package/manifiestos/modulos.json +33 -1
- package/manifiestos/perfiles.json +2 -1
- package/package.json +4 -3
- package/plugin.json +343 -343
- package/scripts/benchmark-memoria.js +167 -0
- package/scripts/detectar-aprendizajes-duplicados.js +151 -0
- package/scripts/lib/benchmark-metrics.js +160 -0
- package/scripts/lib/eval-metrics-store.js +218 -0
- package/scripts/lib/eval-quality.js +171 -0
- package/scripts/lib/eval-schemas.js +144 -0
- package/scripts/lib/eval-self-correct.js +106 -0
- package/scripts/lib/eval-validator.js +185 -0
- package/scripts/lib/jaccard-similarity.js +98 -0
- package/scripts/lib/longmemeval-runner.js +125 -0
- package/scripts/lib/rrf-fusion.js +175 -0
- package/scripts/lib/scoring-instintos.js +40 -3
- package/scripts/mcp-server/README.md +128 -0
- package/scripts/mcp-server/handlers.js +206 -0
- package/scripts/run-eval.js +141 -0
package/CLAUDE.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# CLAUDE.md — @saulwade/swl-ses v1.
|
|
1
|
+
# CLAUDE.md — @saulwade/swl-ses v1.2.0
|
|
2
2
|
|
|
3
3
|
## Reglas de máxima prioridad (aplican SIEMPRE, sin excepción)
|
|
4
4
|
|
|
@@ -23,7 +23,7 @@ El Read tool sigue siendo correcto para `.pdf` (≤20 páginas), `.md`, `.txt` y
|
|
|
23
23
|
## Qué es este repositorio
|
|
24
24
|
|
|
25
25
|
Sistema de ingeniería de software auto-evolutivo multi-runtime polyglot (SDLC completo).
|
|
26
|
-
11 lenguajes, 5 runtimes, 59 agentes,
|
|
26
|
+
11 lenguajes, 5 runtimes, 59 agentes, 153 skills, 42 comandos, 64 reglas, 40 hooks.
|
|
27
27
|
**Idioma**: 100% español (México) para componentes SWL y skills Anthropic en inglés.
|
|
28
28
|
|
|
29
29
|
## Estructura del repositorio
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# swl-ses v1.
|
|
1
|
+
# swl-ses v1.2.0
|
|
2
2
|
|
|
3
3
|
> El paquete anterior `@saulwadeleon/swl-software-engineering-system` está deprecado. Migrar a `@saulwade/swl-ses` (npmjs.org canónico) o `@saul-wade/swl-ses` (mirror en GitHub Packages) — el CLI `swl-ses` no cambia.
|
|
4
4
|
|
|
@@ -177,7 +177,7 @@ claude
|
|
|
177
177
|
| `mobile` | Android + iOS + React Native/Flutter + UX |
|
|
178
178
|
| `devops` | CI/CD + cloud + observabilidad + releases + seguridad |
|
|
179
179
|
| `polyglot` | Todos los lenguajes: 11 lenguajes + revisores + build resolvers |
|
|
180
|
-
| `completo` | Todo: 59 agentes +
|
|
180
|
+
| `completo` | Todo: 59 agentes + 153 habilidades + 42 comandos + 64 reglas + 40 hooks |
|
|
181
181
|
|
|
182
182
|
### Targets soportados
|
|
183
183
|
|
|
@@ -478,7 +478,7 @@ swl-ses/
|
|
|
478
478
|
seguridad.js # Validaciones de seguridad
|
|
479
479
|
manifiestos/ # Perfiles y módulos de instalación
|
|
480
480
|
agentes/ # 59 agentes especializados
|
|
481
|
-
habilidades/ #
|
|
481
|
+
habilidades/ # 153 habilidades modulares
|
|
482
482
|
comandos/swl/ # 42 comandos slash
|
|
483
483
|
reglas/ # 20 reglas base + 40 por lenguaje
|
|
484
484
|
hooks/ # 39 hooks + 62 librerías en hooks/lib/
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* swl-mcp-server — Servidor MCP **EXPERIMENTAL** para exponer la memoria
|
|
6
|
+
* de swl-ses a clientes MCP externos (Cursor, Gemini CLI, OpenCode, etc.).
|
|
7
|
+
*
|
|
8
|
+
* **NO PRODUCCIÓN — STUB EXPERIMENTAL**.
|
|
9
|
+
* Ver `scripts/mcp-server/README.md` para limitaciones detalladas.
|
|
10
|
+
*
|
|
11
|
+
* Modo de transporte: stdio (JSON-RPC sobre stdin/stdout).
|
|
12
|
+
* No HTTP, no auth, no rate limiting.
|
|
13
|
+
*
|
|
14
|
+
* Uso (cliente MCP):
|
|
15
|
+
* - Configurar el cliente para ejecutar `node /path/to/swl-ses/bin/swl-mcp-server.js`
|
|
16
|
+
* con stdio.
|
|
17
|
+
* - Los handlers leen el cwd del proceso para localizar `.planning/`,
|
|
18
|
+
* `instintos/`, `APRENDIZAJES.md`. Por defecto usa `process.cwd()`.
|
|
19
|
+
* - Override con env var `SWL_MCP_BASE_DIR` si el cliente arranca el server
|
|
20
|
+
* desde otro directorio.
|
|
21
|
+
*
|
|
22
|
+
* Protocolo MCP soportado (subset):
|
|
23
|
+
* - initialize / initialized
|
|
24
|
+
* - tools/list
|
|
25
|
+
* - tools/call
|
|
26
|
+
*
|
|
27
|
+
* NO soporta:
|
|
28
|
+
* - resources/list, prompts/list
|
|
29
|
+
* - logging, sampling
|
|
30
|
+
* - cancellation, progress
|
|
31
|
+
* - HTTP transport
|
|
32
|
+
*
|
|
33
|
+
* Trigger documentado para implementación completa: "uso ≥2 runtimes
|
|
34
|
+
* diferentes (Cursor + Claude Code o similar) consistentemente por
|
|
35
|
+
* ≥1 mes". Hoy: 0 instalaciones reportadas.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
const path = require('path');
|
|
39
|
+
|
|
40
|
+
const { HANDLERS } = require('../scripts/mcp-server/handlers');
|
|
41
|
+
|
|
42
|
+
const SERVER_NAME = 'swl-mcp-server';
|
|
43
|
+
const SERVER_VERSION = '0.1.0-experimental';
|
|
44
|
+
const PROTOCOL_VERSION = '2024-11-05';
|
|
45
|
+
|
|
46
|
+
const baseDir = process.env.SWL_MCP_BASE_DIR || process.cwd();
|
|
47
|
+
|
|
48
|
+
// ── logging ───────────────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
// Stderr para evitar contaminar stdout (que es JSON-RPC).
|
|
51
|
+
function log(level, msg, data) {
|
|
52
|
+
const linea = JSON.stringify({
|
|
53
|
+
timestamp: new Date().toISOString(),
|
|
54
|
+
level,
|
|
55
|
+
msg,
|
|
56
|
+
...(data ? { data } : {}),
|
|
57
|
+
});
|
|
58
|
+
process.stderr.write(linea + '\n');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ── JSON-RPC helpers ──────────────────────────────────────────────────────────
|
|
62
|
+
|
|
63
|
+
function respuesta(id, result) {
|
|
64
|
+
return JSON.stringify({ jsonrpc: '2.0', id, result });
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function errorResp(id, code, message) {
|
|
68
|
+
return JSON.stringify({ jsonrpc: '2.0', id, error: { code, message } });
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ── routing ───────────────────────────────────────────────────────────────────
|
|
72
|
+
|
|
73
|
+
function manejarInitialize(request) {
|
|
74
|
+
return respuesta(request.id, {
|
|
75
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
76
|
+
capabilities: {
|
|
77
|
+
tools: { listChanged: false },
|
|
78
|
+
},
|
|
79
|
+
serverInfo: {
|
|
80
|
+
name: SERVER_NAME,
|
|
81
|
+
version: SERVER_VERSION,
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function manejarToolsList(request) {
|
|
87
|
+
const tools = Object.entries(HANDLERS).map(([name, def]) => ({
|
|
88
|
+
name,
|
|
89
|
+
description: def.description,
|
|
90
|
+
inputSchema: def.inputSchema,
|
|
91
|
+
}));
|
|
92
|
+
return respuesta(request.id, { tools });
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function manejarToolsCall(request) {
|
|
96
|
+
const { name, arguments: args } = request.params || {};
|
|
97
|
+
const def = HANDLERS[name];
|
|
98
|
+
if (!def) {
|
|
99
|
+
return errorResp(request.id, -32601, `Tool no encontrado: ${name}`);
|
|
100
|
+
}
|
|
101
|
+
try {
|
|
102
|
+
const result = def.handler(baseDir, args || {});
|
|
103
|
+
return respuesta(request.id, {
|
|
104
|
+
content: [{ type: 'text', text: JSON.stringify(result, null, 2) }],
|
|
105
|
+
});
|
|
106
|
+
} catch (err) {
|
|
107
|
+
log('error', `Excepción en handler ${name}`, { error: err.message });
|
|
108
|
+
return errorResp(request.id, -32603, `Error interno: ${err.message}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function rutear(request) {
|
|
113
|
+
switch (request.method) {
|
|
114
|
+
case 'initialize':
|
|
115
|
+
return manejarInitialize(request);
|
|
116
|
+
case 'initialized':
|
|
117
|
+
case 'notifications/initialized':
|
|
118
|
+
return null; // notification — sin respuesta
|
|
119
|
+
case 'tools/list':
|
|
120
|
+
return manejarToolsList(request);
|
|
121
|
+
case 'tools/call':
|
|
122
|
+
return manejarToolsCall(request);
|
|
123
|
+
case 'ping':
|
|
124
|
+
return respuesta(request.id, {});
|
|
125
|
+
default:
|
|
126
|
+
return errorResp(request.id, -32601, `Método no soportado: ${request.method}`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// ── loop principal ────────────────────────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
function arrancar() {
|
|
133
|
+
log('warn', '⚠ swl-mcp-server stub experimental — NO usar en producción');
|
|
134
|
+
log('info', `Server iniciando`, { name: SERVER_NAME, version: SERVER_VERSION, baseDir });
|
|
135
|
+
|
|
136
|
+
let buffer = '';
|
|
137
|
+
|
|
138
|
+
process.stdin.setEncoding('utf8');
|
|
139
|
+
process.stdin.on('data', (chunk) => {
|
|
140
|
+
buffer += chunk;
|
|
141
|
+
|
|
142
|
+
// Cada mensaje JSON-RPC termina con \n
|
|
143
|
+
let nlIndex;
|
|
144
|
+
while ((nlIndex = buffer.indexOf('\n')) >= 0) {
|
|
145
|
+
const linea = buffer.slice(0, nlIndex).trim();
|
|
146
|
+
buffer = buffer.slice(nlIndex + 1);
|
|
147
|
+
|
|
148
|
+
if (!linea) continue;
|
|
149
|
+
|
|
150
|
+
let request;
|
|
151
|
+
try {
|
|
152
|
+
request = JSON.parse(linea);
|
|
153
|
+
} catch (err) {
|
|
154
|
+
log('error', 'JSON inválido recibido', { error: err.message, linea: linea.slice(0, 100) });
|
|
155
|
+
process.stdout.write(errorResp(null, -32700, 'Parse error') + '\n');
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const respuestaStr = rutear(request);
|
|
160
|
+
if (respuestaStr) {
|
|
161
|
+
process.stdout.write(respuestaStr + '\n');
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
process.stdin.on('end', () => {
|
|
167
|
+
log('info', 'stdin cerrado, server termina');
|
|
168
|
+
process.exit(0);
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Manejo de errores no capturados — nunca crashear silenciosamente
|
|
172
|
+
process.on('uncaughtException', (err) => {
|
|
173
|
+
log('error', 'uncaughtException', { error: err.message, stack: err.stack });
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (require.main === module) {
|
|
178
|
+
arrancar();
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
module.exports = {
|
|
182
|
+
rutear,
|
|
183
|
+
arrancar,
|
|
184
|
+
SERVER_NAME,
|
|
185
|
+
SERVER_VERSION,
|
|
186
|
+
PROTOCOL_VERSION,
|
|
187
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: benchmark-memoria
|
|
3
|
+
description: >
|
|
4
|
+
Benchmark de retrieval para `hooks/lib/memory-search` que mide R@5, R@10,
|
|
5
|
+
MRR y nDCG@10 contra un dataset de queries con respuestas conocidas
|
|
6
|
+
(gold_ids). Útil para detectar regresión de calidad de búsqueda al cambiar
|
|
7
|
+
RRF weights, scoring o fuentes. Cargar cuando se modifique
|
|
8
|
+
`memory-search.js`, `rrf-fusion.js` o `session-fts.js` para verificar que
|
|
9
|
+
no degrada retrieval. NO cargar para uso operacional ni para evaluar
|
|
10
|
+
outputs de agentes (eso es `eval-framework`).
|
|
11
|
+
version: "1.0.0"
|
|
12
|
+
herramientasPermitidas: [Read, Bash]
|
|
13
|
+
exclusiones:
|
|
14
|
+
- "No cargar para evaluar outputs de agentes (aprendizajes, observaciones, resúmenes); eso es `eval-framework`."
|
|
15
|
+
- "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."
|
|
16
|
+
- "No cargar para benchmark de performance (latencia, throughput); eso es `performance-baseline`."
|
|
17
|
+
- "No cargar si el repo no tiene `.planning/APRENDIZAJES.md` ni sesiones — sin contenido no hay nada que recuperar."
|
|
18
|
+
evolvable: true # default para skill estandar
|
|
19
|
+
---
|
|
20
|
+
# Benchmark de Memoria — LongMemEval-S adaptado a SWL
|
|
21
|
+
|
|
22
|
+
## Cuándo cargar esta skill
|
|
23
|
+
|
|
24
|
+
- Tras modificar `hooks/lib/memory-search.js`, `scripts/lib/rrf-fusion.js`,
|
|
25
|
+
pesos de fusión o algoritmo de scoring de relevancia.
|
|
26
|
+
- Tras agregar fuentes de búsqueda nuevas (ej. evals, instintos globales).
|
|
27
|
+
- Antes de un release que toque la capa de memoria, para confirmar que no
|
|
28
|
+
hay regresión en R@5.
|
|
29
|
+
- Como parte opcional de `/swl:salud` cuando `SWL_BENCHMARK_MEMORIA=1`.
|
|
30
|
+
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Componentes del benchmark
|
|
34
|
+
|
|
35
|
+
| Módulo | Propósito |
|
|
36
|
+
|---|---|
|
|
37
|
+
| `scripts/lib/benchmark-metrics.js` | Funciones puras: `recallAt`, `precisionAt`, `mrr`, `ndcgAt`, `dcg`, `calcularMetricas`, `promediar`. |
|
|
38
|
+
| `scripts/lib/longmemeval-runner.js` | Adapter que ejecuta queries contra `memory-search` y compara con gold. |
|
|
39
|
+
| `scripts/benchmark-memoria.js` | CLI runner principal. |
|
|
40
|
+
| `.planning/benchmark/dataset.jsonl` | Dataset (placeholder por defecto, debe expandirse). |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Estado del dataset (CRÍTICO leer antes de usar)
|
|
45
|
+
|
|
46
|
+
El dataset por defecto en `.planning/benchmark/dataset.jsonl` es **placeholder**
|
|
47
|
+
con 10 entries marcadas explícitamente como `"status": "placeholder"`. Las
|
|
48
|
+
métricas calculadas con este dataset son **indicativas, no estadísticamente
|
|
49
|
+
significativas**.
|
|
50
|
+
|
|
51
|
+
### Limitaciones del dataset placeholder
|
|
52
|
+
|
|
53
|
+
1. **IDs volátiles**: los `gold_ids` referencian `apr-N` (índice de entrada en
|
|
54
|
+
`APRENDIZAJES.md`). Si se agregan/borran entradas, los índices cambian y
|
|
55
|
+
el dataset queda desincronizado. Para dataset real considerar IDs estables
|
|
56
|
+
(sesiones tienen timestamp; instintos tienen `id` propio).
|
|
57
|
+
2. **N=10 es ruido estadístico**: para que R@5=80% sea significativo
|
|
58
|
+
estadísticamente (vs 70% baseline), se requieren al menos 30 queries
|
|
59
|
+
con N=10 random. Por debajo, las métricas reflejan suerte.
|
|
60
|
+
3. **Cobertura limitada**: el placeholder cubre solo categorías técnicas
|
|
61
|
+
(patrones, gotchas, decisiones). Falta cobertura de:
|
|
62
|
+
- Bug fixes históricos
|
|
63
|
+
- Workflow questions ("qué hicimos antes de X")
|
|
64
|
+
- Cross-session ("cuándo se decidió Y")
|
|
65
|
+
- Negative queries (preguntas cuya respuesta es "no aplica")
|
|
66
|
+
|
|
67
|
+
### Cómo expandir a dataset real
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
# 1. Identifica una pregunta real de uso
|
|
71
|
+
QUERY="qué hicimos sobre force push a main protegida"
|
|
72
|
+
|
|
73
|
+
# 2. Ejecuta búsqueda y anota top-5 IDs
|
|
74
|
+
node -e "console.log(require('./hooks/lib/memory-search').search('.', '$QUERY').slice(0, 5).map(r => r.id + ' / ' + r.titulo).join('\n'))"
|
|
75
|
+
|
|
76
|
+
# 3. Verifica manualmente qué IDs son CORRECTOS (revisión humana,
|
|
77
|
+
# no se inventa). Solo esos van en gold_ids.
|
|
78
|
+
|
|
79
|
+
# 4. Agrega la entry al dataset:
|
|
80
|
+
cat >> .planning/benchmark/dataset.jsonl << 'EOF'
|
|
81
|
+
{"question_id": "q-real-001", "question": "qué hicimos sobre force push a main protegida", "gold_ids": ["apr-313"], "category": "decision", "status": "real"}
|
|
82
|
+
EOF
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Repetir hasta tener ≥30 entries con `status: "real"`. Solo entonces el
|
|
86
|
+
benchmark es gate de release.
|
|
87
|
+
|
|
88
|
+
---
|
|
89
|
+
|
|
90
|
+
## Uso
|
|
91
|
+
|
|
92
|
+
### CLI básico
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
# Ejecutar benchmark con dataset por defecto
|
|
96
|
+
node scripts/benchmark-memoria.js
|
|
97
|
+
|
|
98
|
+
# Output esperado:
|
|
99
|
+
# Recall @ 5: 85.0%
|
|
100
|
+
# Recall @ 10: 92.0%
|
|
101
|
+
# MRR: 0.741
|
|
102
|
+
# nDCG @ 10: 0.812
|
|
103
|
+
# Precision @ 5: 41.3%
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Opciones
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# Dataset alternativo
|
|
110
|
+
node scripts/benchmark-memoria.js --dataset .planning/benchmark/custom.jsonl
|
|
111
|
+
|
|
112
|
+
# Top-k personalizado (default 20)
|
|
113
|
+
node scripts/benchmark-memoria.js --limit 30
|
|
114
|
+
|
|
115
|
+
# Output JSON (para scripts)
|
|
116
|
+
node scripts/benchmark-memoria.js --json
|
|
117
|
+
|
|
118
|
+
# Detalle por query (útil para debugging)
|
|
119
|
+
node scripts/benchmark-memoria.js --verbose
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
### Tracking histórico opcional
|
|
123
|
+
|
|
124
|
+
Si se setea `SWL_BENCHMARK_PERSIST=1`, el benchmark escribe el resumen
|
|
125
|
+
agregado a `.planning/evolucion/benchmark-memoria.jsonl` (append-only)
|
|
126
|
+
para detectar regresión entre releases:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
SWL_BENCHMARK_PERSIST=1 node scripts/benchmark-memoria.js
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Comparar entre releases:
|
|
133
|
+
```bash
|
|
134
|
+
tail -5 .planning/evolucion/benchmark-memoria.jsonl | jq -c '{ts: .timestamp, r5: .promedio.recall_at_5}'
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
---
|
|
138
|
+
|
|
139
|
+
## Métricas explicadas
|
|
140
|
+
|
|
141
|
+
| Métrica | Significado | Rango |
|
|
142
|
+
|---|---|---|
|
|
143
|
+
| **Recall @ k** | ¿El sistema recuperó al menos un gold ID en los primeros k? | 0 o 1 por query, promediado en [0, 1] |
|
|
144
|
+
| **Precision @ k** | ¿Qué porcentaje de los primeros k son gold? | [0, 1] |
|
|
145
|
+
| **MRR** | 1 / posición del primer gold encontrado | [0, 1] — alto = gold cerca del top |
|
|
146
|
+
| **nDCG @ k** | DCG normalizado: penaliza gold en posiciones bajas | [0, 1] — mide ranking quality |
|
|
147
|
+
|
|
148
|
+
**Interpretación**:
|
|
149
|
+
- R@5 alto + MRR bajo → encuentra gold pero no en posición 1.
|
|
150
|
+
- R@5 bajo + R@10 alto → necesita expandir top-k para alcanzar.
|
|
151
|
+
- nDCG@10 alto → ranking respeta orden de relevancia.
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Anti-patrones (qué NO hacer)
|
|
156
|
+
|
|
157
|
+
- **Usar el dataset placeholder como gate de CI**: no significativo, da
|
|
158
|
+
falsa sensación de seguridad. Marcar el job como informativo solamente
|
|
159
|
+
hasta tener dataset real ≥30.
|
|
160
|
+
- **Inventar gold_ids "que se ven correctos"**: el dataset SOLO sirve si
|
|
161
|
+
los gold_ids son verificados manualmente como correctos por un humano
|
|
162
|
+
con conocimiento del proyecto.
|
|
163
|
+
- **Optimizar el algoritmo solo para que las métricas suban**: si el
|
|
164
|
+
dataset es placeholder, "mejorar" R@5 puede ser overfitting al
|
|
165
|
+
placeholder. Dataset real primero, optimización después.
|
|
166
|
+
- **Borrar entries que dan métricas bajas**: una query con R@5=0 puede
|
|
167
|
+
estar revelando un bug real del sistema de búsqueda, no un problema
|
|
168
|
+
del dataset. Debug antes de borrar.
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Gotchas / Errores comunes no obvios
|
|
173
|
+
|
|
174
|
+
- **Dataset JSONL acepta comentarios `//`**: las líneas que empiezan con
|
|
175
|
+
`//` son ignoradas. Útil para documentar inline. NO es JSON estándar
|
|
176
|
+
pero el parser de `longmemeval-runner` lo respeta.
|
|
177
|
+
- **Si la query no matchea ningún token mínimo (<= 3 chars), `search()`
|
|
178
|
+
devuelve `[]`**: las stop words están filtradas en
|
|
179
|
+
`hooks/lib/memory-search.js`. Asegurar que cada query tenga ≥2
|
|
180
|
+
términos significativos.
|
|
181
|
+
- **Los `gold_ids` deben usar el formato `apr-N`/`ses-YYYYMMDD-HHmm`/etc.
|
|
182
|
+
EXACTO** que devuelve `memory-search`. Si se anota `apr-14` cuando el
|
|
183
|
+
search devuelve `apr-014`, el match falla silenciosamente.
|
|
184
|
+
- **El benchmark mide `memory-search` específicamente, no toda la memoria
|
|
185
|
+
SWL**: instintos globales no entran si solo se buscan locales; sesiones
|
|
186
|
+
archivadas no entran. Documentar el scope de cada query.
|
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: contenedores-docker
|
|
3
3
|
description: Docker y containerización. Dockerfiles optimizados con multi-stage builds, docker-compose, volúmenes, networking, health checks, security scanning, build caching, distroless images. Anti-patrones comunes.
|
|
4
|
-
version: "1.0.
|
|
4
|
+
version: "1.0.1"
|
|
5
|
+
evolved: true
|
|
6
|
+
evolved-from: "1.0.0"
|
|
7
|
+
evolved-at: "2026-05-05"
|
|
8
|
+
evolved-by: "aprender"
|
|
9
|
+
evolved-note: "Gotcha de la sesión SIGM 2026-05-05 (L6): scripts en /docker-entrypoint-initdb.d con dependencias externas (mc, aws, etc.) abortan initdb"
|
|
5
10
|
herramientasPermitidas: [Read]
|
|
6
11
|
exclusiones:
|
|
7
12
|
- "No cargar para orquestación Kubernetes (Deployments, Services, Helm, HPA) — para Kubernetes cargar `kubernetes-orquestacion`."
|
|
@@ -135,3 +140,5 @@ Para ejemplos completos de multi-stage Node.js/Angular, .dockerignore, docker-co
|
|
|
135
140
|
**Secrets pasados como `ARG` en el Dockerfile son visibles en `docker history` aunque se eliminen en una capa posterior**: `ARG SECRET_KEY` seguido de `RUN rm -f /app/.env` no elimina el valor del ARG del historial de la imagen — `docker history --no-trunc imagen` muestra los valores de los ARG. Causa: `docker history` registra los comandos de cada capa, incluyendo los valores de ARG usados. Fix: NUNCA pasar secretos como `ARG` o `ENV` en el Dockerfile; para secrets en build-time usar `--secret` de Docker BuildKit: `RUN --mount=type=secret,id=api_key cat /run/secrets/api_key`.
|
|
136
141
|
|
|
137
142
|
**`.dockerignore` mal configurado incluye archivos `.env` o `node_modules` en el contexto de build, ralentizando el daemon y exponiendo secretos**: si `.dockerignore` no existe o no excluye `node_modules`, el contexto de build puede ser de cientos de MB — todo se transfiere al daemon antes del build. Causa: el contexto de build incluye todo el directorio por defecto. Fix: crear `.dockerignore` con al menos `.git`, `node_modules`, `__pycache__`, `.env*`, `*.pyc`, `dist`, `build`, y verificar el tamaño del contexto con `docker build` mirando la línea "Sending build context to Docker daemon X.XX MB".
|
|
143
|
+
|
|
144
|
+
**Scripts en `database/init/` (mounted a `/docker-entrypoint-initdb.d`) no pueden depender de binarios fuera de la imagen base**: un script con `set -e` que invoca `mc` (cliente MinIO), `aws`, `gh`, `kubectl` o cualquier binario no presente en la imagen `postgres`/`postgis` aborta el initdb con `command not found` (exit 127). El contenedor sale con error y los scripts SQL que iban después (incluido schemas/seeds completos) NUNCA se ejecutan. Caso real (SIGM 2026-05-04): `02-crear-buckets-minio.sh` con `mc alias set` aborta `postgis/postgis:17-3.5` antes de cargar el schema; ningún schema se aplicaba aunque el archivo init.sh estaba bien. Causa: el directorio `/docker-entrypoint-initdb.d` ejecuta todos los archivos `.sh` y `.sql` en orden alfabético; cualquier fallo aborta toda la cadena. Fix: scripts que invocan binarios externos van en `scripts/setup/` o `scripts/init-deps/` y se ejecutan manualmente o via `docker compose run --rm --entrypoint /scripts/setup/X.sh <servicio_que_tiene_el_binario>`. Solo SQL puro o bash que use `psql` pueden vivir en `database/init/`.
|
|
@@ -4,7 +4,12 @@ description: >
|
|
|
4
4
|
Ingeniería de datos y ETL: diseño de pipelines, calidad de datos, evolución de esquemas,
|
|
5
5
|
CDC (change data capture), validación de datos, linaje de datos, procesamiento batch vs
|
|
6
6
|
streaming, patrones dbt, patrones de data lake.
|
|
7
|
-
version: "1.0.
|
|
7
|
+
version: "1.0.1"
|
|
8
|
+
evolved: true
|
|
9
|
+
evolved-from: "1.0.0"
|
|
10
|
+
evolved-at: "2026-05-05"
|
|
11
|
+
evolved-by: "aprender"
|
|
12
|
+
evolved-note: "Gotcha de la sesión SIGM 2026-05-05 (L9): seeds con check constraint fecha_vencimiento >= fecha_emision violan al usar emision única para todas las tuplas"
|
|
8
13
|
herramientasPermitidas: [Read, Grep]
|
|
9
14
|
evolvable: true # default para skill estandar
|
|
10
15
|
exclusiones:
|
|
@@ -127,3 +132,15 @@ esquemas (Avro), patrones dbt (modelos incrementales), y linaje de datos, ver
|
|
|
127
132
|
**Validación con Great Expectations pasa en staging pero falla en producción con el mismo schema**: los expectations se definen sobre una muestra de datos de staging que puede no tener todos los valores posibles del dominio. Causa: un expectation de tipo `expect_column_values_to_be_in_set(["A","B","C"])` falla en producción cuando aparece un valor "D" válido que no existía en el sample de staging. Fix: para columnas con dominio abierto, usar `expect_column_values_to_not_be_null` en lugar de un set cerrado. Reservar sets cerrados solo para columnas con dominio verdaderamente finito y controlado (estados de máquina de estado, flags booleanos).
|
|
128
133
|
|
|
129
134
|
**El job de Spark/pandas falla en producción por OOM aunque funcionó bien en staging con datos de muestra**: el procesamiento en memoria de un DataFrame completo escala linealmente con el volumen de datos. En staging con 10K registros todo cabe en RAM; en producción con 10M registros el proceso muere. Causa: operaciones como `groupby + apply` con funciones Python puras no se pueden paralelizar ni distribuir automáticamente. Fix: para transformaciones sobre datasets grandes, usar procesamiento por chunks (`chunksize` en pandas) o migrar a Spark/DuckDB para operaciones distribuidas. Medir el uso de memoria en staging con el volumen máximo esperado en producción, no con la muestra de desarrollo.
|
|
135
|
+
|
|
136
|
+
**Seeds que simulan data histórica con check constraints de fechas relativas violan el constraint si se usa una sola fecha de emisión**: cuando un seed crea facturas/pagos demo con una mezcla de estatus (vencidas, vigentes, pagadas) y la tabla tiene check constraint del tipo `fecha_vencimiento >= fecha_emision`, generar `fecha_emision = '2025-12-10'` y luego asignar `fecha_vencimiento = '2025-10-31'` para vencidas viola el constraint. Caso real (SIGM 2026-05-04): seed `009-datos-operativos-demo.sql` con 500 facturas demo donde `n%10=0` representaba "vencidas" pero la fecha_emision común era posterior. Causa: la lógica genera dimensiones derivadas (vencimiento) sin re-derivar las fuente (emisión). Fix: separar fechas POR ESTATUS antes de generar tuplas:
|
|
137
|
+
```sql
|
|
138
|
+
-- Vencidas: emision en pasado profundo, vencimiento ya pasó
|
|
139
|
+
CASE WHEN n % 10 = 0 THEN DATE '2025-09-30'
|
|
140
|
+
ELSE DATE '2025-12-10' + (n % 3)
|
|
141
|
+
END AS fecha_emision,
|
|
142
|
+
CASE WHEN n % 10 = 0 THEN DATE '2025-10-31'
|
|
143
|
+
ELSE DATE '2026-01-31'
|
|
144
|
+
END AS fecha_vencimiento,
|
|
145
|
+
```
|
|
146
|
+
Generalización: cualquier check constraint que relacione columnas debe respetarse al SEEDEAR, no solo en INSERT productivo.
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: eval-framework
|
|
3
|
+
description: >
|
|
4
|
+
Eval framework para validar y puntuar outputs estructurados de SWL
|
|
5
|
+
(aprendizajes, instintos, observaciones, resúmenes, contextos). Cargar
|
|
6
|
+
cuando un agente produzca un output estructurado y se quiera medir su
|
|
7
|
+
calidad antes de persistir, o cuando se audite la calidad histórica de
|
|
8
|
+
funciones críticas (extractor-de-aprendizajes, perfilador-usuario,
|
|
9
|
+
consolidador, planificador).
|
|
10
|
+
version: "1.0.0"
|
|
11
|
+
herramientasPermitidas: [Read, Bash]
|
|
12
|
+
exclusiones:
|
|
13
|
+
- "No cargar para validación de input de usuario o request HTTP — eso es validación de boundary, usar Pydantic/Zod en el endpoint."
|
|
14
|
+
- "No cargar para auditoría de seguridad — usar `revisor-seguridad-swl` y `escaneo-secretos`."
|
|
15
|
+
- "No cargar para tests unitarios de código — usar `tdd-workflow` y Vitest."
|
|
16
|
+
- "No cargar cuando el output no tiene estructura definida (texto libre): el eval framework requiere schemas declarados o quality scorers específicos."
|
|
17
|
+
evolvable: true # default para skill estandar
|
|
18
|
+
---
|
|
19
|
+
# Eval Framework — Validación + Calidad de Outputs SWL
|
|
20
|
+
|
|
21
|
+
## Cuándo cargar
|
|
22
|
+
|
|
23
|
+
- Tras producir un output estructurado (observación, aprendizaje, resumen,
|
|
24
|
+
resultado de búsqueda) cuando se quiera puntuar su calidad antes de
|
|
25
|
+
persistir.
|
|
26
|
+
- Para auditar histórico de calidad de una función crítica (ver métricas
|
|
27
|
+
agregadas en `.planning/evolucion/eval-metrics.json`).
|
|
28
|
+
- En tests/CI cuando el contrato del output tenga campos obligatorios y
|
|
29
|
+
quality thresholds.
|
|
30
|
+
- En loops de auto-corrección donde un output inválido debe regenerarse
|
|
31
|
+
con un prompt más estricto.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Componentes del framework
|
|
36
|
+
|
|
37
|
+
| Módulo | Propósito |
|
|
38
|
+
|---|---|
|
|
39
|
+
| `scripts/lib/eval-schemas.js` | Schemas JSON-lite para outputs (observación, resumen, search input, etc.). |
|
|
40
|
+
| `scripts/lib/eval-validator.js` | Validador zero-deps de schemas (sin Zod). |
|
|
41
|
+
| `scripts/lib/eval-quality.js` | Funciones de scoring: `scoreObservacion`, `scoreResumen`, `scoreAprendizaje`, `scoreInstinto`, `scoreRelevanciaContexto`. |
|
|
42
|
+
| `scripts/lib/eval-self-correct.js` | Loop de retry con sufijo estricto cuando validador falla. |
|
|
43
|
+
| `scripts/lib/eval-metrics-store.js` | Persistencia: JSONL append-only (`eval-results.jsonl`) + agregado JSON (`eval-metrics.json`). |
|
|
44
|
+
| `scripts/run-eval.js` | CLI para evaluar un output desde archivo JSON. |
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Uso típico desde agente o test
|
|
49
|
+
|
|
50
|
+
### Validar output contra schema
|
|
51
|
+
|
|
52
|
+
```js
|
|
53
|
+
const { validar } = require('./scripts/lib/eval-validator');
|
|
54
|
+
const { COMPRESS_OUTPUT_SCHEMA } = require('./scripts/lib/eval-schemas');
|
|
55
|
+
|
|
56
|
+
const observacion = {
|
|
57
|
+
type: 'discovery',
|
|
58
|
+
title: 'Detalle relevante',
|
|
59
|
+
facts: ['fact 1', 'fact 2'],
|
|
60
|
+
narrative: 'Narrativa con suficiente detalle para evaluar',
|
|
61
|
+
concepts: ['c1'],
|
|
62
|
+
files: ['ruta/al/archivo.js'],
|
|
63
|
+
importance: 7,
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
const r = validar(observacion, COMPRESS_OUTPUT_SCHEMA);
|
|
67
|
+
if (!r.valid) {
|
|
68
|
+
console.error('Output inválido:', r.errors);
|
|
69
|
+
} else {
|
|
70
|
+
// Persistir
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Puntuar calidad (independiente de validez)
|
|
75
|
+
|
|
76
|
+
```js
|
|
77
|
+
const { scoreObservacion, scoreAprendizaje, scoreInstinto } = require('./scripts/lib/eval-quality');
|
|
78
|
+
|
|
79
|
+
const score = scoreObservacion(observacion);
|
|
80
|
+
// score ∈ [0, 100]. 100 = todos los campos óptimos.
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Loop de auto-corrección
|
|
84
|
+
|
|
85
|
+
```js
|
|
86
|
+
const { compresseConReintento } = require('./scripts/lib/eval-self-correct');
|
|
87
|
+
|
|
88
|
+
const productor = async (sysPrompt, userPrompt) => {
|
|
89
|
+
// Llamar al LLM o ejecutar Skill que produzca el output
|
|
90
|
+
return await llamarClaude(sysPrompt, userPrompt);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
const validador = (output) => {
|
|
94
|
+
const parsed = JSON.parse(output);
|
|
95
|
+
return validar(parsed, COMPRESS_OUTPUT_SCHEMA);
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const r = await compresseConReintento({
|
|
99
|
+
productor, validador,
|
|
100
|
+
sysPrompt: '...', userPrompt: '...',
|
|
101
|
+
maxRetries: 2,
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
if (r.valid) {
|
|
105
|
+
// r.output es válido (puede haber requerido r.intentos retries)
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Persistir métricas para auditoría histórica
|
|
110
|
+
|
|
111
|
+
```js
|
|
112
|
+
const ms = require('./scripts/lib/eval-metrics-store');
|
|
113
|
+
|
|
114
|
+
ms.registrar(process.cwd(), {
|
|
115
|
+
functionId: 'extractor-de-aprendizajes::scorer',
|
|
116
|
+
latencyMs: 42,
|
|
117
|
+
success: true,
|
|
118
|
+
qualityScore: 85,
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Lectura agregada
|
|
122
|
+
const m = ms.obtener(process.cwd(), 'extractor-de-aprendizajes::scorer');
|
|
123
|
+
// → { totalCalls, successCount, failureCount, avgLatencyMs, avgQualityScore, ... }
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### CLI desde Bash (para CI o manual)
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Crear archivo de eval
|
|
130
|
+
cat > /tmp/eval.json << 'EOF'
|
|
131
|
+
{
|
|
132
|
+
"functionId": "memoria-busqueda::search",
|
|
133
|
+
"schemaName": "MEMORY_SEARCH_RESULT_SCHEMA",
|
|
134
|
+
"qualityScorer": null,
|
|
135
|
+
"expectedKeys": ["id", "tipo", "titulo", "fecha", "relevancia"],
|
|
136
|
+
"output": { ... }
|
|
137
|
+
}
|
|
138
|
+
EOF
|
|
139
|
+
|
|
140
|
+
# Ejecutar
|
|
141
|
+
node scripts/run-eval.js /tmp/eval.json
|
|
142
|
+
# Exit 0 si valid, 1 si inválido. Persiste métricas automáticamente.
|
|
143
|
+
|
|
144
|
+
# Reconstruir agregado desde JSONL si se corrompe
|
|
145
|
+
node scripts/run-eval.js --rebuild-aggregate
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
---
|
|
149
|
+
|
|
150
|
+
## Schemas disponibles
|
|
151
|
+
|
|
152
|
+
- `COMPRESS_OUTPUT_SCHEMA` — observación comprimida con type, title, facts,
|
|
153
|
+
narrative, concepts, files, importance.
|
|
154
|
+
- `SUMMARY_OUTPUT_SCHEMA` — resumen de sesión con title, narrative,
|
|
155
|
+
keyDecisions, filesModified, concepts.
|
|
156
|
+
- `SEARCH_INPUT_SCHEMA` — input de búsqueda { query, limit? }.
|
|
157
|
+
- `REMEMBER_INPUT_SCHEMA` — input de "remember" { content, type?,
|
|
158
|
+
concepts?, files? }.
|
|
159
|
+
- `EVAL_RESULT_SCHEMA` — resultado de evaluación { valid, errors?,
|
|
160
|
+
qualityScore, latencyMs, functionId, metadata? }.
|
|
161
|
+
- `MEMORY_SEARCH_RESULT_SCHEMA` — resultado de `hooks/lib/memory-search`
|
|
162
|
+
con id, tipo, titulo, fecha, relevancia, combinedScore?, confidence?.
|
|
163
|
+
|
|
164
|
+
Agregar más schemas en `scripts/lib/eval-schemas.js` siguiendo el formato
|
|
165
|
+
JSON Schema-lite (subset documentado en `eval-validator.js`).
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## Quality scorers disponibles
|
|
170
|
+
|
|
171
|
+
- `scoreObservacion(obs)` — observación con type/title/facts/narrative/concepts/importance.
|
|
172
|
+
- `scoreResumen(summary)` — resumen con title/narrative/keyDecisions/filesModified/concepts.
|
|
173
|
+
- `scoreAprendizaje(aprendizaje)` — aprendizaje SWL con titulo/contenido/tipo (específico de SWL).
|
|
174
|
+
- `scoreInstinto(instinto)` — instinto con pattern/confidence/status/source_*/evidence_count (específico de SWL).
|
|
175
|
+
- `scoreRelevanciaContexto(context, project)` — contexto inyectado en sesión.
|
|
176
|
+
|
|
177
|
+
Cada scorer devuelve un número en [0, 100]. Los criterios están documentados
|
|
178
|
+
en cada función en `scripts/lib/eval-quality.js`.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Diferencias con tests unitarios
|
|
183
|
+
|
|
184
|
+
| Eval framework | Tests unitarios |
|
|
185
|
+
|---|---|
|
|
186
|
+
| Mide calidad subjetiva sobre estructura | Mide correctitud lógica |
|
|
187
|
+
| Score graduado [0, 100] | Pass/fail binario |
|
|
188
|
+
| Persiste histórico para auditoría | No persiste (corre en CI) |
|
|
189
|
+
| Para outputs estructurados de agentes | Para funciones puras / API |
|
|
190
|
+
| Permite retry con prompt estricto | No aplicable |
|
|
191
|
+
|
|
192
|
+
Los dos son complementarios: tests unitarios para `scoring-instintos.js`,
|
|
193
|
+
eval framework para "¿el aprendizaje que extrajo el hook es de calidad?".
|
|
194
|
+
|
|
195
|
+
---
|
|
196
|
+
|
|
197
|
+
## Gotchas / Errores comunes no obvios
|
|
198
|
+
|
|
199
|
+
- **Validez estructural ≠ calidad**: un aprendizaje con `titulo: "X"` y
|
|
200
|
+
`contenido: "trivial"` puede pasar `expectedKeys` pero tener
|
|
201
|
+
`qualityScore: 0`. El framework los distingue. En CI gates, considerar
|
|
202
|
+
ambos: `valid && qualityScore >= 60`.
|
|
203
|
+
- **Métricas agregadas se reescriben atómicamente**: si dos procesos
|
|
204
|
+
llaman `registrar()` en paralelo sobre el mismo `functionId`, el último
|
|
205
|
+
gana (race condition en agregado). Para alta concurrencia usar
|
|
206
|
+
`reconstruirAgregado()` periódicamente desde el JSONL append-only.
|
|
207
|
+
- **`run-eval.js` exit code**: 0 = valid, 1 = inválido o error de I/O,
|
|
208
|
+
2 = error de uso. No confundir con quality threshold — el CLI no
|
|
209
|
+
bloquea por quality bajo, solo por validez.
|
|
210
|
+
- **`compresseConReintento` no reintenta indefinidamente**: respeta
|
|
211
|
+
`maxRetries`. Tras agotar reintentos devuelve el último output con
|
|
212
|
+
`valid: false`. El caller decide qué hacer.
|