@saulwade/swl-ses 1.4.1 → 1.5.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 +3 -3
- package/README.md +561 -560
- package/agentes/nemesis-auditor-swl.md +161 -161
- package/bin/swl-mcp-server.js +49 -22
- package/bin/swl-ses.js +74 -0
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/ejecutar-fase.md +33 -4
- package/comandos/swl/metricas.md +72 -0
- package/comandos/swl/nemesis.md +122 -122
- package/gateway/lib/event-channel.js +191 -191
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/discutir-fase/SKILL.md +50 -2
- package/habilidades/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -0
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
- package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
- package/habilidades/harness-claude-code/SKILL.md +299 -299
- package/habilidades/infra-github-actions/SKILL.md +166 -166
- package/habilidades/legacy-code-rescue/SKILL.md +267 -267
- package/habilidades/manejo-errores/.evolved.json +8 -8
- package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
- package/habilidades/patrones-python/SKILL.md +229 -229
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
- package/habilidades/planear-fase/SKILL.md +319 -319
- package/habilidades/protocolo-revision-swl/SKILL.md +276 -0
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
- package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
- package/habilidades/testing-python/SKILL.md +340 -340
- package/habilidades/verificar-trabajo/SKILL.md +49 -5
- package/habilidades/web-fetcher-routing/SKILL.md +75 -75
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/lib/agent-routing.js +107 -107
- package/hooks/lib/auto-consolidator.js +335 -335
- package/hooks/lib/error-classifier.js +308 -308
- package/hooks/lib/merkle-audit.js +96 -96
- package/hooks/lib/provenance-tracker.js +191 -191
- package/hooks/lib/rate-limit-tracker.js +253 -253
- package/hooks/lib/resource-quota.js +122 -122
- package/hooks/lib/retry-jitter.js +165 -165
- package/hooks/lib/security-net.js +201 -201
- package/hooks/lib/skill-auditor.js +588 -588
- package/hooks/lib/sync-status.js +228 -228
- package/hooks/lib/taint-tracker.js +107 -107
- package/hooks/lib/text-similarity.js +241 -241
- package/hooks/lib/toon-compressor.js +245 -245
- package/hooks/registro-turnos.js +209 -209
- package/hooks/sugerir-regenerar-inventario.js +170 -170
- package/hooks/validar-formato-post-subagente.js +140 -140
- package/hooks/validar-memoria-hook.js +218 -218
- package/instintos/prompt-appendices.yaml +57 -57
- package/manifiestos/agent-output-schemas.json +57 -57
- package/manifiestos/modulos.json +1321 -1262
- package/manifiestos/perfiles.json +2 -1
- package/manifiestos/skills-lock.json +1114 -1114
- package/package.json +3 -3
- package/plantillas/auditor-veto-template.md +105 -105
- package/plantillas/github-workflows/README.md +47 -47
- package/plantillas/github-workflows/release-please.yml +44 -44
- package/plantillas/github-workflows/swl-ci.yml +107 -107
- package/plantillas/github-workflows/swl-security.yml +51 -51
- package/plugin.json +351 -343
- package/reglas/analisis-previo-tareas-grandes.md +172 -172
- package/reglas/arreglar-al-detectar.md +147 -147
- package/reglas/fragmentos-compartidos.md +152 -152
- package/reglas/harness-claude-code.md +213 -213
- package/reglas/usar-context7.md +226 -226
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/audit-tools/audit-history.js +330 -330
- package/scripts/audit-tools/bundle-tracker.js +290 -290
- package/scripts/audit-tools/canary-monitor.js +352 -352
- package/scripts/audit-tools/code-profiler.js +605 -605
- package/scripts/audit-tools/dep-doctor.js +320 -320
- package/scripts/audit-tools/env-validator.js +206 -206
- package/scripts/audit-tools/lib/fs-walk.js +48 -48
- package/scripts/audit-tools/lib/output.js +23 -23
- package/scripts/audit-tools/migration-checker.js +392 -392
- package/scripts/audit-tools/pentest-scanner.js +1436 -1436
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/derivar-feature-list.js +489 -0
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- package/scripts/doctor.js +31 -4
- package/scripts/field-report.js +199 -199
- package/scripts/generar-checklists-consolidados.js +273 -273
- package/scripts/generar-inventario.js +420 -420
- package/scripts/generar-matriz-lenguajes.js +271 -271
- package/scripts/instalador.js +56 -5
- package/scripts/lib/artefactos-python.js +43 -43
- package/scripts/lib/benchmark-metrics.js +160 -160
- package/scripts/lib/budget-enforcer.js +252 -252
- package/scripts/lib/configurar-ci.js +380 -380
- package/scripts/lib/contadores-inventario.js +217 -217
- package/scripts/lib/detectar-runtime.js +75 -9
- package/scripts/lib/detectar-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- package/scripts/lib/estado.js +13 -1
- package/scripts/lib/eval-metrics-store.js +218 -218
- package/scripts/lib/eval-quality.js +171 -171
- package/scripts/lib/eval-schemas.js +144 -144
- package/scripts/lib/eval-self-correct.js +106 -106
- package/scripts/lib/eval-validator.js +185 -185
- package/scripts/lib/expandir-targets.js +71 -0
- package/scripts/lib/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/manifiestos.js +42 -1
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- package/scripts/lib/parsear-opciones.js +3 -0
- package/scripts/lib/prompt-builder.js +264 -264
- package/scripts/lib/rrf-fusion.js +175 -175
- package/scripts/lib/scoring-instintos.js +277 -277
- package/scripts/lib/semantic-search.js +252 -252
- package/scripts/lib/toml-merge.js +204 -0
- package/scripts/lib/transformadores/base.js +43 -9
- package/scripts/lib/transformadores/codex.js +375 -115
- package/scripts/lib/transformadores/cursor.js +359 -0
- package/scripts/lib/transformadores/index.js +2 -0
- package/scripts/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/README.md +122 -80
- package/scripts/mcp-server/auth.js +105 -0
- package/scripts/mcp-server/cache.js +106 -0
- package/scripts/mcp-server/handlers.js +386 -206
- package/scripts/mcp-server/telemetry.js +78 -0
- package/scripts/migrar-csv-a-array.js +168 -168
- package/scripts/migrar-fase-dominio.js +201 -201
- package/scripts/publicar.js +511 -511
- package/scripts/run-eval.js +141 -141
- package/scripts/validar-manifest.js +231 -195
- package/scripts/validar-userland-vacio.js +110 -110
package/scripts/instalador.js
CHANGED
|
@@ -411,6 +411,11 @@ async function install(opciones) {
|
|
|
411
411
|
perfil,
|
|
412
412
|
rutaBase: rutas.base,
|
|
413
413
|
global: esGlobal,
|
|
414
|
+
// Persistir contexto de filtrado de reglas-lenguajes para que doctor
|
|
415
|
+
// verifique conteos contra el filtro real al instalar (no recalcule
|
|
416
|
+
// desde el cwd actual del doctor). Fix v1.4.3.
|
|
417
|
+
allLangs: allLangs === true,
|
|
418
|
+
stackInstalado: stackDetectado instanceof Set ? [...stackDetectado] : null,
|
|
414
419
|
});
|
|
415
420
|
|
|
416
421
|
let instalados = 0;
|
|
@@ -447,6 +452,11 @@ async function install(opciones) {
|
|
|
447
452
|
}
|
|
448
453
|
}
|
|
449
454
|
|
|
455
|
+
// Cargar transformador del target ANTES del loop de instalación —
|
|
456
|
+
// Sub-fase 11.5: permite que instalarArchivo invoque transformarAgente
|
|
457
|
+
// por archivo para targets que cambian formato (ej. codex .md → .toml).
|
|
458
|
+
const transformadorTarget = obtenerTransformador(target, runtime);
|
|
459
|
+
|
|
450
460
|
// Instalar archivos core
|
|
451
461
|
for (const archivo of resolucion.archivos) {
|
|
452
462
|
try {
|
|
@@ -457,6 +467,7 @@ async function install(opciones) {
|
|
|
457
467
|
estado,
|
|
458
468
|
syncMode,
|
|
459
469
|
flatNaming,
|
|
470
|
+
transformador: transformadorTarget,
|
|
460
471
|
});
|
|
461
472
|
if (resultado.instalado) {
|
|
462
473
|
registrarArchivo(estado, {
|
|
@@ -612,6 +623,13 @@ async function install(opciones) {
|
|
|
612
623
|
// transformador Claude pueda detectar stack/comandos/framework.
|
|
613
624
|
// Otros transformadores ignoran este campo.
|
|
614
625
|
dirProyecto: process.cwd(),
|
|
626
|
+
// ADR-0019 Sub-fase 1: el transformador Codex necesita saber el scope para
|
|
627
|
+
// resolver dirBase (~/.codex/ vs cwd) y registrar el MCP server.
|
|
628
|
+
// El runtime trae los paths absolutos calculados por detectar-runtime.js.
|
|
629
|
+
esGlobal,
|
|
630
|
+
dirRuntimeGlobal: runtime.global,
|
|
631
|
+
withMcp: Boolean(opciones.with_mcp) && !opciones.no_mcp,
|
|
632
|
+
swlBinPath: path.join(RAIZ_PKG, 'bin', 'swl-mcp-server.js'),
|
|
615
633
|
});
|
|
616
634
|
|
|
617
635
|
if (instrucciones) {
|
|
@@ -628,6 +646,7 @@ async function install(opciones) {
|
|
|
628
646
|
console.log(' = CLAUDE.md no modificado (--no-claudemd)');
|
|
629
647
|
} else if (instrucciones.merge && instrucciones.merge.tipo === 'marcadores') {
|
|
630
648
|
// Merge idempotente con marcadores — preserva contenido del usuario.
|
|
649
|
+
// Aplica a CLAUDE.md (Claude Code) y AGENTS.md (Codex CLI) — ADR-0019 Sub-fase 1.
|
|
631
650
|
if (!fs.existsSync(dirInstrucciones)) {
|
|
632
651
|
fs.mkdirSync(dirInstrucciones, { recursive: true });
|
|
633
652
|
}
|
|
@@ -637,12 +656,13 @@ async function install(opciones) {
|
|
|
637
656
|
endTag: instrucciones.merge.endTag,
|
|
638
657
|
beginPrefix: instrucciones.merge.beginPrefix,
|
|
639
658
|
});
|
|
659
|
+
const nombreArchivo = instrucciones.rutaRelativa;
|
|
640
660
|
const etiqueta = {
|
|
641
|
-
'creado':
|
|
642
|
-
'append':
|
|
643
|
-
'reemplazado':
|
|
644
|
-
'sin-cambios':
|
|
645
|
-
'error':
|
|
661
|
+
'creado': `+ ${nombreArchivo} creado con bloque SWL`,
|
|
662
|
+
'append': `+ Bloque SWL agregado al final de ${nombreArchivo} (contenido del usuario preservado)`,
|
|
663
|
+
'reemplazado': `* Bloque SWL actualizado en ${nombreArchivo} (contenido del usuario preservado)`,
|
|
664
|
+
'sin-cambios': `= ${nombreArchivo} ya tenía el bloque SWL actualizado`,
|
|
665
|
+
'error': `! Error al fusionar ${nombreArchivo}`,
|
|
646
666
|
}[resMerge.accion] || `${resMerge.accion} ${resMerge.archivo}`;
|
|
647
667
|
console.log(` ${etiqueta}${resMerge.detalle ? ': ' + resMerge.detalle : ''}`);
|
|
648
668
|
registrarArchivo(estado, {
|
|
@@ -850,6 +870,33 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
|
|
|
850
870
|
|
|
851
871
|
let nombreArchivo = path.basename(archivo.origen);
|
|
852
872
|
|
|
873
|
+
// Sub-fase 11.5 v1.5.0: para tipo 'agentes', aplicar transformarAgente del
|
|
874
|
+
// transformador del target ANTES de la copia. Permite que codex emita TOML
|
|
875
|
+
// (.toml) en lugar del .md original, o que cursor normalice el frontmatter.
|
|
876
|
+
// Si transformarAgente devuelve `consolidar: true`, saltar — el archivo
|
|
877
|
+
// se incluirá en el archivoPrincipal (AGENTS.md) generado más adelante.
|
|
878
|
+
let contenidoTransformado = null;
|
|
879
|
+
if (archivo.tipo === 'agentes' && opciones.transformador && typeof opciones.transformador.transformarAgente === 'function') {
|
|
880
|
+
try {
|
|
881
|
+
const original = fs.readFileSync(archivo.origen, 'utf-8');
|
|
882
|
+
const t = opciones.transformador.transformarAgente(original, { nombreArchivo });
|
|
883
|
+
if (t && t.consolidar === true) {
|
|
884
|
+
// El transformador del target prefiere consolidar este agente en el
|
|
885
|
+
// archivoPrincipal en lugar de tener archivo individual. No instalar aquí.
|
|
886
|
+
return { instalado: false, razon: 'consolidado en archivoPrincipal' };
|
|
887
|
+
}
|
|
888
|
+
if (t && typeof t.contenido === 'string') {
|
|
889
|
+
contenidoTransformado = t.contenido;
|
|
890
|
+
if (t.rutaRelativa) {
|
|
891
|
+
// Usar el basename del rutaRelativa devuelto (puede cambiar extensión .md → .toml)
|
|
892
|
+
nombreArchivo = path.basename(t.rutaRelativa);
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
} catch (err) {
|
|
896
|
+
console.log(` ! Transformación de agente falló (${err.message}), copia literal del .md`);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
|
|
853
900
|
// Flat naming: convierte rutas jerárquicas en nombres planos con __
|
|
854
901
|
// Ejemplo: habilidades/build-errors-python → build-errors-python (sin cambio, ya es plano)
|
|
855
902
|
// Ejemplo: reglas/lenguajes/java/java-estilo.md → java__java-estilo.md
|
|
@@ -991,6 +1038,10 @@ function instalarArchivo(archivo, rutas, runtime, opciones = {}) {
|
|
|
991
1038
|
// Copy mode (default) o fallback de symlink
|
|
992
1039
|
if (stat.isDirectory()) {
|
|
993
1040
|
copiarDirectorio(archivo.origen, path.join(dirDestino, nombreArchivo));
|
|
1041
|
+
} else if (contenidoTransformado !== null) {
|
|
1042
|
+
// Sub-fase 11.5: el transformador del target reescribió el contenido
|
|
1043
|
+
// (ej. codex .md → .toml). Escribir el contenido transformado.
|
|
1044
|
+
fs.writeFileSync(destino, contenidoTransformado, 'utf-8');
|
|
994
1045
|
} else {
|
|
995
1046
|
fs.copyFileSync(archivo.origen, destino);
|
|
996
1047
|
}
|
|
@@ -1,43 +1,43 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* artefactos-python.js — fuente única de verdad para los artefactos
|
|
5
|
-
* Python que NO deben distribuirse en el paquete npm.
|
|
6
|
-
*
|
|
7
|
-
* Por qué existe:
|
|
8
|
-
* Tanto `scripts/limpiar-artefactos-python.js` (prepack) como
|
|
9
|
-
* `scripts/publicar.js` (copiarDir hacia tmpDir del mirror GH)
|
|
10
|
-
* necesitan saber qué directorios y extensiones excluir. Tener la
|
|
11
|
-
* lista hardcodeada en cada archivo es violación DRY de conocimiento
|
|
12
|
-
* — agregar `.pyd` o `.mypy_cache_v2` exigiría editar dos archivos
|
|
13
|
-
* y mantenerlos sincronizados.
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
const DIRS_ARTEFACTOS_PYTHON = Object.freeze(new Set([
|
|
17
|
-
'__pycache__',
|
|
18
|
-
'.pytest_cache',
|
|
19
|
-
'.mypy_cache',
|
|
20
|
-
'.ruff_cache',
|
|
21
|
-
]));
|
|
22
|
-
|
|
23
|
-
const EXTS_ARTEFACTOS_PYTHON = Object.freeze(new Set([
|
|
24
|
-
'.pyc',
|
|
25
|
-
'.pyo',
|
|
26
|
-
]));
|
|
27
|
-
|
|
28
|
-
function esDirArtefacto(nombre) {
|
|
29
|
-
return DIRS_ARTEFACTOS_PYTHON.has(nombre);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function esArchivoArtefacto(nombre) {
|
|
33
|
-
const dot = nombre.lastIndexOf('.');
|
|
34
|
-
if (dot < 0) return false;
|
|
35
|
-
return EXTS_ARTEFACTOS_PYTHON.has(nombre.slice(dot).toLowerCase());
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
module.exports = {
|
|
39
|
-
DIRS_ARTEFACTOS_PYTHON,
|
|
40
|
-
EXTS_ARTEFACTOS_PYTHON,
|
|
41
|
-
esDirArtefacto,
|
|
42
|
-
esArchivoArtefacto,
|
|
43
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* artefactos-python.js — fuente única de verdad para los artefactos
|
|
5
|
+
* Python que NO deben distribuirse en el paquete npm.
|
|
6
|
+
*
|
|
7
|
+
* Por qué existe:
|
|
8
|
+
* Tanto `scripts/limpiar-artefactos-python.js` (prepack) como
|
|
9
|
+
* `scripts/publicar.js` (copiarDir hacia tmpDir del mirror GH)
|
|
10
|
+
* necesitan saber qué directorios y extensiones excluir. Tener la
|
|
11
|
+
* lista hardcodeada en cada archivo es violación DRY de conocimiento
|
|
12
|
+
* — agregar `.pyd` o `.mypy_cache_v2` exigiría editar dos archivos
|
|
13
|
+
* y mantenerlos sincronizados.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const DIRS_ARTEFACTOS_PYTHON = Object.freeze(new Set([
|
|
17
|
+
'__pycache__',
|
|
18
|
+
'.pytest_cache',
|
|
19
|
+
'.mypy_cache',
|
|
20
|
+
'.ruff_cache',
|
|
21
|
+
]));
|
|
22
|
+
|
|
23
|
+
const EXTS_ARTEFACTOS_PYTHON = Object.freeze(new Set([
|
|
24
|
+
'.pyc',
|
|
25
|
+
'.pyo',
|
|
26
|
+
]));
|
|
27
|
+
|
|
28
|
+
function esDirArtefacto(nombre) {
|
|
29
|
+
return DIRS_ARTEFACTOS_PYTHON.has(nombre);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function esArchivoArtefacto(nombre) {
|
|
33
|
+
const dot = nombre.lastIndexOf('.');
|
|
34
|
+
if (dot < 0) return false;
|
|
35
|
+
return EXTS_ARTEFACTOS_PYTHON.has(nombre.slice(dot).toLowerCase());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
module.exports = {
|
|
39
|
+
DIRS_ARTEFACTOS_PYTHON,
|
|
40
|
+
EXTS_ARTEFACTOS_PYTHON,
|
|
41
|
+
esDirArtefacto,
|
|
42
|
+
esArchivoArtefacto,
|
|
43
|
+
};
|
|
@@ -1,160 +1,160 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* benchmark-metrics.js — Métricas de retrieval para benchmark de memoria SWL.
|
|
5
|
-
*
|
|
6
|
-
* Patrón adoptado de `temp/agentmemory-main/benchmark/longmemeval-bench.ts`.
|
|
7
|
-
* Funciones puras zero-deps. Adaptado a IDs SWL (aprendizaje, sesion,
|
|
8
|
-
* instinto) en lugar de session_id de agentmemory.
|
|
9
|
-
*
|
|
10
|
-
* Métricas:
|
|
11
|
-
* - recallAt(k): 1.0 si al menos un gold ID está en los top-k, sino 0.0
|
|
12
|
-
* - precisionAt(k): proporción de top-k que son gold
|
|
13
|
-
* - mrr: Mean Reciprocal Rank (1/rank del primer gold encontrado)
|
|
14
|
-
* - ndcgAt(k): Normalized Discounted Cumulative Gain
|
|
15
|
-
*
|
|
16
|
-
* @module scripts/lib/benchmark-metrics
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
20
|
-
|
|
21
|
-
function asSet(arr) {
|
|
22
|
-
return new Set(Array.isArray(arr) ? arr : []);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// ── métricas individuales ─────────────────────────────────────────────────────
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Recall @ k: 1.0 si ALGÚN id gold está en los primeros k retrieved.
|
|
29
|
-
*
|
|
30
|
-
* @param {string[]} retrievedIds - IDs ordenados (mejor primero).
|
|
31
|
-
* @param {string[]} goldIds - IDs correctos esperados.
|
|
32
|
-
* @param {number} k
|
|
33
|
-
* @returns {number} 0 o 1
|
|
34
|
-
*/
|
|
35
|
-
function recallAt(retrievedIds, goldIds, k) {
|
|
36
|
-
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds)) return 0;
|
|
37
|
-
const topK = new Set(retrievedIds.slice(0, k));
|
|
38
|
-
return goldIds.some(g => topK.has(g)) ? 1.0 : 0.0;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* Precision @ k: proporción de los primeros k retrieved que son gold.
|
|
43
|
-
*
|
|
44
|
-
* @param {string[]} retrievedIds
|
|
45
|
-
* @param {string[]} goldIds
|
|
46
|
-
* @param {number} k
|
|
47
|
-
* @returns {number} en [0, 1]
|
|
48
|
-
*/
|
|
49
|
-
function precisionAt(retrievedIds, goldIds, k) {
|
|
50
|
-
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds) || k <= 0) return 0;
|
|
51
|
-
const goldSet = asSet(goldIds);
|
|
52
|
-
const topK = retrievedIds.slice(0, k);
|
|
53
|
-
if (topK.length === 0) return 0;
|
|
54
|
-
const hits = topK.filter(id => goldSet.has(id)).length;
|
|
55
|
-
return hits / topK.length;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/**
|
|
59
|
-
* Mean Reciprocal Rank: 1/rank del primer gold encontrado, o 0 si ninguno.
|
|
60
|
-
*
|
|
61
|
-
* @param {string[]} retrievedIds
|
|
62
|
-
* @param {string[]} goldIds
|
|
63
|
-
* @returns {number} en [0, 1]
|
|
64
|
-
*/
|
|
65
|
-
function mrr(retrievedIds, goldIds) {
|
|
66
|
-
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds)) return 0;
|
|
67
|
-
const goldSet = asSet(goldIds);
|
|
68
|
-
for (let i = 0; i < retrievedIds.length; i++) {
|
|
69
|
-
if (goldSet.has(retrievedIds[i])) {
|
|
70
|
-
return 1 / (i + 1);
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
return 0;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
function dcg(relevancias, k) {
|
|
77
|
-
let suma = 0;
|
|
78
|
-
for (let i = 0; i < Math.min(k, relevancias.length); i++) {
|
|
79
|
-
suma += (relevancias[i] ? 1 : 0) / Math.log2(i + 2);
|
|
80
|
-
}
|
|
81
|
-
return suma;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* Normalized Discounted Cumulative Gain @ k.
|
|
86
|
-
*
|
|
87
|
-
* @param {string[]} retrievedIds
|
|
88
|
-
* @param {string[]} goldIds
|
|
89
|
-
* @param {number} k
|
|
90
|
-
* @returns {number} en [0, 1]
|
|
91
|
-
*/
|
|
92
|
-
function ndcgAt(retrievedIds, goldIds, k) {
|
|
93
|
-
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds) || k <= 0) return 0;
|
|
94
|
-
const goldSet = asSet(goldIds);
|
|
95
|
-
const rels = retrievedIds.slice(0, k).map(id => goldSet.has(id));
|
|
96
|
-
const idealRels = Array.from({ length: Math.min(k, goldSet.size) }, () => true);
|
|
97
|
-
const idealDCG = dcg(idealRels, k);
|
|
98
|
-
if (idealDCG === 0) return 0;
|
|
99
|
-
return dcg(rels, k) / idealDCG;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// ── agregados ─────────────────────────────────────────────────────────────────
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* Calcula el conjunto completo de métricas para una query.
|
|
106
|
-
*
|
|
107
|
-
* @param {string[]} retrievedIds
|
|
108
|
-
* @param {string[]} goldIds
|
|
109
|
-
* @returns {{ recall_at_5, recall_at_10, recall_at_20, mrr, ndcg_at_10, precision_at_5 }}
|
|
110
|
-
*/
|
|
111
|
-
function calcularMetricas(retrievedIds, goldIds) {
|
|
112
|
-
return {
|
|
113
|
-
recall_at_5: recallAt(retrievedIds, goldIds, 5),
|
|
114
|
-
recall_at_10: recallAt(retrievedIds, goldIds, 10),
|
|
115
|
-
recall_at_20: recallAt(retrievedIds, goldIds, 20),
|
|
116
|
-
mrr: mrr(retrievedIds, goldIds),
|
|
117
|
-
ndcg_at_10: ndcgAt(retrievedIds, goldIds, 10),
|
|
118
|
-
precision_at_5: precisionAt(retrievedIds, goldIds, 5),
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Promedia métricas sobre múltiples queries.
|
|
124
|
-
*
|
|
125
|
-
* @param {Array<object>} resultados - Array de objetos producidos por calcularMetricas().
|
|
126
|
-
* @returns {object} Promedios con campo `n` (cantidad de queries).
|
|
127
|
-
*/
|
|
128
|
-
function promediar(resultados) {
|
|
129
|
-
if (!Array.isArray(resultados) || resultados.length === 0) {
|
|
130
|
-
return {
|
|
131
|
-
n: 0,
|
|
132
|
-
recall_at_5: 0,
|
|
133
|
-
recall_at_10: 0,
|
|
134
|
-
recall_at_20: 0,
|
|
135
|
-
mrr: 0,
|
|
136
|
-
ndcg_at_10: 0,
|
|
137
|
-
precision_at_5: 0,
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const claves = ['recall_at_5', 'recall_at_10', 'recall_at_20', 'mrr', 'ndcg_at_10', 'precision_at_5'];
|
|
142
|
-
const promedio = { n: resultados.length };
|
|
143
|
-
for (const k of claves) {
|
|
144
|
-
const sum = resultados.reduce((a, r) => a + (r[k] || 0), 0);
|
|
145
|
-
promedio[k] = sum / resultados.length;
|
|
146
|
-
}
|
|
147
|
-
return promedio;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// ── exports ───────────────────────────────────────────────────────────────────
|
|
151
|
-
|
|
152
|
-
module.exports = {
|
|
153
|
-
recallAt,
|
|
154
|
-
precisionAt,
|
|
155
|
-
mrr,
|
|
156
|
-
ndcgAt,
|
|
157
|
-
dcg,
|
|
158
|
-
calcularMetricas,
|
|
159
|
-
promediar,
|
|
160
|
-
};
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* benchmark-metrics.js — Métricas de retrieval para benchmark de memoria SWL.
|
|
5
|
+
*
|
|
6
|
+
* Patrón adoptado de `temp/agentmemory-main/benchmark/longmemeval-bench.ts`.
|
|
7
|
+
* Funciones puras zero-deps. Adaptado a IDs SWL (aprendizaje, sesion,
|
|
8
|
+
* instinto) en lugar de session_id de agentmemory.
|
|
9
|
+
*
|
|
10
|
+
* Métricas:
|
|
11
|
+
* - recallAt(k): 1.0 si al menos un gold ID está en los top-k, sino 0.0
|
|
12
|
+
* - precisionAt(k): proporción de top-k que son gold
|
|
13
|
+
* - mrr: Mean Reciprocal Rank (1/rank del primer gold encontrado)
|
|
14
|
+
* - ndcgAt(k): Normalized Discounted Cumulative Gain
|
|
15
|
+
*
|
|
16
|
+
* @module scripts/lib/benchmark-metrics
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
// ── helpers ───────────────────────────────────────────────────────────────────
|
|
20
|
+
|
|
21
|
+
function asSet(arr) {
|
|
22
|
+
return new Set(Array.isArray(arr) ? arr : []);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// ── métricas individuales ─────────────────────────────────────────────────────
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Recall @ k: 1.0 si ALGÚN id gold está en los primeros k retrieved.
|
|
29
|
+
*
|
|
30
|
+
* @param {string[]} retrievedIds - IDs ordenados (mejor primero).
|
|
31
|
+
* @param {string[]} goldIds - IDs correctos esperados.
|
|
32
|
+
* @param {number} k
|
|
33
|
+
* @returns {number} 0 o 1
|
|
34
|
+
*/
|
|
35
|
+
function recallAt(retrievedIds, goldIds, k) {
|
|
36
|
+
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds)) return 0;
|
|
37
|
+
const topK = new Set(retrievedIds.slice(0, k));
|
|
38
|
+
return goldIds.some(g => topK.has(g)) ? 1.0 : 0.0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Precision @ k: proporción de los primeros k retrieved que son gold.
|
|
43
|
+
*
|
|
44
|
+
* @param {string[]} retrievedIds
|
|
45
|
+
* @param {string[]} goldIds
|
|
46
|
+
* @param {number} k
|
|
47
|
+
* @returns {number} en [0, 1]
|
|
48
|
+
*/
|
|
49
|
+
function precisionAt(retrievedIds, goldIds, k) {
|
|
50
|
+
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds) || k <= 0) return 0;
|
|
51
|
+
const goldSet = asSet(goldIds);
|
|
52
|
+
const topK = retrievedIds.slice(0, k);
|
|
53
|
+
if (topK.length === 0) return 0;
|
|
54
|
+
const hits = topK.filter(id => goldSet.has(id)).length;
|
|
55
|
+
return hits / topK.length;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Mean Reciprocal Rank: 1/rank del primer gold encontrado, o 0 si ninguno.
|
|
60
|
+
*
|
|
61
|
+
* @param {string[]} retrievedIds
|
|
62
|
+
* @param {string[]} goldIds
|
|
63
|
+
* @returns {number} en [0, 1]
|
|
64
|
+
*/
|
|
65
|
+
function mrr(retrievedIds, goldIds) {
|
|
66
|
+
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds)) return 0;
|
|
67
|
+
const goldSet = asSet(goldIds);
|
|
68
|
+
for (let i = 0; i < retrievedIds.length; i++) {
|
|
69
|
+
if (goldSet.has(retrievedIds[i])) {
|
|
70
|
+
return 1 / (i + 1);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function dcg(relevancias, k) {
|
|
77
|
+
let suma = 0;
|
|
78
|
+
for (let i = 0; i < Math.min(k, relevancias.length); i++) {
|
|
79
|
+
suma += (relevancias[i] ? 1 : 0) / Math.log2(i + 2);
|
|
80
|
+
}
|
|
81
|
+
return suma;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Normalized Discounted Cumulative Gain @ k.
|
|
86
|
+
*
|
|
87
|
+
* @param {string[]} retrievedIds
|
|
88
|
+
* @param {string[]} goldIds
|
|
89
|
+
* @param {number} k
|
|
90
|
+
* @returns {number} en [0, 1]
|
|
91
|
+
*/
|
|
92
|
+
function ndcgAt(retrievedIds, goldIds, k) {
|
|
93
|
+
if (!Array.isArray(retrievedIds) || !Array.isArray(goldIds) || k <= 0) return 0;
|
|
94
|
+
const goldSet = asSet(goldIds);
|
|
95
|
+
const rels = retrievedIds.slice(0, k).map(id => goldSet.has(id));
|
|
96
|
+
const idealRels = Array.from({ length: Math.min(k, goldSet.size) }, () => true);
|
|
97
|
+
const idealDCG = dcg(idealRels, k);
|
|
98
|
+
if (idealDCG === 0) return 0;
|
|
99
|
+
return dcg(rels, k) / idealDCG;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ── agregados ─────────────────────────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Calcula el conjunto completo de métricas para una query.
|
|
106
|
+
*
|
|
107
|
+
* @param {string[]} retrievedIds
|
|
108
|
+
* @param {string[]} goldIds
|
|
109
|
+
* @returns {{ recall_at_5, recall_at_10, recall_at_20, mrr, ndcg_at_10, precision_at_5 }}
|
|
110
|
+
*/
|
|
111
|
+
function calcularMetricas(retrievedIds, goldIds) {
|
|
112
|
+
return {
|
|
113
|
+
recall_at_5: recallAt(retrievedIds, goldIds, 5),
|
|
114
|
+
recall_at_10: recallAt(retrievedIds, goldIds, 10),
|
|
115
|
+
recall_at_20: recallAt(retrievedIds, goldIds, 20),
|
|
116
|
+
mrr: mrr(retrievedIds, goldIds),
|
|
117
|
+
ndcg_at_10: ndcgAt(retrievedIds, goldIds, 10),
|
|
118
|
+
precision_at_5: precisionAt(retrievedIds, goldIds, 5),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Promedia métricas sobre múltiples queries.
|
|
124
|
+
*
|
|
125
|
+
* @param {Array<object>} resultados - Array de objetos producidos por calcularMetricas().
|
|
126
|
+
* @returns {object} Promedios con campo `n` (cantidad de queries).
|
|
127
|
+
*/
|
|
128
|
+
function promediar(resultados) {
|
|
129
|
+
if (!Array.isArray(resultados) || resultados.length === 0) {
|
|
130
|
+
return {
|
|
131
|
+
n: 0,
|
|
132
|
+
recall_at_5: 0,
|
|
133
|
+
recall_at_10: 0,
|
|
134
|
+
recall_at_20: 0,
|
|
135
|
+
mrr: 0,
|
|
136
|
+
ndcg_at_10: 0,
|
|
137
|
+
precision_at_5: 0,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const claves = ['recall_at_5', 'recall_at_10', 'recall_at_20', 'mrr', 'ndcg_at_10', 'precision_at_5'];
|
|
142
|
+
const promedio = { n: resultados.length };
|
|
143
|
+
for (const k of claves) {
|
|
144
|
+
const sum = resultados.reduce((a, r) => a + (r[k] || 0), 0);
|
|
145
|
+
promedio[k] = sum / resultados.length;
|
|
146
|
+
}
|
|
147
|
+
return promedio;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ── exports ───────────────────────────────────────────────────────────────────
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
recallAt,
|
|
154
|
+
precisionAt,
|
|
155
|
+
mrr,
|
|
156
|
+
ndcgAt,
|
|
157
|
+
dcg,
|
|
158
|
+
calcularMetricas,
|
|
159
|
+
promediar,
|
|
160
|
+
};
|