@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.
Files changed (136) hide show
  1. package/CLAUDE.md +3 -3
  2. package/README.md +561 -560
  3. package/agentes/nemesis-auditor-swl.md +161 -161
  4. package/bin/swl-mcp-server.js +49 -22
  5. package/bin/swl-ses.js +74 -0
  6. package/comandos/swl/.evolved.json +22 -22
  7. package/comandos/swl/contribuir.md +233 -233
  8. package/comandos/swl/ejecutar-fase.md +33 -4
  9. package/comandos/swl/metricas.md +72 -0
  10. package/comandos/swl/nemesis.md +122 -122
  11. package/gateway/lib/event-channel.js +191 -191
  12. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  13. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  14. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  15. package/habilidades/discutir-fase/SKILL.md +50 -2
  16. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  17. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  18. package/habilidades/ejecutar-task-iterativo/SKILL.md +278 -0
  19. package/habilidades/eval-framework/SKILL.md +212 -212
  20. package/habilidades/feynman-auditor-swl/SKILL.md +123 -123
  21. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -108
  22. package/habilidades/harness-claude-code/SKILL.md +299 -299
  23. package/habilidades/infra-github-actions/SKILL.md +166 -166
  24. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  25. package/habilidades/manejo-errores/.evolved.json +8 -8
  26. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  27. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  28. package/habilidades/patrones-python/SKILL.md +229 -229
  29. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  30. package/habilidades/planear-fase/SKILL.md +319 -319
  31. package/habilidades/protocolo-revision-swl/SKILL.md +276 -0
  32. package/habilidades/release-semver/.evolved.json +8 -8
  33. package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -166
  34. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -147
  35. package/habilidades/testing-python/SKILL.md +340 -340
  36. package/habilidades/verificar-trabajo/SKILL.md +49 -5
  37. package/habilidades/web-fetcher-routing/SKILL.md +75 -75
  38. package/hooks/claudemd-bloat-detector.js +161 -161
  39. package/hooks/lib/agent-routing.js +107 -107
  40. package/hooks/lib/auto-consolidator.js +335 -335
  41. package/hooks/lib/error-classifier.js +308 -308
  42. package/hooks/lib/merkle-audit.js +96 -96
  43. package/hooks/lib/provenance-tracker.js +191 -191
  44. package/hooks/lib/rate-limit-tracker.js +253 -253
  45. package/hooks/lib/resource-quota.js +122 -122
  46. package/hooks/lib/retry-jitter.js +165 -165
  47. package/hooks/lib/security-net.js +201 -201
  48. package/hooks/lib/skill-auditor.js +588 -588
  49. package/hooks/lib/sync-status.js +228 -228
  50. package/hooks/lib/taint-tracker.js +107 -107
  51. package/hooks/lib/text-similarity.js +241 -241
  52. package/hooks/lib/toon-compressor.js +245 -245
  53. package/hooks/registro-turnos.js +209 -209
  54. package/hooks/sugerir-regenerar-inventario.js +170 -170
  55. package/hooks/validar-formato-post-subagente.js +140 -140
  56. package/hooks/validar-memoria-hook.js +218 -218
  57. package/instintos/prompt-appendices.yaml +57 -57
  58. package/manifiestos/agent-output-schemas.json +57 -57
  59. package/manifiestos/modulos.json +1321 -1262
  60. package/manifiestos/perfiles.json +2 -1
  61. package/manifiestos/skills-lock.json +1114 -1114
  62. package/package.json +3 -3
  63. package/plantillas/auditor-veto-template.md +105 -105
  64. package/plantillas/github-workflows/README.md +47 -47
  65. package/plantillas/github-workflows/release-please.yml +44 -44
  66. package/plantillas/github-workflows/swl-ci.yml +107 -107
  67. package/plantillas/github-workflows/swl-security.yml +51 -51
  68. package/plugin.json +351 -343
  69. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  70. package/reglas/arreglar-al-detectar.md +147 -147
  71. package/reglas/fragmentos-compartidos.md +152 -152
  72. package/reglas/harness-claude-code.md +213 -213
  73. package/reglas/usar-context7.md +226 -226
  74. package/schemas/diary-entry.schema.json +80 -80
  75. package/scripts/audit-tools/audit-history.js +330 -330
  76. package/scripts/audit-tools/bundle-tracker.js +290 -290
  77. package/scripts/audit-tools/canary-monitor.js +352 -352
  78. package/scripts/audit-tools/code-profiler.js +605 -605
  79. package/scripts/audit-tools/dep-doctor.js +320 -320
  80. package/scripts/audit-tools/env-validator.js +206 -206
  81. package/scripts/audit-tools/lib/fs-walk.js +48 -48
  82. package/scripts/audit-tools/lib/output.js +23 -23
  83. package/scripts/audit-tools/migration-checker.js +392 -392
  84. package/scripts/audit-tools/pentest-scanner.js +1436 -1436
  85. package/scripts/benchmark-memoria.js +167 -167
  86. package/scripts/configurar-branch-protection.js +418 -418
  87. package/scripts/derivar-feature-list.js +489 -0
  88. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  89. package/scripts/doctor.js +31 -4
  90. package/scripts/field-report.js +199 -199
  91. package/scripts/generar-checklists-consolidados.js +273 -273
  92. package/scripts/generar-inventario.js +420 -420
  93. package/scripts/generar-matriz-lenguajes.js +271 -271
  94. package/scripts/instalador.js +56 -5
  95. package/scripts/lib/artefactos-python.js +43 -43
  96. package/scripts/lib/benchmark-metrics.js +160 -160
  97. package/scripts/lib/budget-enforcer.js +252 -252
  98. package/scripts/lib/configurar-ci.js +380 -380
  99. package/scripts/lib/contadores-inventario.js +217 -217
  100. package/scripts/lib/detectar-runtime.js +75 -9
  101. package/scripts/lib/detectar-stack-detallado.js +307 -307
  102. package/scripts/lib/diary-entry.js +234 -234
  103. package/scripts/lib/estado.js +13 -1
  104. package/scripts/lib/eval-metrics-store.js +218 -218
  105. package/scripts/lib/eval-quality.js +171 -171
  106. package/scripts/lib/eval-schemas.js +144 -144
  107. package/scripts/lib/eval-self-correct.js +106 -106
  108. package/scripts/lib/eval-validator.js +185 -185
  109. package/scripts/lib/expandir-targets.js +71 -0
  110. package/scripts/lib/jaccard-similarity.js +98 -98
  111. package/scripts/lib/longmemeval-runner.js +125 -125
  112. package/scripts/lib/manifiestos.js +42 -1
  113. package/scripts/lib/npm-version.js +261 -261
  114. package/scripts/lib/paquetes-conocidos.js +50 -50
  115. package/scripts/lib/parsear-opciones.js +3 -0
  116. package/scripts/lib/prompt-builder.js +264 -264
  117. package/scripts/lib/rrf-fusion.js +175 -175
  118. package/scripts/lib/scoring-instintos.js +277 -277
  119. package/scripts/lib/semantic-search.js +252 -252
  120. package/scripts/lib/toml-merge.js +204 -0
  121. package/scripts/lib/transformadores/base.js +43 -9
  122. package/scripts/lib/transformadores/codex.js +375 -115
  123. package/scripts/lib/transformadores/cursor.js +359 -0
  124. package/scripts/lib/transformadores/index.js +2 -0
  125. package/scripts/limpiar-artefactos-python.js +131 -131
  126. package/scripts/mcp-server/README.md +122 -80
  127. package/scripts/mcp-server/auth.js +105 -0
  128. package/scripts/mcp-server/cache.js +106 -0
  129. package/scripts/mcp-server/handlers.js +386 -206
  130. package/scripts/mcp-server/telemetry.js +78 -0
  131. package/scripts/migrar-csv-a-array.js +168 -168
  132. package/scripts/migrar-fase-dominio.js +201 -201
  133. package/scripts/publicar.js +511 -511
  134. package/scripts/run-eval.js +141 -141
  135. package/scripts/validar-manifest.js +231 -195
  136. package/scripts/validar-userland-vacio.js +110 -110
