@saulwade/swl-ses 1.3.8 → 1.4.1

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 (148) hide show
  1. package/CLAUDE.md +15 -6
  2. package/README.md +15 -14
  3. package/agentes/nemesis-auditor-swl.md +161 -0
  4. package/bin/swl-mcp-server.js +187 -187
  5. package/bin/swl-webhook-server.js +198 -0
  6. package/comandos/swl/.evolved.json +22 -22
  7. package/comandos/swl/adoptar-proyecto.md +21 -1
  8. package/comandos/swl/claudemd.md +14 -1
  9. package/comandos/swl/contribuir.md +233 -233
  10. package/comandos/swl/exportar-vault.md +108 -0
  11. package/comandos/swl/nemesis.md +122 -0
  12. package/comandos/swl/nuevo-proyecto.md +24 -2
  13. package/comandos/swl/salud.md +34 -0
  14. package/comandos/swl/verificar.md +45 -0
  15. package/gateway/adapters/base.js +109 -0
  16. package/gateway/adapters/discord.js +167 -0
  17. package/gateway/adapters/email.js +221 -0
  18. package/gateway/adapters/slack.js +192 -0
  19. package/gateway/adapters/telegram.js +183 -0
  20. package/gateway/adapters/webhook.js +113 -0
  21. package/gateway/adapters/whatsapp.js +214 -0
  22. package/gateway/agent-executor.js +322 -0
  23. package/gateway/command-relay.js +271 -0
  24. package/gateway/cron/jobs.js +263 -0
  25. package/gateway/cron/scheduler.js +322 -0
  26. package/gateway/cron/store.js +335 -0
  27. package/gateway/index.js +320 -0
  28. package/gateway/lib/event-channel.js +191 -0
  29. package/gateway/session.js +131 -0
  30. package/gateway/webhook-server.js +324 -0
  31. package/habilidades/backend-production-resilience/SKILL.md +288 -288
  32. package/habilidades/benchmark-memoria/SKILL.md +186 -186
  33. package/habilidades/build-errors-nextjs/SKILL.md +55 -1
  34. package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
  35. package/habilidades/doubt-driven-review/SKILL.md +171 -171
  36. package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
  37. package/habilidades/eval-framework/SKILL.md +212 -212
  38. package/habilidades/extractor-de-aprendizajes/SKILL.md +20 -10
  39. package/habilidades/feynman-auditor-swl/SKILL.md +123 -0
  40. package/habilidades/feynman-auditor-swl/recursos/preguntas-language-agnostic.md +108 -0
  41. package/habilidades/harness-claude-code/SKILL.md +299 -299
  42. package/habilidades/infra-github-actions/SKILL.md +166 -166
  43. package/habilidades/legacy-code-rescue/SKILL.md +267 -267
  44. package/habilidades/manejo-errores/.evolved.json +8 -8
  45. package/habilidades/meta-skills-estandar/recursos/convencion-examples.md +93 -93
  46. package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -163
  47. package/habilidades/nextjs-testing/SKILL.md +89 -5
  48. package/habilidades/node-experto/SKILL.md +37 -1
  49. package/habilidades/patrones-python/SKILL.md +229 -229
  50. package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -469
  51. package/habilidades/planear-fase/SKILL.md +319 -319
  52. package/habilidades/react-experto/SKILL.md +45 -4
  53. package/habilidades/release-semver/.evolved.json +8 -8
  54. package/habilidades/state-inconsistency-auditor-swl/SKILL.md +166 -0
  55. package/habilidades/state-inconsistency-auditor-swl/recursos/coupled-state-patterns.md +147 -0
  56. package/habilidades/tdd-workflow/SKILL.md +36 -4
  57. package/habilidades/testing-python/SKILL.md +340 -340
  58. package/habilidades/web-fetcher-routing/SKILL.md +75 -0
  59. package/hooks/claudemd-bloat-detector.js +161 -161
  60. package/hooks/inyeccion-contexto.js +8 -3
  61. package/hooks/lib/agent-routing.js +107 -107
  62. package/hooks/lib/auto-consolidator.js +335 -335
  63. package/hooks/lib/error-classifier.js +308 -308
  64. package/hooks/lib/merkle-audit.js +96 -96
  65. package/hooks/lib/provenance-tracker.js +191 -191
  66. package/hooks/lib/rate-limit-ip.js +177 -0
  67. package/hooks/lib/rate-limit-tracker.js +253 -253
  68. package/hooks/lib/resource-quota.js +122 -122
  69. package/hooks/lib/retry-jitter.js +165 -165
  70. package/hooks/lib/security-net.js +201 -0
  71. package/hooks/lib/skill-auditor.js +588 -588
  72. package/hooks/lib/sync-status.js +228 -228
  73. package/hooks/lib/taint-tracker.js +107 -107
  74. package/hooks/lib/text-similarity.js +241 -241
  75. package/hooks/lib/toon-compressor.js +245 -245
  76. package/hooks/lib/webhook-dedup.js +184 -0
  77. package/hooks/lib/webhook-verify.js +123 -0
  78. package/hooks/proteccion-rutas.js +120 -15
  79. package/hooks/registro-turnos.js +209 -209
  80. package/hooks/sugerir-regenerar-inventario.js +170 -170
  81. package/hooks/validar-formato-post-subagente.js +140 -140
  82. package/hooks/validar-memoria-hook.js +218 -218
  83. package/instintos/prompt-appendices.yaml +57 -57
  84. package/manifiestos/agent-output-schemas.json +57 -57
  85. package/manifiestos/modulos.json +31 -0
  86. package/manifiestos/skills-lock.json +1114 -1093
  87. package/package.json +6 -4
  88. package/plantillas/auditor-veto-template.md +105 -105
  89. package/plantillas/github-workflows/README.md +47 -47
  90. package/plantillas/github-workflows/release-please.yml +44 -44
  91. package/plantillas/github-workflows/swl-ci.yml +107 -107
  92. package/plantillas/github-workflows/swl-security.yml +51 -51
  93. package/plugin.json +2 -2
  94. package/reglas/analisis-previo-tareas-grandes.md +172 -172
  95. package/reglas/arreglar-al-detectar.md +147 -147
  96. package/reglas/fragmentos-compartidos.md +152 -152
  97. package/reglas/harness-claude-code.md +213 -213
  98. package/reglas/usar-context7.md +226 -226
  99. package/reglas/usar-sistema-swl.md +251 -0
  100. package/schemas/diary-entry.schema.json +80 -80
  101. package/scripts/audit-tools/audit-history.js +330 -0
  102. package/scripts/audit-tools/bundle-tracker.js +290 -0
  103. package/scripts/audit-tools/canary-monitor.js +352 -0
  104. package/scripts/audit-tools/code-profiler.js +605 -0
  105. package/scripts/audit-tools/dep-doctor.js +320 -0
  106. package/scripts/audit-tools/env-validator.js +206 -0
  107. package/scripts/audit-tools/lib/fs-walk.js +48 -0
  108. package/scripts/audit-tools/lib/output.js +23 -0
  109. package/scripts/audit-tools/migration-checker.js +392 -0
  110. package/scripts/audit-tools/pentest-scanner.js +1436 -0
  111. package/scripts/benchmark-memoria.js +167 -167
  112. package/scripts/comandos/skills.js +251 -2
  113. package/scripts/configurar-branch-protection.js +418 -418
  114. package/scripts/detectar-aprendizajes-duplicados.js +151 -151
  115. package/scripts/field-report.js +199 -199
  116. package/scripts/generar-checklists-consolidados.js +273 -273
  117. package/scripts/generar-inventario.js +420 -420
  118. package/scripts/generar-matriz-lenguajes.js +271 -271
  119. package/scripts/lib/artefactos-python.js +43 -43
  120. package/scripts/lib/benchmark-metrics.js +160 -160
  121. package/scripts/lib/budget-enforcer.js +252 -252
  122. package/scripts/lib/configurar-ci.js +380 -380
  123. package/scripts/lib/contadores-inventario.js +217 -217
  124. package/scripts/lib/detectar-stack-detallado.js +307 -307
  125. package/scripts/lib/diary-entry.js +234 -234
  126. package/scripts/lib/eval-metrics-store.js +218 -218
  127. package/scripts/lib/eval-quality.js +171 -171
  128. package/scripts/lib/eval-schemas.js +144 -144
  129. package/scripts/lib/eval-self-correct.js +106 -106
  130. package/scripts/lib/eval-validator.js +185 -185
  131. package/scripts/lib/jaccard-similarity.js +98 -98
  132. package/scripts/lib/longmemeval-runner.js +125 -125
  133. package/scripts/lib/npm-version.js +261 -261
  134. package/scripts/lib/paquetes-conocidos.js +50 -50
  135. package/scripts/lib/prompt-builder.js +264 -264
  136. package/scripts/lib/rrf-fusion.js +175 -175
  137. package/scripts/lib/scoring-instintos.js +277 -277
  138. package/scripts/lib/semantic-search.js +252 -252
  139. package/scripts/limpiar-artefactos-python.js +131 -131
  140. package/scripts/mcp-server/README.md +128 -128
  141. package/scripts/mcp-server/handlers.js +206 -206
  142. package/scripts/migrar-csv-a-array.js +168 -168
  143. package/scripts/migrar-fase-dominio.js +201 -201
  144. package/scripts/publicar.js +511 -511
  145. package/scripts/run-eval.js +141 -141
  146. package/scripts/validar-manifest.js +195 -195
  147. package/scripts/validar-userland-vacio.js +110 -110
  148. package/scripts/verificar-release.js +110 -0
