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.
- package/bin/akdd.js +10 -5
- package/package.json +7 -3
- package/src/dashboard-template.cjs +1730 -0
- package/src/dashboard-template.js +1345 -0
- package/src/dashboard.js +57 -0
- package/src/graph.js +27 -36
- package/src/init.js +280 -125
- package/templates/.agentic/grafo/grafo.cjs +524 -0
- package/templates/.agentic/grafo/schema.sql +95 -0
- package/templates/.agentic/grafo/watch-errors.cjs +238 -0
|
@@ -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
|
+
});
|