@@ -1,98 +1,98 @@
1
- 'use strict';
2
-
3
- /**
4
- * jaccard-similarity.js — Métrica de Jaccard sobre conjuntos de tokens.
5
- *
6
- * Patrón adoptado de `temp/agentmemory-main/src/functions/auto-forget.ts`
7
- * para detectar memorias contradictorias/duplicadas con vocabulario compartido.
8
- *
9
- * Jaccard(A, B) = |A ∩ B| / |A ∪ B|
10
- *
11
- * Propiedades:
12
- * - Rango [0, 1]: 0 = sin overlap, 1 = idénticos.
13
- * - Simétrico: J(A, B) = J(B, A).
14
- * - Independiente de longitudes absolutas (ambos cortos pueden ser 1.0).
15
- *
16
- * Sin dependencias — Node stdlib only. Funciones puras.
17
- *
18
- * @module scripts/lib/jaccard-similarity
19
- */
20
-
21
- // ── constantes ────────────────────────────────────────────────────────────────
22
-
23
- /** Longitud mínima de un token para ser considerado significativo. */
24
- const MIN_TOKEN_LENGTH = 3;
25
-
26
- /** Stop words en español que se excluyen del análisis. */
27
- const STOP_WORDS = new Set([
28
- 'que', 'los', 'las', 'del', 'una', 'por', 'con', 'para', 'como',
29
- 'sin', 'mas', 'sus', 'lo', 'le', 'la', 'el', 'al', 'no', 'es',
30
- 'se', 'de', 'en', 'un', 'a', 'y', 'o', 'pero', 'cuando',
31
- 'donde', 'porque', 'desde', 'hasta', 'sobre', 'bajo', 'entre',
32
- 'esta', 'este', 'esto', 'esa', 'ese', 'eso', 'tras', 'durante',
33
- 'mediante', 'segun', 'asi', 'tan', 'ya', 'aun', 'aunque',
34
- // English equivalents (frequently mixed in technical text)
35
- 'the', 'and', 'for', 'with', 'this', 'that', 'have', 'from',
36
- 'are', 'was', 'will', 'not', 'has', 'had', 'but', 'can',
37
- ]);
38
-
39
- // ── funciones puras ───────────────────────────────────────────────────────────
40
-
41
- /**
42
- * Convierte un texto en un Set de tokens significativos (lowercase, sin stop
43
- * words, longitud mínima). Preserva acentos.
44
- *
45
- * @param {string} text
46
- * @returns {Set<string>}
47
- */
48
- function tokenize(text) {
49
- if (!text || typeof text !== 'string') return new Set();
50
- return new Set(
51
- String(text)
52
- .toLowerCase()
53
- .replace(/[`*_~\[\](){}<>#"'\-.,;:!?\/\\]/g, ' ')
54
- .split(/\s+/)
55
- .filter(t => t.length >= MIN_TOKEN_LENGTH && !STOP_WORDS.has(t)),
56
- );
57
- }
58
-
59
- /**
60
- * Jaccard similarity entre dos Sets.
61
- *
62
- * @param {Set} setA
63
- * @param {Set} setB
64
- * @returns {number} en [0, 1]
65
- */
66
- function jaccard(setA, setB) {
67
- if (!(setA instanceof Set) || !(setB instanceof Set)) return 0;
68
- if (setA.size === 0 && setB.size === 0) return 0;
69
- if (setA.size === 0 || setB.size === 0) return 0;
70
-
71
- let intersection = 0;
72
- for (const token of setA) {
73
- if (setB.has(token)) intersection++;
74
- }
75
- const union = setA.size + setB.size - intersection;
76
- return union === 0 ? 0 : intersection / union;
77
- }
78
-
79
- /**
80
- * Conveniencia: jaccard sobre dos textos.
81
- *
82
- * @param {string} a
83
- * @param {string} b
84
- * @returns {number} en [0, 1]
85
- */
86
- function similarity(a, b) {
87
- return jaccard(tokenize(a), tokenize(b));
88
- }
89
-
90
- // ── exports ───────────────────────────────────────────────────────────────────
91
-
92
- module.exports = {
93
- tokenize,
94
- jaccard,
95
- similarity,
96
- MIN_TOKEN_LENGTH,
97
- STOP_WORDS,
98
- };
1
+ 'use strict';
2
+
3
+ /**
4
+ * jaccard-similarity.js — Métrica de Jaccard sobre conjuntos de tokens.
5
+ *
6
+ * Patrón adoptado de `temp/agentmemory-main/src/functions/auto-forget.ts`
7
+ * para detectar memorias contradictorias/duplicadas con vocabulario compartido.
8
+ *
9
+ * Jaccard(A, B) = |A ∩ B| / |A ∪ B|
10
+ *
11
+ * Propiedades:
12
+ * - Rango [0, 1]: 0 = sin overlap, 1 = idénticos.
13
+ * - Simétrico: J(A, B) = J(B, A).
14
+ * - Independiente de longitudes absolutas (ambos cortos pueden ser 1.0).
15
+ *
16
+ * Sin dependencias — Node stdlib only. Funciones puras.
17
+ *
18
+ * @module scripts/lib/jaccard-similarity
19
+ */
20
+
21
+ // ── constantes ────────────────────────────────────────────────────────────────
22
+
23
+ /** Longitud mínima de un token para ser considerado significativo. */
24
+ const MIN_TOKEN_LENGTH = 3;
25
+
26
+ /** Stop words en español que se excluyen del análisis. */
27
+ const STOP_WORDS = new Set([
28
+ 'que', 'los', 'las', 'del', 'una', 'por', 'con', 'para', 'como',
29
+ 'sin', 'mas', 'sus', 'lo', 'le', 'la', 'el', 'al', 'no', 'es',
30
+ 'se', 'de', 'en', 'un', 'a', 'y', 'o', 'pero', 'cuando',
31
+ 'donde', 'porque', 'desde', 'hasta', 'sobre', 'bajo', 'entre',
32
+ 'esta', 'este', 'esto', 'esa', 'ese', 'eso', 'tras', 'durante',
33
+ 'mediante', 'segun', 'asi', 'tan', 'ya', 'aun', 'aunque',
34
+ // English equivalents (frequently mixed in technical text)
35
+ 'the', 'and', 'for', 'with', 'this', 'that', 'have', 'from',
36
+ 'are', 'was', 'will', 'not', 'has', 'had', 'but', 'can',
37
+ ]);
38
+
39
+ // ── funciones puras ───────────────────────────────────────────────────────────
40
+
41
+ /**
42
+ * Convierte un texto en un Set de tokens significativos (lowercase, sin stop
43
+ * words, longitud mínima). Preserva acentos.
44
+ *
45
+ * @param {string} text
46
+ * @returns {Set<string>}
47
+ */
48
+ function tokenize(text) {
49
+ if (!text || typeof text !== 'string') return new Set();
50
+ return new Set(
51
+ String(text)
52
+ .toLowerCase()
53
+ .replace(/[`*_~\[\](){}<>#"'\-.,;:!?\/\\]/g, ' ')
54
+ .split(/\s+/)
55
+ .filter(t => t.length >= MIN_TOKEN_LENGTH && !STOP_WORDS.has(t)),
56
+ );
57
+ }
58
+
59
+ /**
60
+ * Jaccard similarity entre dos Sets.
61
+ *
62
+ * @param {Set} setA
63
+ * @param {Set} setB
64
+ * @returns {number} en [0, 1]
65
+ */
66
+ function jaccard(setA, setB) {
67
+ if (!(setA instanceof Set) || !(setB instanceof Set)) return 0;
68
+ if (setA.size === 0 && setB.size === 0) return 0;
69
+ if (setA.size === 0 || setB.size === 0) return 0;
70
+
71
+ let intersection = 0;
72
+ for (const token of setA) {
73
+ if (setB.has(token)) intersection++;
74
+ }
75
+ const union = setA.size + setB.size - intersection;
76
+ return union === 0 ? 0 : intersection / union;
77
+ }
78
+
79
+ /**
80
+ * Conveniencia: jaccard sobre dos textos.
81
+ *
82
+ * @param {string} a
83
+ * @param {string} b
84
+ * @returns {number} en [0, 1]
85
+ */
86
+ function similarity(a, b) {
87
+ return jaccard(tokenize(a), tokenize(b));
88
+ }
89
+
90
+ // ── exports ───────────────────────────────────────────────────────────────────
91
+
92
+ module.exports = {
93
+ tokenize,
94
+ jaccard,
95
+ similarity,
96
+ MIN_TOKEN_LENGTH,
97
+ STOP_WORDS,
98
+ };
@@ -1,125 +1,125 @@
1
- 'use strict';
2
-
3
- /**
4
- * longmemeval-runner.js — Adapter que ejecuta queries del benchmark contra
5
- * `hooks/lib/memory-search` y devuelve métricas.
6
- *
7
- * Patrón adoptado de `temp/agentmemory-main/benchmark/longmemeval-bench.ts`.
8
- * Adaptado: en lugar de cargar haystack desde el dataset, usa el estado
9
- * actual del proyecto SWL (APRENDIZAJES.md, sesiones, instintos).
10
- *
11
- * El dataset es un JSONL donde cada línea es:
12
- * {
13
- * "question_id": "q-001",
14
- * "question": "texto libre de la query",
15
- * "gold_ids": ["apr-N", "ses-YYYY-MM-DD-HHMM"],
16
- * "category": "decision" | "patron" | "anti-patron" | "gotcha" | ...,
17
- * "status": "real" | "placeholder"
18
- * }
19
- *
20
- * @module scripts/lib/longmemeval-runner
21
- */
22
-
23
- const fs = require('fs');
24
- const path = require('path');
25
-
26
- const memorySearch = require('../../hooks/lib/memory-search');
27
- const benchmarkMetrics = require('./benchmark-metrics');
28
-
29
- // ── parser de dataset ─────────────────────────────────────────────────────────
30
-
31
- /**
32
- * Parsea un archivo JSONL del dataset.
33
- * @param {string} ruta
34
- * @returns {object[]}
35
- */
36
- function leerDataset(ruta) {
37
- if (!fs.existsSync(ruta)) {
38
- throw new Error(`Dataset no encontrado: ${ruta}`);
39
- }
40
- const contenido = fs.readFileSync(ruta, 'utf8');
41
- const entries = [];
42
- let lineNum = 0;
43
- for (const linea of contenido.split('\n')) {
44
- lineNum++;
45
- if (!linea.trim()) continue;
46
- if (linea.trim().startsWith('//')) continue; // comentarios
47
- try {
48
- entries.push(JSON.parse(linea));
49
- } catch (err) {
50
- throw new Error(`JSONL malformado en línea ${lineNum}: ${err.message}`);
51
- }
52
- }
53
- return entries;
54
- }
55
-
56
- // ── ejecución de query individual ─────────────────────────────────────────────
57
-
58
- /**
59
- * Ejecuta una query del benchmark contra memoria SWL y compara con gold.
60
- *
61
- * @param {string} baseDir - Raíz del proyecto.
62
- * @param {object} entry - Una línea del dataset.
63
- * @param {object} [opts]
64
- * @param {number} [opts.limit=20] - Top-k a recuperar.
65
- * @returns {object} Métricas + ids retrieved + entry original.
66
- */
67
- function ejecutarEntry(baseDir, entry, opts = {}) {
68
- const limit = opts.limit || 20;
69
- const inicio = Date.now();
70
- const resultados = memorySearch.search(baseDir, entry.question, { limit });
71
- const latencyMs = Date.now() - inicio;
72
-
73
- const retrievedIds = resultados.map(r => r.id);
74
- const goldIds = Array.isArray(entry.gold_ids) ? entry.gold_ids : [];
75
- const metricas = benchmarkMetrics.calcularMetricas(retrievedIds, goldIds);
76
-
77
- return {
78
- question_id: entry.question_id || 'unknown',
79
- question: entry.question,
80
- category: entry.category || null,
81
- status: entry.status || 'unknown',
82
- retrievedIds,
83
- goldIds,
84
- metricas,
85
- latencyMs,
86
- };
87
- }
88
-
89
- /**
90
- * Ejecuta el dataset completo y devuelve resultados + métricas agregadas.
91
- *
92
- * @param {string} baseDir
93
- * @param {string} datasetPath
94
- * @param {object} [opts]
95
- * @returns {{ entries: object[], promedio: object, dataset: object }}
96
- */
97
- function ejecutarDataset(baseDir, datasetPath, opts = {}) {
98
- const entries = leerDataset(datasetPath);
99
- const resultados = entries.map(e => ejecutarEntry(baseDir, e, opts));
100
- const promedio = benchmarkMetrics.promediar(resultados.map(r => r.metricas));
101
-
102
- // Estadísticas del dataset
103
- const placeholderCount = entries.filter(e => e.status === 'placeholder').length;
104
- const realCount = entries.filter(e => e.status === 'real').length;
105
- const datasetMeta = {
106
- total: entries.length,
107
- real: realCount,
108
- placeholder: placeholderCount,
109
- significativo: realCount >= 30,
110
- };
111
-
112
- return {
113
- entries: resultados,
114
- promedio,
115
- dataset: datasetMeta,
116
- };
117
- }
118
-
119
- // ── exports ───────────────────────────────────────────────────────────────────
120
-
121
- module.exports = {
122
- leerDataset,
123
- ejecutarEntry,
124
- ejecutarDataset,
125
- };
1
+ 'use strict';
2
+
3
+ /**
4
+ * longmemeval-runner.js — Adapter que ejecuta queries del benchmark contra
5
+ * `hooks/lib/memory-search` y devuelve métricas.
6
+ *
7
+ * Patrón adoptado de `temp/agentmemory-main/benchmark/longmemeval-bench.ts`.
8
+ * Adaptado: en lugar de cargar haystack desde el dataset, usa el estado
9
+ * actual del proyecto SWL (APRENDIZAJES.md, sesiones, instintos).
10
+ *
11
+ * El dataset es un JSONL donde cada línea es:
12
+ * {
13
+ * "question_id": "q-001",
14
+ * "question": "texto libre de la query",
15
+ * "gold_ids": ["apr-N", "ses-YYYY-MM-DD-HHMM"],
16
+ * "category": "decision" | "patron" | "anti-patron" | "gotcha" | ...,
17
+ * "status": "real" | "placeholder"
18
+ * }
19
+ *
20
+ * @module scripts/lib/longmemeval-runner
21
+ */
22
+
23
+ const fs = require('fs');
24
+ const path = require('path');
25
+
26
+ const memorySearch = require('../../hooks/lib/memory-search');
27
+ const benchmarkMetrics = require('./benchmark-metrics');
28
+
29
+ // ── parser de dataset ─────────────────────────────────────────────────────────
30
+
31
+ /**
32
+ * Parsea un archivo JSONL del dataset.
33
+ * @param {string} ruta
34
+ * @returns {object[]}
35
+ */
36
+ function leerDataset(ruta) {
37
+ if (!fs.existsSync(ruta)) {
38
+ throw new Error(`Dataset no encontrado: ${ruta}`);
39
+ }
40
+ const contenido = fs.readFileSync(ruta, 'utf8');
41
+ const entries = [];
42
+ let lineNum = 0;
43
+ for (const linea of contenido.split('\n')) {
44
+ lineNum++;
45
+ if (!linea.trim()) continue;
46
+ if (linea.trim().startsWith('//')) continue; // comentarios
47
+ try {
48
+ entries.push(JSON.parse(linea));
49
+ } catch (err) {
50
+ throw new Error(`JSONL malformado en línea ${lineNum}: ${err.message}`);
51
+ }
52
+ }
53
+ return entries;
54
+ }
55
+
56
+ // ── ejecución de query individual ─────────────────────────────────────────────
57
+
58
+ /**
59
+ * Ejecuta una query del benchmark contra memoria SWL y compara con gold.
60
+ *
61
+ * @param {string} baseDir - Raíz del proyecto.
62
+ * @param {object} entry - Una línea del dataset.
63
+ * @param {object} [opts]
64
+ * @param {number} [opts.limit=20] - Top-k a recuperar.
65
+ * @returns {object} Métricas + ids retrieved + entry original.
66
+ */
67
+ function ejecutarEntry(baseDir, entry, opts = {}) {
68
+ const limit = opts.limit || 20;
69
+ const inicio = Date.now();
70
+ const resultados = memorySearch.search(baseDir, entry.question, { limit });
71
+ const latencyMs = Date.now() - inicio;
72
+
73
+ const retrievedIds = resultados.map(r => r.id);
74
+ const goldIds = Array.isArray(entry.gold_ids) ? entry.gold_ids : [];
75
+ const metricas = benchmarkMetrics.calcularMetricas(retrievedIds, goldIds);
76
+
77
+ return {
78
+ question_id: entry.question_id || 'unknown',
79
+ question: entry.question,
80
+ category: entry.category || null,
81
+ status: entry.status || 'unknown',
82
+ retrievedIds,
83
+ goldIds,
84
+ metricas,
85
+ latencyMs,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Ejecuta el dataset completo y devuelve resultados + métricas agregadas.
91
+ *
92
+ * @param {string} baseDir
93
+ * @param {string} datasetPath
94
+ * @param {object} [opts]
95
+ * @returns {{ entries: object[], promedio: object, dataset: object }}
96
+ */
97
+ function ejecutarDataset(baseDir, datasetPath, opts = {}) {
98
+ const entries = leerDataset(datasetPath);
99
+ const resultados = entries.map(e => ejecutarEntry(baseDir, e, opts));
100
+ const promedio = benchmarkMetrics.promediar(resultados.map(r => r.metricas));
101
+
102
+ // Estadísticas del dataset
103
+ const placeholderCount = entries.filter(e => e.status === 'placeholder').length;
104
+ const realCount = entries.filter(e => e.status === 'real').length;
105
+ const datasetMeta = {
106
+ total: entries.length,
107
+ real: realCount,
108
+ placeholder: placeholderCount,
109
+ significativo: realCount >= 30,
110
+ };
111
+
112
+ return {
113
+ entries: resultados,
114
+ promedio,
115
+ dataset: datasetMeta,
116
+ };
117
+ }
118
+
119
+ // ── exports ───────────────────────────────────────────────────────────────────
120
+
121
+ module.exports = {
122
+ leerDataset,
123
+ ejecutarEntry,
124
+ ejecutarDataset,
125
+ };
@@ -23,6 +23,31 @@ function cargarManifiestos() {
23
23
  return { perfiles: perfiles.perfiles, modulos: modulos.modulos };
24
24
  }
25
25
 
26
+ /**
27
+ * Infiere el tipo de un archivo según su prefijo de ruta.
28
+ *
29
+ * Se usa para módulos con `tipo: "mixto"` (archivos heterogéneos en el mismo
30
+ * módulo). Sin esta función, el instalador heredaría `tipo: "mixto"` en cada
31
+ * archivo y caería al `default` del switch en `instalarArchivo()`, depositando
32
+ * todos los archivos en `rutas.base` en lugar de `rutas.agentes`, `rutas.habilidades`, etc.
33
+ *
34
+ * Bug histórico: módulo `auditoria-profunda` (Opción C, v1.4.1) declarado como
35
+ * `mixto` con archivos en 5 carpetas distintas. Sin inferencia, el smoke test
36
+ * detectó 16/409 archivos faltantes en el destino.
37
+ *
38
+ * Retorna `null` si el prefijo no se reconoce — el caller emite warning y omite.
39
+ */
40
+ function inferirTipoPorRuta(ruta) {
41
+ if (ruta.startsWith('agentes/')) return 'agentes';
42
+ if (ruta.startsWith('habilidades/')) return 'habilidades';
43
+ if (ruta.startsWith('comandos/')) return 'comandos';
44
+ if (ruta.startsWith('reglas/')) return 'reglas';
45
+ if (ruta.startsWith('hooks/')) return 'hooks';
46
+ if (ruta.startsWith('scripts/')) return 'hooks';
47
+ if (ruta.startsWith('plantillas/')) return 'plantillas';
48
+ return null;
49
+ }
50
+
26
51
  /**
27
52
  * Resuelve un perfil a lista de archivos con rutas absolutas
28
53
  */
@@ -76,10 +101,26 @@ function resolverPerfil(nombrePerfil, opciones = {}) {
76
101
 
77
102
  for (const rutaRelativa of modulo.archivos) {
78
103
  const rutaAbsoluta = path.join(RAIZ_PKG, rutaRelativa);
104
+ // Módulos tipo "mixto" agrupan archivos heterogéneos; cada archivo debe
105
+ // declarar su tipo real para que el instalador lo coloque en la carpeta
106
+ // correcta. Inferir por prefijo de ruta.
107
+ let tipoArchivo = modulo.tipo;
108
+ if (tipoArchivo === 'mixto') {
109
+ const inferido = inferirTipoPorRuta(rutaRelativa);
110
+ if (!inferido) {
111
+ warnings.push(
112
+ `Archivo "${rutaRelativa}" del módulo mixto "${nombreModulo}" ` +
113
+ `no tiene prefijo reconocible (agentes/, habilidades/, comandos/, ` +
114
+ `reglas/, hooks/, scripts/, plantillas/), omitido`
115
+ );
116
+ continue;
117
+ }
118
+ tipoArchivo = inferido;
119
+ }
79
120
  archivos.push({
80
121
  origen: rutaAbsoluta,
81
122
  rutaRelativa,
82
- tipo: modulo.tipo,
123
+ tipo: tipoArchivo,
83
124
  modulo: nombreModulo,
84
125
  });
85
126
  }