agentic-kdd 2.0.7 → 2.1.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.
@@ -0,0 +1,238 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * Agentic KDD — Watch Errors
6
+ * Observa la salida del servidor de desarrollo y registra errores automáticamente en memoria KDD.
7
+ *
8
+ * Uso:
9
+ * npm run dev 2>&1 | node .agentic/grafo/watch-errors.cjs
10
+ * npm run build 2>&1 | node .agentic/grafo/watch-errors.cjs
11
+ *
12
+ * O agrega a package.json:
13
+ * "dev:kdd": "npm run dev 2>&1 | node .agentic/grafo/watch-errors.cjs"
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const readline = require('readline');
19
+
20
+ const ROOT = path.join(__dirname, '..', '..');
21
+ const ERRORES_PATH = path.join(ROOT, '.agentic', 'memoria', 'errores.md');
22
+ const GRAFO_PATH = path.join(ROOT, '.agentic', 'grafo', 'grafo.cjs');
23
+ const LOG_PATH = path.join(ROOT, '_output', 'watch-errors.log');
24
+
25
+ // ─── Patrones de error por stack ──────────────────────────────────────────────
26
+ const ERROR_PATTERNS = [
27
+ // TypeScript
28
+ { regex: /error TS(\d+): (.+)/i, tipo: 'typescript', extraer: (m) => ({ codigo: m[1], mensaje: m[2] }) },
29
+ { regex: /Type '(.+)' is not assignable/i, tipo: 'typescript', extraer: (m) => ({ mensaje: m[0].slice(0,120) }) },
30
+ { regex: /Property '(.+)' does not exist/i, tipo: 'typescript', extraer: (m) => ({ mensaje: m[0].slice(0,120) }) },
31
+ { regex: /Cannot find module '(.+)'/i, tipo: 'typescript', extraer: (m) => ({ mensaje: m[0].slice(0,120), modulo: m[1] }) },
32
+
33
+ // Next.js / React
34
+ { regex: /Error: (.+)\n.*at (.+\.tsx?)/i, tipo: 'nextjs', extraer: (m) => ({ mensaje: m[1], archivo: m[2] }) },
35
+ { regex: /Unhandled Runtime Error\n(.+)/i, tipo: 'runtime', extraer: (m) => ({ mensaje: m[1] }) },
36
+ { regex: /Module not found: Error: (.+)/i, tipo: 'module', extraer: (m) => ({ mensaje: m[1] }) },
37
+ { regex: /SyntaxError: (.+)/i, tipo: 'syntax', extraer: (m) => ({ mensaje: m[1] }) },
38
+
39
+ // Node.js / Express
40
+ { regex: /UnhandledPromiseRejection: (.+)/i, tipo: 'promise', extraer: (m) => ({ mensaje: m[1] }) },
41
+ { regex: /ECONNREFUSED (\S+)/i, tipo: 'connection', extraer: (m) => ({ mensaje: 'Connection refused: '+m[1] }) },
42
+ { regex: /ENOENT: no such file.+?'(.+?)'/i, tipo: 'filesystem', extraer: (m) => ({ mensaje: 'File not found: '+m[1] }) },
43
+
44
+ // SQL / Supabase / Prisma
45
+ { regex: /invalid input syntax for type (.+)/i, tipo: 'database', extraer: (m) => ({ mensaje: m[0].slice(0,120) }) },
46
+ { regex: /relation "(.+)" does not exist/i, tipo: 'database', extraer: (m) => ({ mensaje: 'Tabla no existe: '+m[1] }) },
47
+ { regex: /null value in column "(.+)" violates/i, tipo: 'database', extraer: (m) => ({ mensaje: 'Campo requerido: '+m[1] }) },
48
+ { regex: /duplicate key value violates unique/i, tipo: 'database', extraer: (m) => ({ mensaje: m[0].slice(0,80) }) },
49
+ { regex: /PrismaClientKnownRequestError.+?code: '(.+?)'/i, tipo: 'prisma', extraer: (m) => ({ mensaje: 'Prisma error '+m[1] }) },
50
+
51
+ // Laravel / PHP
52
+ { regex: /ErrorException: (.+)/i, tipo: 'php', extraer: (m) => ({ mensaje: m[1] }) },
53
+ { regex: /Illuminate\\(.+): (.+)/i, tipo: 'laravel', extraer: (m) => ({ clase: m[1], mensaje: m[2] }) },
54
+ { regex: /SQLSTATE\[(.+)\]: (.+)/i, tipo: 'database', extraer: (m) => ({ codigo: m[1], mensaje: m[2].slice(0,100) }) },
55
+
56
+ // Python / FastAPI
57
+ { regex: /pydantic.error_wrappers.ValidationError/i, tipo: 'validation', extraer: (m) => ({ mensaje: 'Pydantic validation error' }) },
58
+ { regex: /sqlalchemy.exc.(.+): (.+)/i, tipo: 'database', extraer: (m) => ({ clase: m[1], mensaje: m[2].slice(0,100) }) },
59
+
60
+ // Genérico
61
+ { regex: /\[ERROR\] (.+)/i, tipo: 'generic', extraer: (m) => ({ mensaje: m[1] }) },
62
+ { regex: /error: (.{10,120})/i, tipo: 'generic', extraer: (m) => ({ mensaje: m[1] }) },
63
+ ];
64
+
65
+ // ─── Detectar área del proyecto basada en el error ────────────────────────────
66
+ function detectarArea(linea) {
67
+ const lower = linea.toLowerCase();
68
+ if (lower.includes('auth') || lower.includes('login') || lower.includes('session')) return 'auth';
69
+ if (lower.includes('api') || lower.includes('route') || lower.includes('endpoint')) return 'api';
70
+ if (lower.includes('database') || lower.includes('sql') || lower.includes('prisma') || lower.includes('supabase')) return 'database';
71
+ if (lower.includes('component') || lower.includes('.tsx') || lower.includes('.jsx')) return 'frontend';
72
+ if (lower.includes('middleware')) return 'middleware';
73
+ if (lower.includes('payment') || lower.includes('pago') || lower.includes('stripe')) return 'payments';
74
+ if (lower.includes('user') || lower.includes('usuario')) return 'users';
75
+ return 'global';
76
+ }
77
+
78
+ // ─── Extraer archivo y línea del error ────────────────────────────────────────
79
+ function extraerUbicacion(lineas) {
80
+ for (const linea of lineas) {
81
+ const m = linea.match(/at .+?\((.+?):(\d+):\d+\)/) ||
82
+ linea.match(/→ (.+?):(\d+)/) ||
83
+ linea.match(/in (.+\.(?:ts|tsx|js|jsx|py|php)):(\d+)/);
84
+ if (m) return `${m[1]}:${m[2]}`;
85
+ }
86
+ return null;
87
+ }
88
+
89
+ // ─── Verificar si el error ya está en memoria ─────────────────────────────────
90
+ function yaExisteEnMemoria(titulo) {
91
+ if (!fs.existsSync(ERRORES_PATH)) return false;
92
+ const contenido = fs.readFileSync(ERRORES_PATH, 'utf8');
93
+ // Comparar por similaridad de título (primeras 40 chars)
94
+ const tituloShort = titulo.slice(0, 40).toLowerCase();
95
+ return contenido.toLowerCase().includes(tituloShort);
96
+ }
97
+
98
+ // ─── Registrar error en errores.md ────────────────────────────────────────────
99
+ function registrarError(errorInfo) {
100
+ const { tipo, mensaje, area, ubicacion, raw } = errorInfo;
101
+ const fecha = new Date().toISOString().split('T')[0];
102
+ const hora = new Date().toTimeString().split(' ')[0];
103
+
104
+ // Título limpio
105
+ const titulo = mensaje.slice(0, 60).replace(/[#\n\r]/g, '').trim();
106
+
107
+ if (yaExisteEnMemoria(titulo)) {
108
+ log(`⏭ Ya existe en memoria: ${titulo.slice(0, 40)}`);
109
+ return false;
110
+ }
111
+
112
+ const entrada = `
113
+ ## ${fecha} [${tipo.toUpperCase()}] ${titulo}
114
+ Área: ${area}
115
+ Confianza: BAJA
116
+ Aplicado: 0
117
+ Útil: 0
118
+ Estado: ACTIVO
119
+ Última validación: ${fecha}
120
+ Creado: ${fecha}
121
+ Origen: watch-errors — detectado ${hora}
122
+ Tipo: ${tipo}
123
+ Error: ${mensaje.slice(0, 200)}
124
+ ${ubicacion ? `Ubicación: ${ubicacion}` : ''}
125
+ Solución: [pendiente — cuando lo resuelvas corre: aa: aprende — error: ${titulo.slice(0, 40)}]
126
+ Raw: ${(raw||'').slice(0, 150).replace(/\n/g, ' ')}
127
+ `;
128
+
129
+ // Asegurar que el archivo existe
130
+ if (!fs.existsSync(ERRORES_PATH)) {
131
+ fs.mkdirSync(path.dirname(ERRORES_PATH), { recursive: true });
132
+ fs.writeFileSync(ERRORES_PATH, '# Errores — Agentic KDD\n\n', 'utf8');
133
+ }
134
+
135
+ fs.appendFileSync(ERRORES_PATH, entrada, 'utf8');
136
+ log(`✓ Error registrado: [${tipo}] ${titulo.slice(0, 50)}`);
137
+ return true;
138
+ }
139
+
140
+ // ─── Sincronizar grafo ─────────────────────────────────────────────────────────
141
+ function sincronizarGrafo() {
142
+ if (!fs.existsSync(GRAFO_PATH)) return;
143
+ try {
144
+ require('child_process').execSync(`node "${GRAFO_PATH}" sync`, {
145
+ stdio: 'pipe', cwd: ROOT, timeout: 10000
146
+ });
147
+ log('✓ Grafo sincronizado');
148
+ } catch(e) {
149
+ log('⚠ Sync fallido (continúa sin sincronizar)');
150
+ }
151
+ }
152
+
153
+ // ─── Log ──────────────────────────────────────────────────────────────────────
154
+ function log(msg) {
155
+ const ts = new Date().toTimeString().split(' ')[0];
156
+ const line = `[KDD ${ts}] ${msg}`;
157
+ // Mostrar en consola (pass-through)
158
+ process.stderr.write(line + '\n');
159
+ // Guardar en log file
160
+ try {
161
+ fs.mkdirSync(path.dirname(LOG_PATH), { recursive: true });
162
+ fs.appendFileSync(LOG_PATH, line + '\n');
163
+ } catch(e) {}
164
+ }
165
+
166
+ // ─── Procesador de líneas ─────────────────────────────────────────────────────
167
+ const buffer = [];
168
+ let erroresRegistrados = 0;
169
+ let syncPendiente = false;
170
+ let syncTimer = null;
171
+
172
+ function procesarLinea(linea) {
173
+ // Pass-through — mostrar la línea original siempre
174
+ process.stdout.write(linea + '\n');
175
+
176
+ // Guardar contexto (últimas 5 líneas)
177
+ buffer.push(linea);
178
+ if (buffer.length > 5) buffer.shift();
179
+
180
+ // Intentar cada patrón
181
+ for (const patron of ERROR_PATTERNS) {
182
+ const match = linea.match(patron.regex);
183
+ if (match) {
184
+ try {
185
+ const datos = patron.extraer(match);
186
+ const errorInfo = {
187
+ tipo: patron.tipo,
188
+ mensaje: datos.mensaje || datos.codigo || match[0].slice(0, 120),
189
+ area: detectarArea(linea + ' ' + buffer.join(' ')),
190
+ ubicacion: extraerUbicacion(buffer),
191
+ raw: buffer.join(' ')
192
+ };
193
+
194
+ if (registrarError(errorInfo)) {
195
+ erroresRegistrados++;
196
+ // Sincronizar grafo después de 3 segundos de inactividad
197
+ if (syncTimer) clearTimeout(syncTimer);
198
+ syncTimer = setTimeout(() => {
199
+ sincronizarGrafo();
200
+ syncTimer = null;
201
+ }, 3000);
202
+ }
203
+ } catch(e) {
204
+ // Nunca interrumpir el flujo por un error del watch
205
+ }
206
+ break; // Solo el primer patrón que coincida
207
+ }
208
+ }
209
+ }
210
+
211
+ // ─── MAIN ─────────────────────────────────────────────────────────────────────
212
+ log('Agentic KDD Watch — escuchando errores...');
213
+ log(`Registrando en: .agentic/memoria/errores.md`);
214
+ log('Ctrl+C para detener\n');
215
+
216
+ const rl = readline.createInterface({
217
+ input: process.stdin,
218
+ terminal: false
219
+ });
220
+
221
+ rl.on('line', procesarLinea);
222
+
223
+ rl.on('close', () => {
224
+ if (erroresRegistrados > 0) {
225
+ log(`\n✅ Sesión terminada — ${erroresRegistrados} errores registrados en memoria KDD`);
226
+ sincronizarGrafo();
227
+ } else {
228
+ log('\n✅ Sesión terminada — sin errores nuevos detectados');
229
+ }
230
+ });
231
+
232
+ process.on('SIGINT', () => {
233
+ if (erroresRegistrados > 0) {
234
+ log(`\n✅ Detenido — ${erroresRegistrados} errores registrados en memoria KDD`);
235
+ sincronizarGrafo();
236
+ }
237
+ process.exit(0);
238
+ });