agentic-kdd 3.1.1 → 3.2.2

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.
@@ -0,0 +1,265 @@
1
+ /**
2
+ * Agentic KDD — Session Guard v1.0
3
+ *
4
+ * Problema que resuelve:
5
+ * - Cursor se cierra y pierdes el hilo
6
+ * - Cambias de PC y empiezas chat nuevo
7
+ * - No recuerdas en qué ibas exactamente
8
+ *
9
+ * Solución:
10
+ * - Cada 5 ciclos aa: guarda un checkpoint en .agentic/checkpoint.md
11
+ * - akdd historial muestra el checkpoint listo para pegar en el chat nuevo
12
+ * - El chat nuevo recupera el contexto exacto de donde se quedó
13
+ *
14
+ * Uso:
15
+ * node session-guard.cjs checkpoint — genera checkpoint ahora
16
+ * node session-guard.cjs historial — muestra el último checkpoint
17
+ * node session-guard.cjs status — cuántos ciclos hasta el próximo checkpoint
18
+ */
19
+
20
+ 'use strict';
21
+
22
+ const path = require('path');
23
+ const fs = require('fs');
24
+
25
+ const CHECKPOINT_EVERY = 5;
26
+ const CHECKPOINT_PATH = '.agentic/checkpoint.md';
27
+ const COUNTER_KEY = 'session_guard_cycle_count';
28
+
29
+ // ─── DB ───────────────────────────────────────────────────────────────────────
30
+
31
+ function openDB(projectRoot) {
32
+ const dbPath = path.join(projectRoot, '.agentic/memoria.db');
33
+ try { return new (require('better-sqlite3'))(dbPath); } catch {}
34
+ try { const { DatabaseSync } = require('node:sqlite'); return new DatabaseSync(dbPath); } catch {}
35
+ return null;
36
+ }
37
+
38
+ // ─── LEER CICLOS RECIENTES ────────────────────────────────────────────────────
39
+
40
+ function getRecentCycles(db, limit = 5) {
41
+ try {
42
+ return db.prepare(`
43
+ SELECT ciclo_id, descripcion, tipo, estado, fecha_inicio, fecha_fin,
44
+ tests_corridos, patrones_aplicados, errores_encontrados, fases_completadas
45
+ FROM ciclos
46
+ ORDER BY fecha_inicio DESC
47
+ LIMIT ?
48
+ `).all(limit);
49
+ } catch { return []; }
50
+ }
51
+
52
+ function getProjectStats(db) {
53
+ try {
54
+ const nodes = db.prepare("SELECT COUNT(*) as n FROM nodos WHERE estado='ACTIVO'").get()?.n || 0;
55
+ const high = db.prepare("SELECT COUNT(*) as n FROM nodos WHERE confianza='ALTA' AND estado='ACTIVO'").get()?.n || 0;
56
+ const cycles = db.prepare("SELECT COUNT(*) as n FROM ciclos").get()?.n || 0;
57
+ const stops = db.prepare("SELECT COUNT(*) as n FROM ciclos WHERE estado='STOP'").get()?.n || 0;
58
+ const errors = db.prepare("SELECT COUNT(*) as n FROM nodos WHERE tipo='error' AND estado='ACTIVO'").get()?.n || 0;
59
+ return { nodes, high, cycles, stops, errors };
60
+ } catch { return { nodes: 0, high: 0, cycles: 0, stops: 0, errors: 0 }; }
61
+ }
62
+
63
+ function getLastModifiedFiles(db, cicloId) {
64
+ try {
65
+ const trail = db.prepare(`
66
+ SELECT descripcion FROM decisiones
67
+ WHERE ciclo_id = ?
68
+ AND (descripcion LIKE '%.ts%' OR descripcion LIKE '%.js%'
69
+ OR descripcion LIKE '%.tsx%' OR descripcion LIKE '%.jsx%'
70
+ OR descripcion LIKE '%.py%')
71
+ LIMIT 5
72
+ `).all(cicloId);
73
+ return trail.map(t => t.descripcion?.substring(0, 60)).filter(Boolean);
74
+ } catch { return []; }
75
+ }
76
+
77
+ // ─── GENERAR CHECKPOINT ───────────────────────────────────────────────────────
78
+
79
+ function generateCheckpoint(projectRoot) {
80
+ projectRoot = projectRoot || process.cwd();
81
+ const db = openDB(projectRoot);
82
+ if (!db) return null;
83
+
84
+ const cycles = getRecentCycles(db, 5);
85
+ const stats = getProjectStats(db);
86
+ const now = new Date();
87
+ const dateStr = now.toLocaleDateString('es-ES', { day:'2-digit', month:'2-digit', year:'numeric' });
88
+ const timeStr = now.toTimeString().substring(0, 5);
89
+
90
+ if (cycles.length === 0) {
91
+ db.close();
92
+ return null;
93
+ }
94
+
95
+ const latest = cycles[0];
96
+ const prev = cycles.slice(1);
97
+
98
+ // Parsear fases completadas del último ciclo
99
+ let fasesInfo = '';
100
+ try {
101
+ const fases = JSON.parse(latest.fases_completadas || '[]');
102
+ if (fases.length > 0) fasesInfo = ` — ${fases.length} fases completadas`;
103
+ } catch {}
104
+
105
+ // Parsear patrones del último ciclo
106
+ let patronesInfo = '';
107
+ try {
108
+ const pats = JSON.parse(latest.patrones_aplicados || '[]');
109
+ if (pats.length > 0) patronesInfo = `, ${pats.length} patrones aplicados`;
110
+ } catch {}
111
+
112
+ // Archivos del último ciclo
113
+ const files = getLastModifiedFiles(db, latest.ciclo_id);
114
+ const filesStr = files.length > 0 ? `\nArchivos: ${files.join(', ')}` : '';
115
+
116
+ // Construir checkpoint
117
+ const lines = [
118
+ `# Checkpoint Agentic KDD — ${dateStr} ${timeStr}`,
119
+ '',
120
+ `> Proyecto: ${path.basename(projectRoot)}`,
121
+ `> Ciclos totales: ${stats.cycles}`,
122
+ '',
123
+ '---',
124
+ '',
125
+ `## Última tarea (ciclo ${stats.cycles})`,
126
+ `**${latest.descripcion || 'Sin descripción'}**`,
127
+ `Estado: ${latest.estado || 'completado'}${fasesInfo}${patronesInfo}${filesStr}`,
128
+ '',
129
+ ];
130
+
131
+ if (prev.length > 0) {
132
+ lines.push('## Las anteriores');
133
+ prev.forEach((c, i) => {
134
+ const fecha = c.fecha_inicio ? new Date(c.fecha_inicio).toLocaleDateString('es-ES', {day:'2-digit', month:'2-digit'}) : '';
135
+ lines.push(`${i + 1}. ${fecha ? `[${fecha}] ` : ''}${c.descripcion?.substring(0, 80) || 'Sin descripción'}`);
136
+ });
137
+ lines.push('');
138
+ }
139
+
140
+ lines.push('## Estado del proyecto');
141
+ lines.push(`- ${stats.nodes} nodos en memoria · ${stats.high} reglas HIGH`);
142
+ lines.push(`- ${stats.cycles} ciclos completados · ${stats.stops} stops`);
143
+ lines.push(`- ${stats.errors} errores documentados`);
144
+ lines.push('');
145
+ lines.push('## Para retomar en un chat nuevo');
146
+ lines.push('```');
147
+ lines.push('akdd historial');
148
+ lines.push('```');
149
+ lines.push('Pega el output al inicio del nuevo chat y escribe:');
150
+ lines.push('```');
151
+ lines.push(`aa: continúa — ${latest.descripcion?.substring(0, 60) || 'la última tarea'}`);
152
+ lines.push('```');
153
+ lines.push('');
154
+ lines.push(`---`);
155
+ lines.push(`*Generado automáticamente por Session Guard · ${dateStr} ${timeStr}*`);
156
+
157
+ const checkpointContent = lines.join('\n');
158
+ const checkpointPath = path.join(projectRoot, CHECKPOINT_PATH);
159
+
160
+ try {
161
+ fs.writeFileSync(checkpointPath, checkpointContent);
162
+ } catch {}
163
+
164
+ db.close();
165
+ return checkpointContent;
166
+ }
167
+
168
+ // ─── MOSTRAR HISTORIAL (para pegar en chat nuevo) ─────────────────────────────
169
+
170
+ function showHistorial(projectRoot) {
171
+ projectRoot = projectRoot || process.cwd();
172
+ const checkpointPath = path.join(projectRoot, CHECKPOINT_PATH);
173
+
174
+ if (!fs.existsSync(checkpointPath)) {
175
+ // Generar uno ahora si no existe
176
+ const generated = generateCheckpoint(projectRoot);
177
+ if (!generated) {
178
+ console.log('\n[SESSION] Sin historial todavía — corre ciclos aa: primero.\n');
179
+ return null;
180
+ }
181
+ return generated;
182
+ }
183
+
184
+ const content = fs.readFileSync(checkpointPath, 'utf8');
185
+
186
+ console.log('\n' + '═'.repeat(60));
187
+ console.log(' 📋 HISTORIAL — Pegar al inicio del chat nuevo');
188
+ console.log('═'.repeat(60));
189
+ console.log('\n' + content);
190
+ console.log('═'.repeat(60));
191
+ console.log('\n Copia todo lo de arriba y pégalo en el nuevo chat.\n');
192
+
193
+ return content;
194
+ }
195
+
196
+ // ─── CHECK Y AUTO-CHECKPOINT ──────────────────────────────────────────────────
197
+
198
+ /**
199
+ * Llamar al final de cada ciclo desde grafo.cjs.
200
+ * Genera checkpoint cada CHECKPOINT_EVERY ciclos.
201
+ */
202
+ function maybeCheckpoint(projectRoot, currentCycleCount) {
203
+ if (currentCycleCount % CHECKPOINT_EVERY === 0) {
204
+ generateCheckpoint(projectRoot);
205
+ return true;
206
+ }
207
+ return false;
208
+ }
209
+
210
+ // ─── STATUS ───────────────────────────────────────────────────────────────────
211
+
212
+ function getStatus(projectRoot) {
213
+ projectRoot = projectRoot || process.cwd();
214
+ const db = openDB(projectRoot);
215
+ if (!db) return { cycles: 0, next_checkpoint: CHECKPOINT_EVERY };
216
+
217
+ const cycles = db.prepare("SELECT COUNT(*) as n FROM ciclos").get()?.n || 0;
218
+ db.close();
219
+
220
+ const remaining = CHECKPOINT_EVERY - (cycles % CHECKPOINT_EVERY);
221
+ const checkpointExists = fs.existsSync(path.join(projectRoot, CHECKPOINT_PATH));
222
+
223
+ return {
224
+ cycles,
225
+ checkpoint_every: CHECKPOINT_EVERY,
226
+ cycles_until_next: remaining === CHECKPOINT_EVERY ? 0 : remaining,
227
+ last_checkpoint: checkpointExists
228
+ ? fs.statSync(path.join(projectRoot, CHECKPOINT_PATH)).mtime.toLocaleDateString('es-ES')
229
+ : 'nunca',
230
+ };
231
+ }
232
+
233
+ // ─── CLI ──────────────────────────────────────────────────────────────────────
234
+
235
+ if (require.main === module) {
236
+ const [,, cmd] = process.argv;
237
+ const projectRoot = process.cwd();
238
+
239
+ switch (cmd) {
240
+ case 'checkpoint':
241
+ const c = generateCheckpoint(projectRoot);
242
+ if (c) console.log(`\n✅ Checkpoint guardado en ${CHECKPOINT_PATH}\n`);
243
+ else console.log('\n❌ Sin ciclos para guardar todavía.\n');
244
+ break;
245
+
246
+ case 'historial':
247
+ showHistorial(projectRoot);
248
+ break;
249
+
250
+ case 'status': {
251
+ const s = getStatus(projectRoot);
252
+ console.log(`\nSession Guard Status:`);
253
+ console.log(` Ciclos totales: ${s.cycles}`);
254
+ console.log(` Checkpoint cada: ${s.checkpoint_every} ciclos`);
255
+ console.log(` Ciclos hasta próximo: ${s.cycles_until_next || CHECKPOINT_EVERY}`);
256
+ console.log(` Último checkpoint: ${s.last_checkpoint}\n`);
257
+ break;
258
+ }
259
+
260
+ default:
261
+ console.log('Uso: node session-guard.cjs [checkpoint | historial | status]');
262
+ }
263
+ }
264
+
265
+ module.exports = { generateCheckpoint, showHistorial, maybeCheckpoint, getStatus };