agentic-kdd 3.2.2 → 3.3.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.
- package/autonomous-decision.cjs +735 -0
- package/bin/akdd.js +33 -0
- package/collab-manager.cjs +62 -15
- package/effectiveness-report.cjs +402 -0
- package/package.json +1 -1
|
@@ -0,0 +1,735 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentic KDD — Autonomous Decision Engine v1.0
|
|
3
|
+
*
|
|
4
|
+
* Motor de decisión autónoma L4.
|
|
5
|
+
*
|
|
6
|
+
* Lo que resuelve:
|
|
7
|
+
* - El agente detecta situaciones que afectan lo que se está trabajando
|
|
8
|
+
* y decide autónomamente si implementar, advertir, o diferir
|
|
9
|
+
* - Prerequisite chain: detecta si algo que hay que tocar tiene un
|
|
10
|
+
* prerequisito roto y lo resuelve primero
|
|
11
|
+
* - Cross-module pattern: si el mismo error ya ocurrió en otro módulo
|
|
12
|
+
* y fue resuelto allá, aplica la misma solución aquí
|
|
13
|
+
* - Full spectrum: no solo mira errores — también protege lo que funciona
|
|
14
|
+
*
|
|
15
|
+
* Decisiones posibles:
|
|
16
|
+
* STOP → blast CRITICAL o PROTECTED contract roto
|
|
17
|
+
* No implementar. Reportar exactamente qué rompería.
|
|
18
|
+
* WARN → blast HIGH o VERIFIED contract en riesgo
|
|
19
|
+
* Implementar pero avisar. El dev decide si continuar.
|
|
20
|
+
* IMPLEMENT → blast MEDIUM/LOW + sin rotura de contratos
|
|
21
|
+
* Implementar ahora dentro del ciclo.
|
|
22
|
+
* IMPLEMENT_CAUTIOUS→ prerequisito resuelto primero, luego la tarea
|
|
23
|
+
* Implementar en orden: prerequisito → tarea original
|
|
24
|
+
* DEFER → blast LOW + sin historial + sin contratos en riesgo
|
|
25
|
+
* No tocar ahora. Agregar a cola de sugerencias al final.
|
|
26
|
+
*
|
|
27
|
+
* Uso desde el pipeline (harness.cjs lo llama automáticamente):
|
|
28
|
+
* const { analyze } = require('./autonomous-decision.cjs');
|
|
29
|
+
* const decision = await analyze({ files: ['src/auth.ts'], task: 'fix session' });
|
|
30
|
+
*
|
|
31
|
+
* Uso manual:
|
|
32
|
+
* node autonomous-decision.cjs analyze src/auth.ts
|
|
33
|
+
* node autonomous-decision.cjs queue — ver cola diferida
|
|
34
|
+
* node autonomous-decision.cjs flush — mostrar cola y limpiarla
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
'use strict';
|
|
38
|
+
|
|
39
|
+
const path = require('path');
|
|
40
|
+
const fs = require('fs');
|
|
41
|
+
|
|
42
|
+
// ─── CONSTANTES ───────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
const DECISIONS = {
|
|
45
|
+
STOP: 'STOP',
|
|
46
|
+
WARN: 'WARN',
|
|
47
|
+
IMPLEMENT: 'IMPLEMENT',
|
|
48
|
+
IMPLEMENT_CAUTIOUS: 'IMPLEMENT_CAUTIOUS',
|
|
49
|
+
DEFER: 'DEFER',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
const BLAST_LEVELS = { LOW: 0, MEDIUM: 1, HIGH: 2, CRITICAL: 3 };
|
|
53
|
+
|
|
54
|
+
const DEFERRED_QUEUE_PATH = '.agentic/deferred_queue.json';
|
|
55
|
+
|
|
56
|
+
// ─── DB ───────────────────────────────────────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
function openDB(projectRoot) {
|
|
59
|
+
const dbPath = path.join(projectRoot, '.agentic/memoria.db');
|
|
60
|
+
try { return new (require('better-sqlite3'))(dbPath); } catch {}
|
|
61
|
+
try { const { DatabaseSync } = require('node:sqlite'); return new DatabaseSync(dbPath); } catch {}
|
|
62
|
+
return null;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function safe(fn, fallback = null) {
|
|
66
|
+
try { return fn(); } catch { return fallback; }
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── PREREQUISITE CHAIN DETECTION ────────────────────────────────────────────
|
|
70
|
+
/**
|
|
71
|
+
* Dado un set de archivos a modificar, detecta si algún prerequisito
|
|
72
|
+
* en la cadena de dependencias tiene contratos rotos.
|
|
73
|
+
*
|
|
74
|
+
* Ejemplo:
|
|
75
|
+
* Quieres tocar dashboard.ts
|
|
76
|
+
* dashboard.ts importa authMiddleware.ts
|
|
77
|
+
* authMiddleware.ts tiene contrato INVALIDATED
|
|
78
|
+
* → authMiddleware.ts es prerequisito roto
|
|
79
|
+
*/
|
|
80
|
+
function detectPrerequisiteChain(db, targetFiles, projectRoot) {
|
|
81
|
+
const broken = [];
|
|
82
|
+
|
|
83
|
+
if (!db) return broken;
|
|
84
|
+
|
|
85
|
+
targetFiles.forEach(file => {
|
|
86
|
+
const basename = path.basename(file);
|
|
87
|
+
|
|
88
|
+
// Obtener dependencias via AST edges
|
|
89
|
+
const deps = safe(() =>
|
|
90
|
+
db.prepare(`
|
|
91
|
+
SELECT DISTINCT hacia_entidad as dep
|
|
92
|
+
FROM relaciones_semanticas
|
|
93
|
+
WHERE (desde_entidad LIKE ? OR desde_entidad = ?)
|
|
94
|
+
AND tipo IN ('depende_de', 'importa', 'usa', 'llama')
|
|
95
|
+
AND (invalid_at IS NULL OR invalid_at = '')
|
|
96
|
+
LIMIT 20
|
|
97
|
+
`).all(`%${basename}%`, file)
|
|
98
|
+
) || [];
|
|
99
|
+
|
|
100
|
+
deps.forEach(({ dep }) => {
|
|
101
|
+
if (!dep) return;
|
|
102
|
+
|
|
103
|
+
// Verificar si esa dependencia tiene contratos fallidos o invalidados
|
|
104
|
+
const brokenContracts = safe(() =>
|
|
105
|
+
db.prepare(`
|
|
106
|
+
SELECT id, name, status, module
|
|
107
|
+
FROM verified_contracts
|
|
108
|
+
WHERE (test_file LIKE ? OR name LIKE ? OR module LIKE ?)
|
|
109
|
+
AND status IN ('invalidated')
|
|
110
|
+
LIMIT 5
|
|
111
|
+
`).all(`%${dep}%`, `%${dep}%`, `%${path.basename(dep, path.extname(dep))}%`)
|
|
112
|
+
) || [];
|
|
113
|
+
|
|
114
|
+
if (brokenContracts.length > 0) {
|
|
115
|
+
broken.push({
|
|
116
|
+
prerequisite: dep,
|
|
117
|
+
broken_contracts: brokenContracts,
|
|
118
|
+
affects: file,
|
|
119
|
+
reason: `${dep} has ${brokenContracts.length} broken contract(s) — must be fixed before ${basename}`,
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// También verificar errores HIGH confidence no resueltos en la dependencia
|
|
124
|
+
const depArea = path.basename(dep, path.extname(dep)).toLowerCase();
|
|
125
|
+
const unresolvedErrors = safe(() =>
|
|
126
|
+
db.prepare(`
|
|
127
|
+
SELECT titulo, confianza FROM nodos
|
|
128
|
+
WHERE tipo = 'error'
|
|
129
|
+
AND confianza IN ('ALTA', 'MEDIA')
|
|
130
|
+
AND estado = 'ACTIVO'
|
|
131
|
+
AND area LIKE ?
|
|
132
|
+
AND (vigencia_tipo = 'VIGENTE' OR vigencia_tipo IS NULL)
|
|
133
|
+
LIMIT 3
|
|
134
|
+
`).all(`%${depArea}%`)
|
|
135
|
+
) || [];
|
|
136
|
+
|
|
137
|
+
if (unresolvedErrors.length > 0 && brokenContracts.length === 0) {
|
|
138
|
+
broken.push({
|
|
139
|
+
prerequisite: dep,
|
|
140
|
+
broken_contracts: [],
|
|
141
|
+
unresolved_errors: unresolvedErrors,
|
|
142
|
+
affects: file,
|
|
143
|
+
reason: `${dep} has ${unresolvedErrors.length} HIGH/MEDIUM unresolved error(s)`,
|
|
144
|
+
severity: 'soft', // no es STOP, es WARN
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
return broken;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// ─── CROSS-MODULE ERROR CHECK ─────────────────────────────────────────────────
|
|
154
|
+
/**
|
|
155
|
+
* ¿Este error ya ocurrió en otro módulo?
|
|
156
|
+
* Si sí y fue resuelto → propone misma solución
|
|
157
|
+
* Si sí y no fue resuelto → es sistémico, escalar
|
|
158
|
+
*/
|
|
159
|
+
function crossModuleCheck(db, errorSignatures, currentFiles) {
|
|
160
|
+
if (!db || !errorSignatures || errorSignatures.length === 0) return [];
|
|
161
|
+
|
|
162
|
+
const findings = [];
|
|
163
|
+
const currentAreas = currentFiles.map(f => path.basename(f, path.extname(f)).toLowerCase());
|
|
164
|
+
|
|
165
|
+
errorSignatures.forEach(sig => {
|
|
166
|
+
if (!sig) return;
|
|
167
|
+
|
|
168
|
+
// Buscar el mismo error en otras áreas
|
|
169
|
+
const otherInstances = safe(() =>
|
|
170
|
+
db.prepare(`
|
|
171
|
+
SELECT id, titulo, area, contenido, aplicado
|
|
172
|
+
FROM nodos
|
|
173
|
+
WHERE tipo = 'error'
|
|
174
|
+
AND (titulo LIKE ? OR contenido LIKE ?)
|
|
175
|
+
AND estado = 'ACTIVO'
|
|
176
|
+
AND area NOT IN (${currentAreas.map(() => '?').join(',')})
|
|
177
|
+
ORDER BY aplicado DESC
|
|
178
|
+
LIMIT 5
|
|
179
|
+
`).all(`%${sig}%`, `%${sig}%`, ...currentAreas)
|
|
180
|
+
) || [];
|
|
181
|
+
|
|
182
|
+
otherInstances.forEach(inst => {
|
|
183
|
+
// ¿Fue resuelto allá?
|
|
184
|
+
const fixEdge = safe(() =>
|
|
185
|
+
db.prepare(`
|
|
186
|
+
SELECT descripcion, hacia_entidad
|
|
187
|
+
FROM relaciones_semanticas
|
|
188
|
+
WHERE desde_entidad LIKE ?
|
|
189
|
+
AND tipo = 'was_fixed_by'
|
|
190
|
+
AND (invalid_at IS NULL OR invalid_at = '')
|
|
191
|
+
LIMIT 1
|
|
192
|
+
`).get(`%${inst.area}%`)
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
findings.push({
|
|
196
|
+
signature: sig,
|
|
197
|
+
found_in: inst.area,
|
|
198
|
+
titulo: inst.titulo,
|
|
199
|
+
was_fixed: !!fixEdge,
|
|
200
|
+
fix_applied: fixEdge?.descripcion || null,
|
|
201
|
+
fix_target: fixEdge?.hacia_entidad || null,
|
|
202
|
+
is_systemic: !fixEdge,
|
|
203
|
+
recommendation: fixEdge
|
|
204
|
+
? `Apply same fix as in ${inst.area}: ${fixEdge.descripcion?.substring(0, 80)}`
|
|
205
|
+
: `Systemic error — also present in ${inst.area} without resolution. Escalate priority.`,
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
return findings;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// ─── BLAST RADIUS CHECK ───────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
function getBlastLevel(db, targetFiles) {
|
|
216
|
+
if (!db) return { level: 'LOW', level_int: 0, contracts_at_risk: 0, protected: 0 };
|
|
217
|
+
|
|
218
|
+
let contractsAtRisk = 0;
|
|
219
|
+
let protectedAtRisk = 0;
|
|
220
|
+
|
|
221
|
+
try {
|
|
222
|
+
const allContracts = db.prepare(
|
|
223
|
+
"SELECT * FROM verified_contracts WHERE status IN ('protected','verified')"
|
|
224
|
+
).all();
|
|
225
|
+
|
|
226
|
+
targetFiles.forEach(file => {
|
|
227
|
+
const basename = path.basename(file);
|
|
228
|
+
allContracts.forEach(c => {
|
|
229
|
+
const testFile = c.test_file || '';
|
|
230
|
+
const isAtRisk = testFile.includes(basename) ||
|
|
231
|
+
(c.module && file.toLowerCase().includes(c.module.toLowerCase()));
|
|
232
|
+
if (isAtRisk) {
|
|
233
|
+
contractsAtRisk++;
|
|
234
|
+
if (c.status === 'protected') protectedAtRisk++;
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
} catch {}
|
|
239
|
+
|
|
240
|
+
const level = contractsAtRisk === 0 ? 'LOW'
|
|
241
|
+
: contractsAtRisk <= 3 ? 'LOW'
|
|
242
|
+
: contractsAtRisk <= 10 ? 'MEDIUM'
|
|
243
|
+
: contractsAtRisk <= 20 ? 'HIGH'
|
|
244
|
+
: 'CRITICAL';
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
level,
|
|
248
|
+
level_int: BLAST_LEVELS[level],
|
|
249
|
+
contracts_at_risk: contractsAtRisk,
|
|
250
|
+
protected: protectedAtRisk,
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ─── DEFERRED QUEUE ───────────────────────────────────────────────────────────
|
|
255
|
+
|
|
256
|
+
function loadDeferredQueue(projectRoot) {
|
|
257
|
+
const qPath = path.join(projectRoot, DEFERRED_QUEUE_PATH);
|
|
258
|
+
if (!fs.existsSync(qPath)) return [];
|
|
259
|
+
try { return JSON.parse(fs.readFileSync(qPath, 'utf8')); } catch { return []; }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function saveDeferredQueue(projectRoot, queue) {
|
|
263
|
+
const qPath = path.join(projectRoot, DEFERRED_QUEUE_PATH);
|
|
264
|
+
try { fs.writeFileSync(qPath, JSON.stringify(queue, null, 2)); } catch {}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function addToDeferred(projectRoot, item) {
|
|
268
|
+
const queue = loadDeferredQueue(projectRoot);
|
|
269
|
+
queue.push({ ...item, deferred_at: new Date().toISOString() });
|
|
270
|
+
// Máx 50 items en cola
|
|
271
|
+
if (queue.length > 50) queue.splice(0, queue.length - 50);
|
|
272
|
+
saveDeferredQueue(projectRoot, queue);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
function flushDeferredQueue(projectRoot) {
|
|
276
|
+
const queue = loadDeferredQueue(projectRoot);
|
|
277
|
+
if (queue.length === 0) return [];
|
|
278
|
+
saveDeferredQueue(projectRoot, []);
|
|
279
|
+
return queue;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ─── MOTOR DE DECISIÓN PRINCIPAL ─────────────────────────────────────────────
|
|
283
|
+
/**
|
|
284
|
+
* Analiza un cambio propuesto y decide qué hacer.
|
|
285
|
+
*
|
|
286
|
+
* @param {Object} params
|
|
287
|
+
* @param {string[]} params.files Archivos a modificar
|
|
288
|
+
* @param {string} params.task Descripción de la tarea
|
|
289
|
+
* @param {string[]} params.errorSignatures Firmas de errores detectados (opcional)
|
|
290
|
+
* @param {string} params.projectRoot
|
|
291
|
+
*/
|
|
292
|
+
function analyze(params = {}) {
|
|
293
|
+
const {
|
|
294
|
+
files = [],
|
|
295
|
+
task = '',
|
|
296
|
+
errorSignatures= [],
|
|
297
|
+
projectRoot = process.cwd(),
|
|
298
|
+
} = params;
|
|
299
|
+
|
|
300
|
+
const db = openDB(projectRoot);
|
|
301
|
+
const result = {
|
|
302
|
+
decision: DECISIONS.IMPLEMENT,
|
|
303
|
+
files,
|
|
304
|
+
task,
|
|
305
|
+
blast: null,
|
|
306
|
+
prerequisites: [],
|
|
307
|
+
cross_module: [],
|
|
308
|
+
reasons: [],
|
|
309
|
+
deferred: [],
|
|
310
|
+
action_plan: [],
|
|
311
|
+
summary: '',
|
|
312
|
+
};
|
|
313
|
+
|
|
314
|
+
// ── 1. BLAST RADIUS ────────────────────────────────────────────────────────
|
|
315
|
+
if (files.length > 0) {
|
|
316
|
+
result.blast = getBlastLevel(db, files);
|
|
317
|
+
|
|
318
|
+
if (result.blast.protected > 0) {
|
|
319
|
+
// Hay contratos PROTECTED en riesgo → STOP
|
|
320
|
+
result.decision = DECISIONS.STOP;
|
|
321
|
+
result.reasons.push(
|
|
322
|
+
`PROTECTED contracts at risk: ${result.blast.protected}. ` +
|
|
323
|
+
`These represent verified behavior that must not break.`
|
|
324
|
+
);
|
|
325
|
+
} else if (result.blast.level === 'CRITICAL') {
|
|
326
|
+
result.decision = DECISIONS.STOP;
|
|
327
|
+
result.reasons.push(
|
|
328
|
+
`Blast radius CRITICAL: ${result.blast.contracts_at_risk} contracts at risk. ` +
|
|
329
|
+
`Too many verified behaviors could break.`
|
|
330
|
+
);
|
|
331
|
+
} else if (result.blast.level === 'HIGH') {
|
|
332
|
+
result.decision = DECISIONS.WARN;
|
|
333
|
+
result.reasons.push(
|
|
334
|
+
`Blast radius HIGH: ${result.blast.contracts_at_risk} contracts at risk. ` +
|
|
335
|
+
`Proceed with caution. Run akdd contracts gate after changes.`
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ── 2. PREREQUISITE CHAIN ──────────────────────────────────────────────────
|
|
341
|
+
if (files.length > 0 && result.decision !== DECISIONS.STOP) {
|
|
342
|
+
const prereqs = detectPrerequisiteChain(db, files, projectRoot);
|
|
343
|
+
const hardPrereqs = prereqs.filter(p => !p.severity || p.severity === 'hard');
|
|
344
|
+
const softPrereqs = prereqs.filter(p => p.severity === 'soft');
|
|
345
|
+
|
|
346
|
+
if (hardPrereqs.length > 0) {
|
|
347
|
+
result.prerequisites = hardPrereqs;
|
|
348
|
+
result.decision = DECISIONS.IMPLEMENT_CAUTIOUS;
|
|
349
|
+
result.reasons.push(
|
|
350
|
+
`Prerequisite chain broken: ${hardPrereqs.length} dependency(ies) have broken contracts. ` +
|
|
351
|
+
`Must fix prerequisites first.`
|
|
352
|
+
);
|
|
353
|
+
result.action_plan = [
|
|
354
|
+
...hardPrereqs.map(p => ({
|
|
355
|
+
step: 'FIX_PREREQUISITE',
|
|
356
|
+
target: p.prerequisite,
|
|
357
|
+
reason: p.reason,
|
|
358
|
+
before_main_task: true,
|
|
359
|
+
})),
|
|
360
|
+
{ step: 'EXECUTE_ORIGINAL_TASK', target: files, task },
|
|
361
|
+
];
|
|
362
|
+
} else if (softPrereqs.length > 0) {
|
|
363
|
+
result.prerequisites = softPrereqs;
|
|
364
|
+
if (result.decision === DECISIONS.IMPLEMENT) {
|
|
365
|
+
result.decision = DECISIONS.WARN;
|
|
366
|
+
}
|
|
367
|
+
result.reasons.push(
|
|
368
|
+
`Soft prerequisite warning: ${softPrereqs.length} dependency(ies) have unresolved HIGH errors.`
|
|
369
|
+
);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// ── 3. CROSS-MODULE CHECK ──────────────────────────────────────────────────
|
|
374
|
+
if (errorSignatures.length > 0) {
|
|
375
|
+
const crossModuleFindings = crossModuleCheck(db, errorSignatures, files);
|
|
376
|
+
result.cross_module = crossModuleFindings;
|
|
377
|
+
|
|
378
|
+
const systemicErrors = crossModuleFindings.filter(f => f.is_systemic);
|
|
379
|
+
const fixableErrors = crossModuleFindings.filter(f => f.was_fixed);
|
|
380
|
+
|
|
381
|
+
if (systemicErrors.length > 0 && result.decision === DECISIONS.IMPLEMENT) {
|
|
382
|
+
result.decision = DECISIONS.WARN;
|
|
383
|
+
result.reasons.push(
|
|
384
|
+
`Systemic error detected: ${systemicErrors.length} error(s) also present in other modules ` +
|
|
385
|
+
`without resolution. Consider addressing root cause.`
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (fixableErrors.length > 0) {
|
|
390
|
+
result.reasons.push(
|
|
391
|
+
`Cross-module pattern found: ${fixableErrors.length} error(s) were already ` +
|
|
392
|
+
`resolved in other modules. Applying same fix pattern.`
|
|
393
|
+
);
|
|
394
|
+
result.action_plan.push(
|
|
395
|
+
...fixableErrors.map(f => ({
|
|
396
|
+
step: 'APPLY_KNOWN_FIX',
|
|
397
|
+
signature: f.signature,
|
|
398
|
+
from_module: f.found_in,
|
|
399
|
+
fix: f.fix_applied,
|
|
400
|
+
}))
|
|
401
|
+
);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// ── 4. LOW BLAST + SIN HISTORIAL → DEFER ──────────────────────────────────
|
|
406
|
+
if (result.decision === DECISIONS.IMPLEMENT &&
|
|
407
|
+
result.blast?.level === 'LOW' &&
|
|
408
|
+
result.cross_module.length === 0 &&
|
|
409
|
+
result.prerequisites.length === 0 &&
|
|
410
|
+
files.length > 0) {
|
|
411
|
+
|
|
412
|
+
// ¿Hay historial causal para estos archivos?
|
|
413
|
+
let hasHistory = false;
|
|
414
|
+
if (db) {
|
|
415
|
+
files.forEach(f => {
|
|
416
|
+
const basename = path.basename(f);
|
|
417
|
+
const history = safe(() =>
|
|
418
|
+
db.prepare(`
|
|
419
|
+
SELECT COUNT(*) as n FROM relaciones_semanticas
|
|
420
|
+
WHERE (desde_entidad LIKE ? OR hacia_entidad LIKE ?)
|
|
421
|
+
AND tipo IN ('caused_failure', 'was_fixed_by', 'regressed_by')
|
|
422
|
+
AND (invalid_at IS NULL OR invalid_at = '')
|
|
423
|
+
`).get(`%${basename}%`, `%${basename}%`)?.n
|
|
424
|
+
) || 0;
|
|
425
|
+
if (history > 0) hasHistory = true;
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
if (!hasHistory && task && task.toLowerCase().includes('minor')) {
|
|
430
|
+
// Solo diferir si explícitamente se indica que es menor
|
|
431
|
+
result.decision = DECISIONS.DEFER;
|
|
432
|
+
result.deferred = files;
|
|
433
|
+
addToDeferred(projectRoot, { files, task, reason: 'Low blast, no history, minor scope' });
|
|
434
|
+
result.reasons.push('Low blast radius, no causal history, minor scope — deferred to end-of-cycle suggestions.');
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// ── 5. RESUMEN EJECUTIVO ───────────────────────────────────────────────────
|
|
439
|
+
result.summary = buildSummary(result);
|
|
440
|
+
|
|
441
|
+
if (db) { try { db.close(); } catch {} }
|
|
442
|
+
return result;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// ─── RESUMEN EJECUTIVO ────────────────────────────────────────────────────────
|
|
446
|
+
|
|
447
|
+
function buildSummary(result) {
|
|
448
|
+
const icons = {
|
|
449
|
+
STOP: '🛑',
|
|
450
|
+
WARN: '⚠️',
|
|
451
|
+
IMPLEMENT: '✅',
|
|
452
|
+
IMPLEMENT_CAUTIOUS: '🔄',
|
|
453
|
+
DEFER: '📋',
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
const icon = icons[result.decision] || '?';
|
|
457
|
+
let summary = `${icon} Decision: ${result.decision}`;
|
|
458
|
+
|
|
459
|
+
if (result.blast) {
|
|
460
|
+
summary += ` | Blast: ${result.blast.level} (${result.blast.contracts_at_risk} contracts)`;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
if (result.prerequisites.length > 0) {
|
|
464
|
+
summary += ` | Prerequisites: ${result.prerequisites.length} broken`;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
if (result.cross_module.length > 0) {
|
|
468
|
+
const fixed = result.cross_module.filter(f => f.was_fixed).length;
|
|
469
|
+
const systemic = result.cross_module.filter(f => f.is_systemic).length;
|
|
470
|
+
if (fixed > 0) summary += ` | ${fixed} known fix(es) available`;
|
|
471
|
+
if (systemic > 0) summary += ` | ${systemic} systemic error(s)`;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return summary;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// ─── PRINT RESULT ─────────────────────────────────────────────────────────────
|
|
478
|
+
|
|
479
|
+
function printAnalysis(result) {
|
|
480
|
+
console.log('\n' + '═'.repeat(60));
|
|
481
|
+
console.log(' Autonomous Decision Engine');
|
|
482
|
+
console.log('═'.repeat(60));
|
|
483
|
+
console.log(`\n ${result.summary}\n`);
|
|
484
|
+
|
|
485
|
+
if (result.reasons.length > 0) {
|
|
486
|
+
console.log(' Reasons:');
|
|
487
|
+
result.reasons.forEach(r => console.log(` • ${r}`));
|
|
488
|
+
console.log('');
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if (result.prerequisites.length > 0) {
|
|
492
|
+
console.log(' Prerequisite chain:');
|
|
493
|
+
result.prerequisites.forEach(p => {
|
|
494
|
+
console.log(` ⚡ ${p.prerequisite}`);
|
|
495
|
+
console.log(` ${p.reason}`);
|
|
496
|
+
});
|
|
497
|
+
console.log('');
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (result.cross_module.length > 0) {
|
|
501
|
+
console.log(' Cross-module findings:');
|
|
502
|
+
result.cross_module.forEach(f => {
|
|
503
|
+
const icon = f.was_fixed ? '✅' : '⚠️';
|
|
504
|
+
console.log(` ${icon} "${f.signature}" also in ${f.found_in}`);
|
|
505
|
+
console.log(` ${f.recommendation}`);
|
|
506
|
+
});
|
|
507
|
+
console.log('');
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
if (result.action_plan.length > 0) {
|
|
511
|
+
console.log(' Action plan:');
|
|
512
|
+
result.action_plan.forEach((step, i) => {
|
|
513
|
+
console.log(` ${i + 1}. [${step.step}] ${step.target || step.signature || ''}`);
|
|
514
|
+
if (step.reason) console.log(` Reason: ${step.reason}`);
|
|
515
|
+
if (step.fix) console.log(` Apply: ${step.fix}`);
|
|
516
|
+
});
|
|
517
|
+
console.log('');
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
console.log('═'.repeat(60) + '\n');
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
// ─── SPRINT PLANNER ───────────────────────────────────────────────────────────
|
|
524
|
+
/**
|
|
525
|
+
* Punto 3: Sprint planning con contexto explícito del dev.
|
|
526
|
+
*
|
|
527
|
+
* El dev pasa el contexto una vez. Agentic cruza contra datos técnicos.
|
|
528
|
+
* Produce un plan ejecutable por prioridad real (negocio + técnica).
|
|
529
|
+
*
|
|
530
|
+
* @param {Object} context
|
|
531
|
+
* @param {string} context.objective "Terminar el módulo de pagos antes del viernes"
|
|
532
|
+
* @param {string[]} context.constraints ["no tocar auth.ts esta semana"]
|
|
533
|
+
* @param {string[]} context.priorities ["pagos bloquea al cliente X"]
|
|
534
|
+
* @param {string} projectRoot
|
|
535
|
+
*/
|
|
536
|
+
function planSprint(context = {}, projectRoot) {
|
|
537
|
+
projectRoot = projectRoot || process.cwd();
|
|
538
|
+
const db = openDB(projectRoot);
|
|
539
|
+
if (!db) return { error: 'DB no disponible' };
|
|
540
|
+
|
|
541
|
+
const { objective = '', constraints = [], priorities = [] } = context;
|
|
542
|
+
|
|
543
|
+
const plan = {
|
|
544
|
+
objective,
|
|
545
|
+
constraints,
|
|
546
|
+
business_priorities: priorities,
|
|
547
|
+
technical_findings: [],
|
|
548
|
+
combined_priority: [],
|
|
549
|
+
sprint_blocks: [],
|
|
550
|
+
};
|
|
551
|
+
|
|
552
|
+
// Detectar deuda técnica por blast radius
|
|
553
|
+
try {
|
|
554
|
+
const highRisk = db.prepare(`
|
|
555
|
+
SELECT DISTINCT module, COUNT(*) as contract_count
|
|
556
|
+
FROM verified_contracts
|
|
557
|
+
WHERE status IN ('invalidated', 'verified')
|
|
558
|
+
AND failure_count > 0
|
|
559
|
+
GROUP BY module
|
|
560
|
+
ORDER BY failure_count DESC, contract_count DESC
|
|
561
|
+
LIMIT 10
|
|
562
|
+
`).all();
|
|
563
|
+
|
|
564
|
+
plan.technical_findings = highRisk.map(r => ({
|
|
565
|
+
module: r.module,
|
|
566
|
+
contracts: r.contract_count,
|
|
567
|
+
priority: r.contract_count > 5 ? 'HIGH' : r.contract_count > 2 ? 'MEDIUM' : 'LOW',
|
|
568
|
+
}));
|
|
569
|
+
} catch {}
|
|
570
|
+
|
|
571
|
+
// Errores sin resolver de alta confianza
|
|
572
|
+
try {
|
|
573
|
+
const unresolvedErrors = db.prepare(`
|
|
574
|
+
SELECT area, COUNT(*) as n, MAX(confianza) as max_conf
|
|
575
|
+
FROM nodos
|
|
576
|
+
WHERE tipo = 'error'
|
|
577
|
+
AND estado = 'ACTIVO'
|
|
578
|
+
AND confianza IN ('ALTA', 'MEDIA')
|
|
579
|
+
GROUP BY area
|
|
580
|
+
ORDER BY n DESC
|
|
581
|
+
LIMIT 8
|
|
582
|
+
`).all();
|
|
583
|
+
|
|
584
|
+
unresolvedErrors.forEach(e => {
|
|
585
|
+
plan.technical_findings.push({
|
|
586
|
+
module: e.area,
|
|
587
|
+
errors: e.n,
|
|
588
|
+
priority: e.max_conf === 'ALTA' ? 'HIGH' : 'MEDIUM',
|
|
589
|
+
type: 'unresolved_errors',
|
|
590
|
+
});
|
|
591
|
+
});
|
|
592
|
+
} catch {}
|
|
593
|
+
|
|
594
|
+
// Combinar prioridad de negocio con prioridad técnica
|
|
595
|
+
const businessKeywords = [...priorities, objective].join(' ').toLowerCase();
|
|
596
|
+
|
|
597
|
+
plan.technical_findings.forEach(f => {
|
|
598
|
+
const moduleStr = (f.module || '').toLowerCase();
|
|
599
|
+
const businessMatch = businessKeywords.includes(moduleStr) ||
|
|
600
|
+
priorities.some(p => p.toLowerCase().includes(moduleStr));
|
|
601
|
+
|
|
602
|
+
const isConstrained = constraints.some(c =>
|
|
603
|
+
c.toLowerCase().includes(moduleStr)
|
|
604
|
+
);
|
|
605
|
+
|
|
606
|
+
plan.combined_priority.push({
|
|
607
|
+
module: f.module,
|
|
608
|
+
technical_priority: f.priority,
|
|
609
|
+
business_match: businessMatch,
|
|
610
|
+
constrained: isConstrained,
|
|
611
|
+
final_priority: isConstrained ? 'BLOCKED'
|
|
612
|
+
: businessMatch && f.priority === 'HIGH' ? 'P1_CRITICAL'
|
|
613
|
+
: businessMatch ? 'P2_HIGH'
|
|
614
|
+
: f.priority === 'HIGH' ? 'P3_MEDIUM'
|
|
615
|
+
: 'P4_LOW',
|
|
616
|
+
});
|
|
617
|
+
});
|
|
618
|
+
|
|
619
|
+
// Ordenar plan
|
|
620
|
+
const order = { P1_CRITICAL:0, P2_HIGH:1, P3_MEDIUM:2, P4_LOW:3, BLOCKED:4 };
|
|
621
|
+
plan.combined_priority.sort((a, b) =>
|
|
622
|
+
(order[a.final_priority] || 3) - (order[b.final_priority] || 3)
|
|
623
|
+
);
|
|
624
|
+
|
|
625
|
+
// Bloques del sprint
|
|
626
|
+
plan.sprint_blocks = [
|
|
627
|
+
{
|
|
628
|
+
block: 'Immediate — P1/P2',
|
|
629
|
+
items: plan.combined_priority.filter(p => ['P1_CRITICAL','P2_HIGH'].includes(p.final_priority)),
|
|
630
|
+
},
|
|
631
|
+
{
|
|
632
|
+
block: 'This cycle — P3',
|
|
633
|
+
items: plan.combined_priority.filter(p => p.final_priority === 'P3_MEDIUM'),
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
block: 'Next cycle — P4',
|
|
637
|
+
items: plan.combined_priority.filter(p => p.final_priority === 'P4_LOW'),
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
block: 'BLOCKED — do not touch',
|
|
641
|
+
items: plan.combined_priority.filter(p => p.final_priority === 'BLOCKED'),
|
|
642
|
+
},
|
|
643
|
+
].filter(b => b.items.length > 0);
|
|
644
|
+
|
|
645
|
+
if (db) { try { db.close(); } catch {} }
|
|
646
|
+
return plan;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
// ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
650
|
+
|
|
651
|
+
if (require.main === module) {
|
|
652
|
+
const [,, cmd, ...args] = process.argv;
|
|
653
|
+
const projectRoot = process.cwd();
|
|
654
|
+
|
|
655
|
+
switch (cmd) {
|
|
656
|
+
case 'analyze': {
|
|
657
|
+
const files = args.filter(a => !a.startsWith('--'));
|
|
658
|
+
const result = analyze({ files, task: args.join(' '), projectRoot });
|
|
659
|
+
printAnalysis(result);
|
|
660
|
+
process.exit(result.decision === DECISIONS.STOP ? 1 : 0);
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
case 'queue': {
|
|
664
|
+
const queue = loadDeferredQueue(projectRoot);
|
|
665
|
+
if (queue.length === 0) {
|
|
666
|
+
console.log('\n Cola diferida vacía.\n');
|
|
667
|
+
} else {
|
|
668
|
+
console.log(`\n Cola diferida (${queue.length} items):\n`);
|
|
669
|
+
queue.forEach((item, i) => {
|
|
670
|
+
console.log(` ${i+1}. [${item.deferred_at?.split('T')[0]}] ${item.task}`);
|
|
671
|
+
console.log(` Files: ${(item.files || []).join(', ')}`);
|
|
672
|
+
});
|
|
673
|
+
console.log('');
|
|
674
|
+
}
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
case 'flush': {
|
|
679
|
+
const flushed = flushDeferredQueue(projectRoot);
|
|
680
|
+
if (flushed.length === 0) {
|
|
681
|
+
console.log('\n Sin items diferidos.\n');
|
|
682
|
+
} else {
|
|
683
|
+
console.log(`\n ${flushed.length} items de la cola para revisar:\n`);
|
|
684
|
+
flushed.forEach((item, i) => {
|
|
685
|
+
console.log(` ${i+1}. ${item.task}`);
|
|
686
|
+
if (item.reason) console.log(` ${item.reason}`);
|
|
687
|
+
});
|
|
688
|
+
console.log('');
|
|
689
|
+
}
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
case 'sprint': {
|
|
694
|
+
// Uso: node autonomous-decision.cjs sprint --objective "..." --priority "..." --constraint "..."
|
|
695
|
+
const getArg = (flag) => {
|
|
696
|
+
const idx = process.argv.indexOf(flag);
|
|
697
|
+
return idx >= 0 ? process.argv[idx + 1] : null;
|
|
698
|
+
};
|
|
699
|
+
const objective = getArg('--objective') || 'Complete current sprint tasks';
|
|
700
|
+
const priorities = process.argv.filter((_, i) => process.argv[i-1] === '--priority');
|
|
701
|
+
const constraints = process.argv.filter((_, i) => process.argv[i-1] === '--constraint');
|
|
702
|
+
|
|
703
|
+
const plan = planSprint({ objective, priorities, constraints }, projectRoot);
|
|
704
|
+
|
|
705
|
+
if (plan.error) { console.log(`\n Error: ${plan.error}\n`); break; }
|
|
706
|
+
|
|
707
|
+
console.log('\n Sprint Plan\n ' + '─'.repeat(40));
|
|
708
|
+
console.log(` Objective: ${plan.objective}\n`);
|
|
709
|
+
|
|
710
|
+
plan.sprint_blocks.forEach(block => {
|
|
711
|
+
console.log(` ${block.block}:`);
|
|
712
|
+
block.items.forEach(item => {
|
|
713
|
+
const marker = item.business_match ? '★' : '○';
|
|
714
|
+
console.log(` ${marker} ${item.module} [${item.final_priority}]`);
|
|
715
|
+
});
|
|
716
|
+
console.log('');
|
|
717
|
+
});
|
|
718
|
+
break;
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
default:
|
|
722
|
+
console.log('Uso: node autonomous-decision.cjs [analyze <files> | queue | flush | sprint --objective "..."]');
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
module.exports = {
|
|
727
|
+
analyze,
|
|
728
|
+
planSprint,
|
|
729
|
+
detectPrerequisiteChain,
|
|
730
|
+
crossModuleCheck,
|
|
731
|
+
getBlastLevel,
|
|
732
|
+
loadDeferredQueue,
|
|
733
|
+
flushDeferredQueue,
|
|
734
|
+
DECISIONS,
|
|
735
|
+
};
|
package/bin/akdd.js
CHANGED
|
@@ -66,6 +66,15 @@ const HELP = `
|
|
|
66
66
|
akdd creative apply <id> Apply a suggestion
|
|
67
67
|
akdd creative wins View applied creative improvements
|
|
68
68
|
|
|
69
|
+
Autonomous decisions (L4):
|
|
70
|
+
akdd decide <file> Analyze a change — STOP/WARN/IMPLEMENT/DEFER decision
|
|
71
|
+
akdd deferred View deferred queue (end-of-cycle suggestions)
|
|
72
|
+
akdd deferred flush Show and clear deferred queue
|
|
73
|
+
akdd sprint-plan "obj" Generate sprint plan from business objective
|
|
74
|
+
|
|
75
|
+
Effectiveness:
|
|
76
|
+
akdd report Real data — before vs after comparison across all cycles
|
|
77
|
+
|
|
69
78
|
Session continuity:
|
|
70
79
|
akdd historial Resume context in a new chat — paste output at start
|
|
71
80
|
akdd checkpoint Save checkpoint now (auto-runs every 5 cycles)
|
|
@@ -267,6 +276,30 @@ switch (command) {
|
|
|
267
276
|
break;
|
|
268
277
|
}
|
|
269
278
|
|
|
279
|
+
// ── v3.3: Autonomous Decision Engine ──────────────────────────────────────
|
|
280
|
+
case 'decide': {
|
|
281
|
+
const files = args.slice(1);
|
|
282
|
+
if (!files.length) { console.log('\n Uso: akdd decide <archivo> [archivos...]\n'); break; }
|
|
283
|
+
runModule('autonomous-decision.cjs', 'analyze', files.map(f => `"${f}"`).join(' '));
|
|
284
|
+
break;
|
|
285
|
+
}
|
|
286
|
+
case 'deferred': {
|
|
287
|
+
const sub = arg1 || 'queue';
|
|
288
|
+
runModule('autonomous-decision.cjs', sub === 'flush' ? 'flush' : 'queue');
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
case 'sprint-plan': {
|
|
292
|
+
const objective = args.slice(1).join(' ');
|
|
293
|
+
if (!objective) { console.log('\n Uso: akdd sprint-plan "objetivo del sprint"\n'); break; }
|
|
294
|
+
runModule('autonomous-decision.cjs', 'sprint', `--objective "${objective}"`);
|
|
295
|
+
break;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
// ── v3.3: Effectiveness Report ──────────────────────────────────────────────
|
|
299
|
+
case 'report':
|
|
300
|
+
runModule('effectiveness-report.cjs');
|
|
301
|
+
break;
|
|
302
|
+
|
|
270
303
|
// ── v3.3: Session Guard ────────────────────────────────────────────────────
|
|
271
304
|
case 'historial':
|
|
272
305
|
runModule('session-guard.cjs', 'historial');
|
package/collab-manager.cjs
CHANGED
|
@@ -26,13 +26,42 @@ const PROVISIONER_URL = 'https://agentic-collab.adrianlpz-game.workers.dev';
|
|
|
26
26
|
|
|
27
27
|
const COLLAB_CONFIG_PATH = '.agentic/collab.json';
|
|
28
28
|
|
|
29
|
+
// ─── SYNC TABLES — TODO lo que hace a Agentic L4 ──────────────────────────────
|
|
30
|
+
// Regla: si está aquí, cualquier miembro del equipo que haga collab pull
|
|
31
|
+
// recibe el cerebro completo del proyecto — no solo fragmentos.
|
|
32
|
+
//
|
|
33
|
+
// NO sincronizar:
|
|
34
|
+
// regression_snapshots → snapshots de sesión individual, no conocimiento del equipo
|
|
35
|
+
// working_memory → buffer de sesión activa, se resetea por diseño
|
|
36
|
+
// creative_suggestions → sugerencias personales del dev, no del equipo
|
|
37
|
+
// git_context_log → log local de diffs, cada dev tiene su propio git
|
|
38
|
+
// deferred_queue → archivo JSON por dev, no tabla compartida
|
|
39
|
+
//
|
|
29
40
|
const SYNC_TABLES = [
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
'
|
|
33
|
-
'
|
|
34
|
-
'
|
|
35
|
-
'
|
|
41
|
+
|
|
42
|
+
// ── CORE MEMORY (CoALA 4 layers) ───────────────────────────────────────────
|
|
43
|
+
'nodos', // Patrones, errores, decisiones con confianza — EL núcleo
|
|
44
|
+
'episodios', // Historia cruda de cada ciclo
|
|
45
|
+
'entidades', // Módulos, archivos, APIs detectados en el proyecto
|
|
46
|
+
'relaciones_semanticas', // Causal edges: caused_failure, was_fixed_by, verifies, protects
|
|
47
|
+
|
|
48
|
+
// ── CICLOS Y TRAZABILIDAD ──────────────────────────────────────────────────
|
|
49
|
+
'ciclos', // Qué se hizo, cuándo, con qué resultado — historial ejecutivo
|
|
50
|
+
'fases', // Fases dentro de cada ciclo — granularidad total
|
|
51
|
+
'knowledge_docs', // ADRs, gotchas, convenciones — por qué las cosas son como son
|
|
52
|
+
|
|
53
|
+
// ── AST GRAPH ─────────────────────────────────────────────────────────────
|
|
54
|
+
'ast_symbols', // Símbolos del codebase — estructura completa
|
|
55
|
+
'ast_edges', // Relaciones AST — dependencias, imports, call graph
|
|
56
|
+
|
|
57
|
+
// ── PRESERVATION INTELLIGENCE ─────────────────────────────────────────────
|
|
58
|
+
'verified_contracts', // Contratos protegidos — lo que no se puede romper
|
|
59
|
+
'creative_wins', // Mejoras aplicadas — el equipo aprende de lo que funcionó
|
|
60
|
+
'contract_violations',// Violaciones pasadas — el equipo sabe qué rompió qué
|
|
61
|
+
|
|
62
|
+
// ── PREDICCIÓN Y CI/CD ────────────────────────────────────────────────────
|
|
63
|
+
'prediction_log', // Patrones de riesgo predictivo detectados
|
|
64
|
+
'cicd_reports', // Reportes de CI/CD — qué falló en producción
|
|
36
65
|
];
|
|
37
66
|
|
|
38
67
|
// ─── DB LOCAL ──────────────────────────────────────────────────────────────────
|
|
@@ -346,9 +375,14 @@ async function syncDown(projectRoot) {
|
|
|
346
375
|
const placeholders = keys.map(() => '?').join(', ');
|
|
347
376
|
|
|
348
377
|
// Estrategia por tabla:
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
|
|
378
|
+
// OR IGNORE: tablas aditivas — no sobrescribir versión local
|
|
379
|
+
// episodios, ciclos, fases, prediction_log, cicd_reports
|
|
380
|
+
// (cada dev tiene su propia historia de ejecución)
|
|
381
|
+
// OR REPLACE: tablas de conocimiento compartido — toma la más reciente
|
|
382
|
+
// nodos, entidades, relaciones_semanticas, knowledge_docs,
|
|
383
|
+
// ast_symbols, ast_edges, verified_contracts, creative_wins, contract_violations
|
|
384
|
+
const ADDITIVE_TABLES = new Set(['episodios','ciclos','fases','prediction_log','cicd_reports']);
|
|
385
|
+
const strategy = ADDITIVE_TABLES.has(table) ? 'OR IGNORE' : 'OR REPLACE';
|
|
352
386
|
|
|
353
387
|
try {
|
|
354
388
|
localDB.prepare(
|
|
@@ -420,12 +454,25 @@ async function status(projectRoot) {
|
|
|
420
454
|
|
|
421
455
|
function getDateField(table) {
|
|
422
456
|
const fields = {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
457
|
+
// Core memory
|
|
458
|
+
nodos: 'fecha_update',
|
|
459
|
+
episodios: 'fecha',
|
|
460
|
+
entidades: 'fecha_update',
|
|
461
|
+
relaciones_semanticas: 'valid_at',
|
|
462
|
+
// Ciclos y trazabilidad
|
|
463
|
+
ciclos: 'fecha_inicio',
|
|
464
|
+
fases: 'fecha',
|
|
465
|
+
knowledge_docs: 'last_indexed',
|
|
466
|
+
// AST
|
|
467
|
+
ast_symbols: 'last_indexed',
|
|
468
|
+
ast_edges: 'last_indexed',
|
|
469
|
+
// Preservation Intelligence
|
|
470
|
+
verified_contracts: 'updated_at',
|
|
471
|
+
creative_wins: 'created_at',
|
|
472
|
+
contract_violations: 'created_at',
|
|
473
|
+
// Predicción y CI/CD
|
|
474
|
+
prediction_log: 'fecha',
|
|
475
|
+
cicd_reports: 'fecha',
|
|
429
476
|
};
|
|
430
477
|
return fields[table] || 'rowid';
|
|
431
478
|
}
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agentic KDD — Effectiveness Report v1.0
|
|
3
|
+
*
|
|
4
|
+
* Genera datos reales de efectividad comparando primeros ciclos vs últimos.
|
|
5
|
+
* Todo viene de SQLite — cero estimaciones, cero inventados.
|
|
6
|
+
*
|
|
7
|
+
* Métricas:
|
|
8
|
+
* - Error recurrence rate: ¿El mismo error ocurre dos veces?
|
|
9
|
+
* - Stop rate: ¿Cuántas veces el TDD gate paró el ciclo?
|
|
10
|
+
* - First-try test pass rate: ¿Tests pasan en el primer intento?
|
|
11
|
+
* - Rework index: ¿Cuántos ciclos tocan los mismos archivos?
|
|
12
|
+
* - Pattern velocity: ¿Qué tan rápido suben patrones a HIGH?
|
|
13
|
+
* - Memory growth: ¿Cuánto conocimiento acumula por ciclo?
|
|
14
|
+
*
|
|
15
|
+
* Uso:
|
|
16
|
+
* node effectiveness-report.cjs → reporte completo
|
|
17
|
+
* node effectiveness-report.cjs --json → output JSON para dashboards
|
|
18
|
+
* node effectiveness-report.cjs --short → resumen de una línea por métrica
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
'use strict';
|
|
22
|
+
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const fs = require('fs');
|
|
25
|
+
|
|
26
|
+
// ─── DB ───────────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
function openDB(projectRoot) {
|
|
29
|
+
const dbPath = path.join(projectRoot, '.agentic/memoria.db');
|
|
30
|
+
try { return new (require('better-sqlite3'))(dbPath, { readonly: true }); } catch {}
|
|
31
|
+
try { const { DatabaseSync } = require('node:sqlite'); return new DatabaseSync(dbPath); } catch {}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// ─── CALCULAR MÉTRICAS POR VENTANA DE CICLOS ─────────────────────────────────
|
|
36
|
+
|
|
37
|
+
function calcWindowMetrics(db, cycleIds) {
|
|
38
|
+
if (!cycleIds || cycleIds.length === 0) {
|
|
39
|
+
return { stops: 0, stop_rate: 0, errors_found: 0, errors_per_cycle: 0,
|
|
40
|
+
tests_total: 0, tests_passed: 0, first_try_rate: 0,
|
|
41
|
+
patterns_applied: 0, patterns_per_cycle: 0, cycles: 0 };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const placeholders = cycleIds.map(() => '?').join(',');
|
|
45
|
+
const safe = (fn) => { try { return fn(); } catch { return null; } };
|
|
46
|
+
|
|
47
|
+
const stops = safe(() =>
|
|
48
|
+
db.prepare(`SELECT COUNT(*) as n FROM ciclos WHERE estado='STOP' AND ciclo_id IN (${placeholders})`).get(...cycleIds)?.n
|
|
49
|
+
) || 0;
|
|
50
|
+
|
|
51
|
+
const errorsFound = safe(() => {
|
|
52
|
+
const rows = db.prepare(`SELECT errores_encontrados FROM ciclos WHERE ciclo_id IN (${placeholders})`).all(...cycleIds);
|
|
53
|
+
return rows.reduce((s, r) => s + (parseInt(r.errores_encontrados) || 0), 0);
|
|
54
|
+
}) || 0;
|
|
55
|
+
|
|
56
|
+
// Tests: sumar de todos los ciclos
|
|
57
|
+
let testsTotal = 0, testsPassed = 0;
|
|
58
|
+
safe(() => {
|
|
59
|
+
const rows = db.prepare(`SELECT tests_corridos FROM ciclos WHERE ciclo_id IN (${placeholders})`).all(...cycleIds);
|
|
60
|
+
rows.forEach(r => {
|
|
61
|
+
try {
|
|
62
|
+
const t = JSON.parse(r.tests_corridos || '{}');
|
|
63
|
+
testsTotal += (t.total || parseInt(r.tests_corridos) || 0);
|
|
64
|
+
testsPassed += (t.passed || parseInt(r.tests_corridos) || 0);
|
|
65
|
+
} catch {
|
|
66
|
+
const n = parseInt(r.tests_corridos) || 0;
|
|
67
|
+
testsTotal += n; testsPassed += n;
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Patrones aplicados
|
|
73
|
+
const patternsApplied = safe(() => {
|
|
74
|
+
const rows = db.prepare(`SELECT patrones_aplicados FROM ciclos WHERE ciclo_id IN (${placeholders})`).all(...cycleIds);
|
|
75
|
+
return rows.reduce((s, r) => {
|
|
76
|
+
try { return s + JSON.parse(r.patrones_aplicados || '[]').length; }
|
|
77
|
+
catch { return s; }
|
|
78
|
+
}, 0);
|
|
79
|
+
}) || 0;
|
|
80
|
+
|
|
81
|
+
const n = cycleIds.length;
|
|
82
|
+
return {
|
|
83
|
+
cycles: n,
|
|
84
|
+
stops,
|
|
85
|
+
stop_rate: n > 0 ? Math.round((stops / n) * 100) : 0,
|
|
86
|
+
errors_found: errorsFound,
|
|
87
|
+
errors_per_cycle: n > 0 ? Math.round((errorsFound / n) * 10) / 10 : 0,
|
|
88
|
+
tests_total: testsTotal,
|
|
89
|
+
tests_passed: testsPassed,
|
|
90
|
+
first_try_rate: testsTotal > 0 ? Math.round((testsPassed / testsTotal) * 100) : 100,
|
|
91
|
+
patterns_applied: patternsApplied,
|
|
92
|
+
patterns_per_cycle: n > 0 ? Math.round((patternsApplied / n) * 10) / 10 : 0,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ─── ERROR RECURRENCE RATE ───────────────────────────────────────────────────
|
|
97
|
+
|
|
98
|
+
function calcErrorRecurrence(db, allCycleIds, splitAt) {
|
|
99
|
+
const safe = (fn) => { try { return fn(); } catch { return 0; } };
|
|
100
|
+
|
|
101
|
+
// Errores únicos en primera mitad
|
|
102
|
+
const firstHalf = allCycleIds.slice(0, splitAt);
|
|
103
|
+
const secondHalf = allCycleIds.slice(splitAt);
|
|
104
|
+
|
|
105
|
+
if (firstHalf.length === 0 || secondHalf.length === 0) return { rate: 0, recurred: 0, total_first: 0 };
|
|
106
|
+
|
|
107
|
+
const ph1 = firstHalf.map(() => '?').join(',');
|
|
108
|
+
const ph2 = secondHalf.map(() => '?').join(',');
|
|
109
|
+
|
|
110
|
+
// Patrones de error en primera mitad
|
|
111
|
+
const firstErrors = safe(() =>
|
|
112
|
+
db.prepare(`SELECT titulo FROM nodos WHERE tipo='error' AND fecha_creacion IN (
|
|
113
|
+
SELECT fecha_inicio FROM ciclos WHERE ciclo_id IN (${ph1})
|
|
114
|
+
)`).all(...firstHalf).map(r => r.titulo)
|
|
115
|
+
) || [];
|
|
116
|
+
|
|
117
|
+
// Mismos patrones vistos en segunda mitad
|
|
118
|
+
let recurred = 0;
|
|
119
|
+
if (firstErrors.length > 0 && secondHalf.length > 0) {
|
|
120
|
+
firstErrors.forEach(titulo => {
|
|
121
|
+
if (!titulo) return;
|
|
122
|
+
const found = safe(() =>
|
|
123
|
+
db.prepare(`SELECT COUNT(*) as n FROM nodos WHERE tipo='error' AND titulo=? AND fecha_creacion IN (
|
|
124
|
+
SELECT fecha_inicio FROM ciclos WHERE ciclo_id IN (${ph2})
|
|
125
|
+
)`).get(titulo, ...secondHalf)?.n
|
|
126
|
+
);
|
|
127
|
+
if (found > 0) recurred++;
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return {
|
|
132
|
+
rate: firstErrors.length > 0 ? Math.round((recurred / firstErrors.length) * 100) : 0,
|
|
133
|
+
recurred,
|
|
134
|
+
total_first: firstErrors.length,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// ─── PATTERN VELOCITY ────────────────────────────────────────────────────────
|
|
139
|
+
|
|
140
|
+
function calcPatternVelocity(db) {
|
|
141
|
+
const safe = (fn) => { try { return fn(); } catch { return null; } };
|
|
142
|
+
|
|
143
|
+
const highNodes = safe(() =>
|
|
144
|
+
db.prepare(`SELECT aplicado, fecha_creacion, fecha_update FROM nodos
|
|
145
|
+
WHERE confianza='ALTA' AND estado='ACTIVO' AND tipo='patron'`).all()
|
|
146
|
+
) || [];
|
|
147
|
+
|
|
148
|
+
if (highNodes.length === 0) return { avg_cycles_to_high: null, total_high: 0 };
|
|
149
|
+
|
|
150
|
+
// Promedio de aplicaciones para llegar a HIGH (proxy: campo aplicado)
|
|
151
|
+
const avgApplied = highNodes.reduce((s, n) => s + (n.aplicado || 0), 0) / highNodes.length;
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
avg_cycles_to_high: Math.round(avgApplied * 10) / 10,
|
|
155
|
+
total_high: highNodes.length,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// ─── REWORK INDEX ────────────────────────────────────────────────────────────
|
|
160
|
+
|
|
161
|
+
function calcReworkIndex(db, cycleIds) {
|
|
162
|
+
if (!cycleIds || cycleIds.length < 2) return { index: 0, reworked_areas: 0 };
|
|
163
|
+
|
|
164
|
+
const safe = (fn) => { try { return fn(); } catch { return null; } };
|
|
165
|
+
const ph = cycleIds.map(() => '?').join(',');
|
|
166
|
+
|
|
167
|
+
// Áreas que aparecen en más de un ciclo = rework
|
|
168
|
+
const areas = safe(() =>
|
|
169
|
+
db.prepare(`SELECT n.area, COUNT(DISTINCT c.ciclo_id) as cycles
|
|
170
|
+
FROM nodos n
|
|
171
|
+
JOIN ciclos c ON n.fecha_creacion >= c.fecha_inicio AND n.fecha_creacion <= COALESCE(c.fecha_fin, datetime('now'))
|
|
172
|
+
WHERE c.ciclo_id IN (${ph})
|
|
173
|
+
GROUP BY n.area HAVING cycles > 1`).all(...cycleIds)
|
|
174
|
+
) || [];
|
|
175
|
+
|
|
176
|
+
const totalAreas = safe(() =>
|
|
177
|
+
db.prepare(`SELECT COUNT(DISTINCT area) as n FROM nodos WHERE estado='ACTIVO'`).get()?.n
|
|
178
|
+
) || 1;
|
|
179
|
+
|
|
180
|
+
return {
|
|
181
|
+
index: areas.length,
|
|
182
|
+
reworked_areas:areas.length,
|
|
183
|
+
total_areas: totalAreas,
|
|
184
|
+
rate: Math.round((areas.length / totalAreas) * 100),
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// ─── REPORTE COMPLETO ─────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
function generateReport(projectRoot, options = {}) {
|
|
191
|
+
projectRoot = projectRoot || process.cwd();
|
|
192
|
+
const db = openDB(projectRoot);
|
|
193
|
+
if (!db) return { error: 'DB no disponible' };
|
|
194
|
+
|
|
195
|
+
const safe = (fn) => { try { return fn(); } catch { return null; } };
|
|
196
|
+
|
|
197
|
+
// Obtener todos los ciclos en orden cronológico
|
|
198
|
+
const allCycles = safe(() =>
|
|
199
|
+
db.prepare(`SELECT ciclo_id, descripcion, fecha_inicio, estado FROM ciclos ORDER BY fecha_inicio ASC`).all()
|
|
200
|
+
) || [];
|
|
201
|
+
|
|
202
|
+
if (allCycles.length < 2) {
|
|
203
|
+
db.close();
|
|
204
|
+
return { error: `Solo ${allCycles.length} ciclo(s) — se necesitan al menos 2 para comparar`, cycles: allCycles.length };
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
const n = allCycles.length;
|
|
208
|
+
const splitAt = Math.max(1, Math.floor(n / 2));
|
|
209
|
+
const firstIds = allCycles.slice(0, splitAt).map(c => c.ciclo_id);
|
|
210
|
+
const lastIds = allCycles.slice(splitAt).map(c => c.ciclo_id);
|
|
211
|
+
const last3Ids = allCycles.slice(-3).map(c => c.ciclo_id);
|
|
212
|
+
const first3Ids = allCycles.slice(0, 3).map(c => c.ciclo_id);
|
|
213
|
+
|
|
214
|
+
// Calcular métricas
|
|
215
|
+
const first = calcWindowMetrics(db, firstIds);
|
|
216
|
+
const last = calcWindowMetrics(db, lastIds);
|
|
217
|
+
const first3 = calcWindowMetrics(db, first3Ids);
|
|
218
|
+
const last3 = calcWindowMetrics(db, last3Ids);
|
|
219
|
+
|
|
220
|
+
const recurrence = calcErrorRecurrence(db, allCycles.map(c => c.ciclo_id), splitAt);
|
|
221
|
+
const velocity = calcPatternVelocity(db);
|
|
222
|
+
const rework = calcReworkIndex(db, last3Ids);
|
|
223
|
+
|
|
224
|
+
// Memoria evolution
|
|
225
|
+
const memFirst = safe(() =>
|
|
226
|
+
db.prepare(`SELECT COUNT(*) as n FROM nodos WHERE fecha_creacion <= (
|
|
227
|
+
SELECT fecha_inicio FROM ciclos ORDER BY fecha_inicio ASC LIMIT 1 OFFSET ?
|
|
228
|
+
)`).get(Math.max(0, splitAt - 1))?.n
|
|
229
|
+
) || 0;
|
|
230
|
+
const memNow = safe(() =>
|
|
231
|
+
db.prepare(`SELECT COUNT(*) as n FROM nodos WHERE estado='ACTIVO'`).get()?.n
|
|
232
|
+
) || 0;
|
|
233
|
+
|
|
234
|
+
// Delta helpers
|
|
235
|
+
const delta = (before, after) => {
|
|
236
|
+
if (before === 0 && after === 0) return { val: 0, pct: 0, dir: '→' };
|
|
237
|
+
if (before === 0) return { val: after, pct: 100, dir: '↑' };
|
|
238
|
+
const pct = Math.round(((after - before) / before) * 100);
|
|
239
|
+
return { val: after - before, pct: Math.abs(pct), dir: pct > 0 ? '↑' : pct < 0 ? '↓' : '→' };
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const report = {
|
|
243
|
+
generated: new Date().toISOString(),
|
|
244
|
+
project: path.basename(projectRoot),
|
|
245
|
+
total_cycles: n,
|
|
246
|
+
window: {
|
|
247
|
+
first_half: { cycles: firstIds.length, label: `ciclos 1-${firstIds.length}` },
|
|
248
|
+
second_half: { cycles: lastIds.length, label: `ciclos ${firstIds.length + 1}-${n}` },
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
metrics: {
|
|
252
|
+
stop_rate: {
|
|
253
|
+
label: 'Stop rate (TDD gate)',
|
|
254
|
+
description: 'Ciclos donde el harness tuvo que parar',
|
|
255
|
+
first: `${first.stop_rate}%`,
|
|
256
|
+
last: `${last.stop_rate}%`,
|
|
257
|
+
delta: delta(first.stop_rate, last.stop_rate),
|
|
258
|
+
trend: last.stop_rate < first.stop_rate ? 'improving' : last.stop_rate > first.stop_rate ? 'degrading' : 'stable',
|
|
259
|
+
raw: { first: first.stop_rate, last: last.stop_rate },
|
|
260
|
+
},
|
|
261
|
+
errors_per_cycle: {
|
|
262
|
+
label: 'Errores por ciclo',
|
|
263
|
+
description: 'Promedio de errores nuevos encontrados por ciclo',
|
|
264
|
+
first: first.errors_per_cycle,
|
|
265
|
+
last: last.errors_per_cycle,
|
|
266
|
+
delta: delta(first.errors_per_cycle, last.errors_per_cycle),
|
|
267
|
+
trend: last.errors_per_cycle < first.errors_per_cycle ? 'improving' : 'stable',
|
|
268
|
+
raw: { first: first.errors_per_cycle, last: last.errors_per_cycle },
|
|
269
|
+
},
|
|
270
|
+
first_try_rate: {
|
|
271
|
+
label: 'Tests en primer intento',
|
|
272
|
+
description: '% de tests que pasan sin self-healing',
|
|
273
|
+
first: `${first.first_try_rate}%`,
|
|
274
|
+
last: `${last.first_try_rate}%`,
|
|
275
|
+
delta: delta(first.first_try_rate, last.first_try_rate),
|
|
276
|
+
trend: last.first_try_rate >= first.first_try_rate ? 'improving' : 'degrading',
|
|
277
|
+
raw: { first: first.first_try_rate, last: last.first_try_rate },
|
|
278
|
+
},
|
|
279
|
+
error_recurrence: {
|
|
280
|
+
label: 'Recurrencia de errores',
|
|
281
|
+
description: '% de errores de la primera mitad que volvieron a ocurrir',
|
|
282
|
+
value: `${recurrence.rate}%`,
|
|
283
|
+
recurred: recurrence.recurred,
|
|
284
|
+
total: recurrence.total_first,
|
|
285
|
+
trend: recurrence.rate < 20 ? 'good' : recurrence.rate < 50 ? 'moderate' : 'needs_work',
|
|
286
|
+
},
|
|
287
|
+
pattern_velocity: {
|
|
288
|
+
label: 'Velocidad de patrones',
|
|
289
|
+
description: 'Promedio de aplicaciones para llegar a HIGH confidence',
|
|
290
|
+
avg_to_high: velocity.avg_cycles_to_high,
|
|
291
|
+
total_high: velocity.total_high,
|
|
292
|
+
trend: velocity.total_high > 5 ? 'good' : 'building',
|
|
293
|
+
},
|
|
294
|
+
memory_growth: {
|
|
295
|
+
label: 'Crecimiento de memoria',
|
|
296
|
+
description: 'Nodos de conocimiento acumulados',
|
|
297
|
+
at_start: memFirst,
|
|
298
|
+
now: memNow,
|
|
299
|
+
delta: memNow - memFirst,
|
|
300
|
+
per_cycle: n > 0 ? Math.round(((memNow - memFirst) / n) * 10) / 10 : 0,
|
|
301
|
+
trend: memNow > memFirst ? 'growing' : 'stable',
|
|
302
|
+
},
|
|
303
|
+
patterns_per_cycle: {
|
|
304
|
+
label: 'Patrones aplicados por ciclo',
|
|
305
|
+
description: 'Cuántos patrones de memoria usa el agente en cada ciclo',
|
|
306
|
+
first: first.patterns_per_cycle,
|
|
307
|
+
last: last.patterns_per_cycle,
|
|
308
|
+
delta: delta(first.patterns_per_cycle, last.patterns_per_cycle),
|
|
309
|
+
trend: last.patterns_per_cycle >= first.patterns_per_cycle ? 'improving' : 'stable',
|
|
310
|
+
raw: { first: first.patterns_per_cycle, last: last.patterns_per_cycle },
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
|
|
314
|
+
summary: {
|
|
315
|
+
improving: [],
|
|
316
|
+
stable: [],
|
|
317
|
+
needs_work:[],
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
// Clasificar métricas en el summary
|
|
322
|
+
Object.entries(report.metrics).forEach(([key, m]) => {
|
|
323
|
+
if (!m.trend) return;
|
|
324
|
+
if (['improving','good','growing'].includes(m.trend)) report.summary.improving.push(m.label);
|
|
325
|
+
else if (['degrading','needs_work'].includes(m.trend)) report.summary.needs_work.push(m.label);
|
|
326
|
+
else report.summary.stable.push(m.label);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
db.close();
|
|
330
|
+
return report;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// ─── PRINT REPORT ─────────────────────────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
function printReport(report) {
|
|
336
|
+
if (report.error) {
|
|
337
|
+
console.log(`\n[REPORT] ${report.error}\n`);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const arrow = (trend) => trend === 'improving' || trend === 'good' || trend === 'growing' ? '✅' :
|
|
342
|
+
trend === 'degrading' || trend === 'needs_work' ? '⚠️' : '→';
|
|
343
|
+
|
|
344
|
+
console.log('\n' + '═'.repeat(60));
|
|
345
|
+
console.log(' Agentic KDD — Effectiveness Report');
|
|
346
|
+
console.log(` ${report.project} · ${report.total_cycles} ciclos totales`);
|
|
347
|
+
console.log('═'.repeat(60));
|
|
348
|
+
console.log(`\n Comparando: ${report.window.first_half.label} vs ${report.window.second_half.label}\n`);
|
|
349
|
+
|
|
350
|
+
const m = report.metrics;
|
|
351
|
+
|
|
352
|
+
console.log(' ① Stop rate (TDD gate paró el ciclo)');
|
|
353
|
+
console.log(` Antes: ${m.stop_rate.first} → Ahora: ${m.stop_rate.last} ${arrow(m.stop_rate.trend)}`);
|
|
354
|
+
if (m.stop_rate.delta.pct > 0) console.log(` ${m.stop_rate.delta.dir} ${m.stop_rate.delta.pct}% ${m.stop_rate.trend}`);
|
|
355
|
+
|
|
356
|
+
console.log('\n ② Errores por ciclo');
|
|
357
|
+
console.log(` Antes: ${m.errors_per_cycle.first} → Ahora: ${m.errors_per_cycle.last} ${arrow(m.errors_per_cycle.trend)}`);
|
|
358
|
+
|
|
359
|
+
console.log('\n ③ Tests pasan en primer intento');
|
|
360
|
+
console.log(` Antes: ${m.first_try_rate.first} → Ahora: ${m.first_try_rate.last} ${arrow(m.first_try_rate.trend)}`);
|
|
361
|
+
|
|
362
|
+
console.log('\n ④ Recurrencia de errores');
|
|
363
|
+
console.log(` ${m.error_recurrence.recurred}/${m.error_recurrence.total} errores volvieron a ocurrir = ${m.error_recurrence.value} ${arrow(m.error_recurrence.trend)}`);
|
|
364
|
+
|
|
365
|
+
console.log('\n ⑤ Memoria acumulada');
|
|
366
|
+
console.log(` Inicio: ${m.memory_growth.at_start} nodos → Ahora: ${m.memory_growth.now} nodos (+${m.memory_growth.delta})`);
|
|
367
|
+
console.log(` ${m.memory_growth.per_cycle} nodos nuevos por ciclo promedio`);
|
|
368
|
+
|
|
369
|
+
console.log('\n ⑥ Patrones de memoria aplicados por ciclo');
|
|
370
|
+
console.log(` Antes: ${m.patterns_per_cycle.first} → Ahora: ${m.patterns_per_cycle.last} ${arrow(m.patterns_per_cycle.trend)}`);
|
|
371
|
+
|
|
372
|
+
console.log('\n ⑦ Patrones con confianza HIGH');
|
|
373
|
+
console.log(` ${m.pattern_velocity.total_high} patrones promovidos a HIGH`);
|
|
374
|
+
if (m.pattern_velocity.avg_to_high) console.log(` Promedio de ${m.pattern_velocity.avg_to_high} aplicaciones para llegar a HIGH`);
|
|
375
|
+
|
|
376
|
+
if (report.summary.improving.length > 0) {
|
|
377
|
+
console.log(`\n ✅ Mejorando: ${report.summary.improving.join(' · ')}`);
|
|
378
|
+
}
|
|
379
|
+
if (report.summary.needs_work.length > 0) {
|
|
380
|
+
console.log(` ⚠️ Atención: ${report.summary.needs_work.join(' · ')}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
console.log('\n' + '═'.repeat(60));
|
|
384
|
+
console.log(' Generado:', new Date(report.generated).toLocaleString('es-ES'));
|
|
385
|
+
console.log('═'.repeat(60) + '\n');
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ─── CLI ──────────────────────────────────────────────────────────────────────
|
|
389
|
+
|
|
390
|
+
if (require.main === module) {
|
|
391
|
+
const args = process.argv.slice(2);
|
|
392
|
+
const projectRoot = process.cwd();
|
|
393
|
+
const report = generateReport(projectRoot);
|
|
394
|
+
|
|
395
|
+
if (args.includes('--json')) {
|
|
396
|
+
console.log(JSON.stringify(report, null, 2));
|
|
397
|
+
} else {
|
|
398
|
+
printReport(report);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
module.exports = { generateReport, printReport };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agentic-kdd",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.1",
|
|
4
4
|
"description": "Autonomous development pipeline — aa: · ag: · audit: · AST graph · Harness · Specs · Impact analysis · Decision trail · Metrics · MCP server. Works with Cursor and Claude Code.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|