@saulwade/swl-ses 1.3.7 → 1.4.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 +12 -4
- package/README.md +1 -1
- package/bin/swl-mcp-server.js +187 -187
- package/bin/swl-webhook-server.js +198 -0
- package/comandos/swl/.evolved.json +22 -22
- package/comandos/swl/adoptar-proyecto.md +21 -1
- package/comandos/swl/claudemd.md +14 -1
- package/comandos/swl/contribuir.md +233 -233
- package/comandos/swl/exportar-vault.md +207 -7
- package/comandos/swl/nuevo-proyecto.md +24 -2
- package/gateway/adapters/base.js +109 -0
- package/gateway/adapters/discord.js +167 -0
- package/gateway/adapters/email.js +221 -0
- package/gateway/adapters/slack.js +192 -0
- package/gateway/adapters/telegram.js +183 -0
- package/gateway/adapters/webhook.js +113 -0
- package/gateway/adapters/whatsapp.js +214 -0
- package/gateway/agent-executor.js +322 -0
- package/gateway/command-relay.js +271 -0
- package/gateway/cron/jobs.js +263 -0
- package/gateway/cron/scheduler.js +322 -0
- package/gateway/cron/store.js +335 -0
- package/gateway/index.js +320 -0
- package/gateway/lib/event-channel.js +191 -0
- package/gateway/session.js +131 -0
- package/gateway/webhook-server.js +324 -0
- package/habilidades/backend-production-resilience/SKILL.md +288 -288
- package/habilidades/benchmark-memoria/SKILL.md +186 -186
- package/habilidades/build-errors-nextjs/SKILL.md +55 -1
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -276
- package/habilidades/doubt-driven-review/SKILL.md +171 -171
- package/habilidades/doubt-driven-review/recursos/EXAMPLES.md +130 -130
- package/habilidades/eval-framework/SKILL.md +212 -212
- package/habilidades/extractor-de-aprendizajes/SKILL.md +24 -10
- 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/nextjs-testing/SKILL.md +89 -5
- package/habilidades/node-experto/SKILL.md +37 -1
- 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/react-experto/SKILL.md +45 -4
- package/habilidades/release-semver/.evolved.json +8 -8
- package/habilidades/swl-claudemd/SKILL.md +15 -1
- package/habilidades/tdd-workflow/SKILL.md +36 -4
- package/habilidades/testing-python/SKILL.md +340 -340
- package/hooks/claudemd-bloat-detector.js +161 -161
- package/hooks/inyeccion-contexto.js +8 -3
- 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-ip.js +177 -0
- 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/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/lib/webhook-dedup.js +184 -0
- package/hooks/lib/webhook-verify.js +123 -0
- package/hooks/proteccion-rutas.js +120 -15
- 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 +1 -0
- package/manifiestos/skills-lock.json +37 -37
- package/package.json +5 -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 +1 -1
- 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/reglas/usar-sistema-swl.md +251 -0
- package/schemas/diary-entry.schema.json +80 -80
- package/scripts/benchmark-memoria.js +167 -167
- package/scripts/comandos/skills.js +251 -2
- package/scripts/configurar-branch-protection.js +418 -418
- package/scripts/detectar-aprendizajes-duplicados.js +151 -151
- 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/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-stack-detallado.js +307 -307
- package/scripts/lib/diary-entry.js +234 -234
- 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/jaccard-similarity.js +98 -98
- package/scripts/lib/longmemeval-runner.js +125 -125
- package/scripts/lib/npm-version.js +261 -261
- package/scripts/lib/paquetes-conocidos.js +50 -50
- 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/limpiar-artefactos-python.js +131 -131
- package/scripts/mcp-server/README.md +128 -128
- package/scripts/mcp-server/handlers.js +206 -206
- 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 +195 -195
- package/scripts/validar-userland-vacio.js +110 -110
- 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
|
+
};
|