@@ -1,218 +1,218 @@
1
- 'use strict';
2
-
3
- /**
4
- * eval-metrics-store.js — Persistencia agregada de métricas de evaluación.
5
- *
6
- * Patrón adoptado de `temp/agentmemory-main/src/eval/metrics-store.ts`.
7
- * Adaptado a swl-ses: file-based en lugar de SQLite. Persiste:
8
- * - JSONL append-only: `.planning/evolucion/eval-results.jsonl` (cada eval individual)
9
- * - JSON agregado: `.planning/evolucion/eval-metrics.json` (totales por functionId)
10
- *
11
- * El JSONL preserva el detalle histórico (un evento por evaluación). El JSON
12
- * agregado se recalcula incrementalmente: avg latency, avg quality, success
13
- * rate por functionId. Lectura barata sin escanear todo el JSONL.
14
- *
15
- * Funciones puras donde es posible. Las funciones I/O usan escrituras atómicas
16
- * (atomicWriteJSON) para `eval-metrics.json`. El JSONL usa appendFileSync
17
- * (regla SWL: JSONL para alta frecuencia).
18
- *
19
- * @module scripts/lib/eval-metrics-store
20
- */
21
-
22
- const fs = require('fs');
23
- const path = require('path');
24
-
25
- let atomicWriteJSON;
26
- try {
27
- ({ atomicWriteJSON } = require('../../hooks/lib/atomic-write'));
28
- } catch {
29
- atomicWriteJSON = (p, o) => fs.writeFileSync(p, JSON.stringify(o, null, 2), 'utf8');
30
- }
31
-
32
- // ── constantes ────────────────────────────────────────────────────────────────
33
-
34
- const DIR_EVOLUCION = path.join('.planning', 'evolucion');
35
- const RUTA_JSONL = path.join(DIR_EVOLUCION, 'eval-results.jsonl');
36
- const RUTA_AGREGADO = path.join(DIR_EVOLUCION, 'eval-metrics.json');
37
-
38
- // ── helpers ───────────────────────────────────────────────────────────────────
39
-
40
- function asegurarDir(baseDir) {
41
- const dir = path.join(baseDir, DIR_EVOLUCION);
42
- if (!fs.existsSync(dir)) {
43
- fs.mkdirSync(dir, { recursive: true });
44
- }
45
- }
46
-
47
- function leerAgregado(baseDir) {
48
- const ruta = path.join(baseDir, RUTA_AGREGADO);
49
- if (!fs.existsSync(ruta)) return {};
50
- try {
51
- return JSON.parse(fs.readFileSync(ruta, 'utf8'));
52
- } catch {
53
- return {};
54
- }
55
- }
56
-
57
- // ── API pública ───────────────────────────────────────────────────────────────
58
-
59
- /**
60
- * Registra una evaluación completada.
61
- *
62
- * Append-only al JSONL + actualización incremental del agregado.
63
- *
64
- * @param {string} baseDir - Raíz del proyecto.
65
- * @param {object} evento
66
- * @param {string} evento.functionId - Identificador de la función evaluada.
67
- * @param {number} evento.latencyMs
68
- * @param {boolean} evento.success
69
- * @param {number} [evento.qualityScore] - en [0, 100]
70
- * @param {object} [evento.metadata]
71
- * @returns {{ recorded: boolean, error?: string }}
72
- */
73
- function registrar(baseDir, evento) {
74
- if (!evento || typeof evento.functionId !== 'string') {
75
- return { recorded: false, error: 'functionId requerido' };
76
- }
77
-
78
- asegurarDir(baseDir);
79
-
80
- const ts = new Date().toISOString();
81
- const lineaJSONL = JSON.stringify({
82
- timestamp: ts,
83
- functionId: evento.functionId,
84
- latencyMs: typeof evento.latencyMs === 'number' ? evento.latencyMs : 0,
85
- success: Boolean(evento.success),
86
- qualityScore: typeof evento.qualityScore === 'number' ? evento.qualityScore : null,
87
- metadata: evento.metadata || null,
88
- });
89
-
90
- try {
91
- fs.appendFileSync(path.join(baseDir, RUTA_JSONL), lineaJSONL + '\n', 'utf8');
92
- } catch (err) {
93
- return { recorded: false, error: 'JSONL append failed: ' + err.message };
94
- }
95
-
96
- // Actualizar agregado
97
- try {
98
- const agregado = leerAgregado(baseDir);
99
- const fid = evento.functionId;
100
- const m = agregado[fid] || {
101
- functionId: fid,
102
- totalCalls: 0,
103
- successCount: 0,
104
- failureCount: 0,
105
- avgLatencyMs: 0,
106
- avgQualityScore: 0,
107
- qualityCallCounts: 0,
108
- lastUpdatedAt: ts,
109
- };
110
-
111
- const prev = m.totalCalls;
112
- m.totalCalls += 1;
113
- m.avgLatencyMs = (m.avgLatencyMs * prev + (evento.latencyMs || 0)) / m.totalCalls;
114
- if (evento.success) m.successCount += 1;
115
- else m.failureCount += 1;
116
-
117
- if (typeof evento.qualityScore === 'number') {
118
- const prevQ = m.qualityCallCounts || 0;
119
- m.avgQualityScore = (m.avgQualityScore * prevQ + evento.qualityScore) / (prevQ + 1);
120
- m.qualityCallCounts = prevQ + 1;
121
- }
122
-
123
- m.lastUpdatedAt = ts;
124
- agregado[fid] = m;
125
- atomicWriteJSON(path.join(baseDir, RUTA_AGREGADO), agregado);
126
- } catch (err) {
127
- return { recorded: true, error: 'Aggregate update failed: ' + err.message };
128
- }
129
-
130
- return { recorded: true };
131
- }
132
-
133
- /**
134
- * Lee las métricas agregadas para un functionId específico.
135
- * @param {string} baseDir
136
- * @param {string} functionId
137
- * @returns {object|null}
138
- */
139
- function obtener(baseDir, functionId) {
140
- const agregado = leerAgregado(baseDir);
141
- return agregado[functionId] || null;
142
- }
143
-
144
- /**
145
- * Lee todas las métricas agregadas.
146
- * @param {string} baseDir
147
- * @returns {object[]}
148
- */
149
- function obtenerTodos(baseDir) {
150
- const agregado = leerAgregado(baseDir);
151
- return Object.values(agregado);
152
- }
153
-
154
- /**
155
- * Recorre el JSONL y reconstruye el agregado desde cero.
156
- * Útil tras corrupción del agregado o auditoría histórica.
157
- *
158
- * @param {string} baseDir
159
- * @returns {{ rebuilt: number, functions: number }}
160
- */
161
- function reconstruirAgregado(baseDir) {
162
- const ruta = path.join(baseDir, RUTA_JSONL);
163
- if (!fs.existsSync(ruta)) return { rebuilt: 0, functions: 0 };
164
-
165
- const agregado = {};
166
- let lineas = 0;
167
- const contenido = fs.readFileSync(ruta, 'utf8');
168
- for (const linea of contenido.split('\n')) {
169
- if (!linea.trim()) continue;
170
- let evento;
171
- try { evento = JSON.parse(linea); } catch { continue; }
172
-
173
- const fid = evento.functionId;
174
- if (!fid) continue;
175
- lineas++;
176
-
177
- const m = agregado[fid] || {
178
- functionId: fid,
179
- totalCalls: 0,
180
- successCount: 0,
181
- failureCount: 0,
182
- avgLatencyMs: 0,
183
- avgQualityScore: 0,
184
- qualityCallCounts: 0,
185
- lastUpdatedAt: evento.timestamp,
186
- };
187
-
188
- const prev = m.totalCalls;
189
- m.totalCalls += 1;
190
- m.avgLatencyMs = (m.avgLatencyMs * prev + (evento.latencyMs || 0)) / m.totalCalls;
191
- if (evento.success) m.successCount += 1;
192
- else m.failureCount += 1;
193
-
194
- if (typeof evento.qualityScore === 'number') {
195
- const prevQ = m.qualityCallCounts || 0;
196
- m.avgQualityScore = (m.avgQualityScore * prevQ + evento.qualityScore) / (prevQ + 1);
197
- m.qualityCallCounts = prevQ + 1;
198
- }
199
-
200
- m.lastUpdatedAt = evento.timestamp;
201
- agregado[fid] = m;
202
- }
203
-
204
- asegurarDir(baseDir);
205
- atomicWriteJSON(path.join(baseDir, RUTA_AGREGADO), agregado);
206
- return { rebuilt: lineas, functions: Object.keys(agregado).length };
207
- }
208
-
209
- // ── exports ───────────────────────────────────────────────────────────────────
210
-
211
- module.exports = {
212
- registrar,
213
- obtener,
214
- obtenerTodos,
215
- reconstruirAgregado,
216
- RUTA_JSONL,
217
- RUTA_AGREGADO,
218
- };
1
+ 'use strict';
2
+
3
+ /**
4
+ * eval-metrics-store.js — Persistencia agregada de métricas de evaluación.
5
+ *
6
+ * Patrón adoptado de `temp/agentmemory-main/src/eval/metrics-store.ts`.
7
+ * Adaptado a swl-ses: file-based en lugar de SQLite. Persiste:
8
+ * - JSONL append-only: `.planning/evolucion/eval-results.jsonl` (cada eval individual)
9
+ * - JSON agregado: `.planning/evolucion/eval-metrics.json` (totales por functionId)
10
+ *
11
+ * El JSONL preserva el detalle histórico (un evento por evaluación). El JSON
12
+ * agregado se recalcula incrementalmente: avg latency, avg quality, success
13
+ * rate por functionId. Lectura barata sin escanear todo el JSONL.
14
+ *
15
+ * Funciones puras donde es posible. Las funciones I/O usan escrituras atómicas
16
+ * (atomicWriteJSON) para `eval-metrics.json`. El JSONL usa appendFileSync
17
+ * (regla SWL: JSONL para alta frecuencia).
18
+ *
19
+ * @module scripts/lib/eval-metrics-store
20
+ */
21
+
22
+ const fs = require('fs');
23
+ const path = require('path');
24
+
25
+ let atomicWriteJSON;
26
+ try {
27
+ ({ atomicWriteJSON } = require('../../hooks/lib/atomic-write'));
28
+ } catch {
29
+ atomicWriteJSON = (p, o) => fs.writeFileSync(p, JSON.stringify(o, null, 2), 'utf8');
30
+ }
31
+
32
+ // ── constantes ────────────────────────────────────────────────────────────────
33
+
34
+ const DIR_EVOLUCION = path.join('.planning', 'evolucion');
35
+ const RUTA_JSONL = path.join(DIR_EVOLUCION, 'eval-results.jsonl');
36
+ const RUTA_AGREGADO = path.join(DIR_EVOLUCION, 'eval-metrics.json');
37
+
38
+ // ── helpers ───────────────────────────────────────────────────────────────────
39
+
40
+ function asegurarDir(baseDir) {
41
+ const dir = path.join(baseDir, DIR_EVOLUCION);
42
+ if (!fs.existsSync(dir)) {
43
+ fs.mkdirSync(dir, { recursive: true });
44
+ }
45
+ }
46
+
47
+ function leerAgregado(baseDir) {
48
+ const ruta = path.join(baseDir, RUTA_AGREGADO);
49
+ if (!fs.existsSync(ruta)) return {};
50
+ try {
51
+ return JSON.parse(fs.readFileSync(ruta, 'utf8'));
52
+ } catch {
53
+ return {};
54
+ }
55
+ }
56
+
57
+ // ── API pública ───────────────────────────────────────────────────────────────
58
+
59
+ /**
60
+ * Registra una evaluación completada.
61
+ *
62
+ * Append-only al JSONL + actualización incremental del agregado.
63
+ *
64
+ * @param {string} baseDir - Raíz del proyecto.
65
+ * @param {object} evento
66
+ * @param {string} evento.functionId - Identificador de la función evaluada.
67
+ * @param {number} evento.latencyMs
68
+ * @param {boolean} evento.success
69
+ * @param {number} [evento.qualityScore] - en [0, 100]
70
+ * @param {object} [evento.metadata]
71
+ * @returns {{ recorded: boolean, error?: string }}
72
+ */
73
+ function registrar(baseDir, evento) {
74
+ if (!evento || typeof evento.functionId !== 'string') {
75
+ return { recorded: false, error: 'functionId requerido' };
76
+ }
77
+
78
+ asegurarDir(baseDir);
79
+
80
+ const ts = new Date().toISOString();
81
+ const lineaJSONL = JSON.stringify({
82
+ timestamp: ts,
83
+ functionId: evento.functionId,
84
+ latencyMs: typeof evento.latencyMs === 'number' ? evento.latencyMs : 0,
85
+ success: Boolean(evento.success),
86
+ qualityScore: typeof evento.qualityScore === 'number' ? evento.qualityScore : null,
87
+ metadata: evento.metadata || null,
88
+ });
89
+
90
+ try {
91
+ fs.appendFileSync(path.join(baseDir, RUTA_JSONL), lineaJSONL + '\n', 'utf8');
92
+ } catch (err) {
93
+ return { recorded: false, error: 'JSONL append failed: ' + err.message };
94
+ }
95
+
96
+ // Actualizar agregado
97
+ try {
98
+ const agregado = leerAgregado(baseDir);
99
+ const fid = evento.functionId;
100
+ const m = agregado[fid] || {
101
+ functionId: fid,
102
+ totalCalls: 0,
103
+ successCount: 0,
104
+ failureCount: 0,
105
+ avgLatencyMs: 0,
106
+ avgQualityScore: 0,
107
+ qualityCallCounts: 0,
108
+ lastUpdatedAt: ts,
109
+ };
110
+
111
+ const prev = m.totalCalls;
112
+ m.totalCalls += 1;
113
+ m.avgLatencyMs = (m.avgLatencyMs * prev + (evento.latencyMs || 0)) / m.totalCalls;
114
+ if (evento.success) m.successCount += 1;
115
+ else m.failureCount += 1;
116
+
117
+ if (typeof evento.qualityScore === 'number') {
118
+ const prevQ = m.qualityCallCounts || 0;
119
+ m.avgQualityScore = (m.avgQualityScore * prevQ + evento.qualityScore) / (prevQ + 1);
120
+ m.qualityCallCounts = prevQ + 1;
121
+ }
122
+
123
+ m.lastUpdatedAt = ts;
124
+ agregado[fid] = m;
125
+ atomicWriteJSON(path.join(baseDir, RUTA_AGREGADO), agregado);
126
+ } catch (err) {
127
+ return { recorded: true, error: 'Aggregate update failed: ' + err.message };
128
+ }
129
+
130
+ return { recorded: true };
131
+ }
132
+
133
+ /**
134
+ * Lee las métricas agregadas para un functionId específico.
135
+ * @param {string} baseDir
136
+ * @param {string} functionId
137
+ * @returns {object|null}
138
+ */
139
+ function obtener(baseDir, functionId) {
140
+ const agregado = leerAgregado(baseDir);
141
+ return agregado[functionId] || null;
142
+ }
143
+
144
+ /**
145
+ * Lee todas las métricas agregadas.
146
+ * @param {string} baseDir
147
+ * @returns {object[]}
148
+ */
149
+ function obtenerTodos(baseDir) {
150
+ const agregado = leerAgregado(baseDir);
151
+ return Object.values(agregado);
152
+ }
153
+
154
+ /**
155
+ * Recorre el JSONL y reconstruye el agregado desde cero.
156
+ * Útil tras corrupción del agregado o auditoría histórica.
157
+ *
158
+ * @param {string} baseDir
159
+ * @returns {{ rebuilt: number, functions: number }}
160
+ */
161
+ function reconstruirAgregado(baseDir) {
162
+ const ruta = path.join(baseDir, RUTA_JSONL);
163
+ if (!fs.existsSync(ruta)) return { rebuilt: 0, functions: 0 };
164
+
165
+ const agregado = {};
166
+ let lineas = 0;
167
+ const contenido = fs.readFileSync(ruta, 'utf8');
168
+ for (const linea of contenido.split('\n')) {
169
+ if (!linea.trim()) continue;
170
+ let evento;
171
+ try { evento = JSON.parse(linea); } catch { continue; }
172
+
173
+ const fid = evento.functionId;
174
+ if (!fid) continue;
175
+ lineas++;
176
+
177
+ const m = agregado[fid] || {
178
+ functionId: fid,
179
+ totalCalls: 0,
180
+ successCount: 0,
181
+ failureCount: 0,
182
+ avgLatencyMs: 0,
183
+ avgQualityScore: 0,
184
+ qualityCallCounts: 0,
185
+ lastUpdatedAt: evento.timestamp,
186
+ };
187
+
188
+ const prev = m.totalCalls;
189
+ m.totalCalls += 1;
190
+ m.avgLatencyMs = (m.avgLatencyMs * prev + (evento.latencyMs || 0)) / m.totalCalls;
191
+ if (evento.success) m.successCount += 1;
192
+ else m.failureCount += 1;
193
+
194
+ if (typeof evento.qualityScore === 'number') {
195
+ const prevQ = m.qualityCallCounts || 0;
196
+ m.avgQualityScore = (m.avgQualityScore * prevQ + evento.qualityScore) / (prevQ + 1);
197
+ m.qualityCallCounts = prevQ + 1;
198
+ }
199
+
200
+ m.lastUpdatedAt = evento.timestamp;
201
+ agregado[fid] = m;
202
+ }
203
+
204
+ asegurarDir(baseDir);
205
+ atomicWriteJSON(path.join(baseDir, RUTA_AGREGADO), agregado);
206
+ return { rebuilt: lineas, functions: Object.keys(agregado).length };
207
+ }
208
+
209
+ // ── exports ───────────────────────────────────────────────────────────────────
210
+
211
+ module.exports = {
212
+ registrar,
213
+ obtener,
214
+ obtenerTodos,
215
+ reconstruirAgregado,
216
+ RUTA_JSONL,
217
+ RUTA_AGREGADO,
218
+ };