agentic-kdd 2.1.7 → 2.1.8
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 +1 -1
- package/package.json +1 -1
- package/src/graph.js +5 -5
- package/src/init.js +14 -1
- package/templates/.agentic/grafo/grafo.cjs +448 -54
package/bin/akdd.js
CHANGED
package/package.json
CHANGED
package/src/graph.js
CHANGED
|
@@ -29,22 +29,22 @@ async function graph() {
|
|
|
29
29
|
// Sincronizar primero
|
|
30
30
|
try {
|
|
31
31
|
process.stdout.write(chalk.gray(' Sincronizando memoria... '));
|
|
32
|
-
execSync(`node "${grafo}" sync`, { stdio: 'pipe', cwd: projectPath });
|
|
32
|
+
execSync(`node "${grafo}" sync`, { stdio: ['pipe', 'pipe', 'pipe'], cwd: projectPath });
|
|
33
33
|
console.log(chalk.green('✓'));
|
|
34
34
|
} catch (e) {
|
|
35
|
-
console.log(chalk.yellow('⚠
|
|
35
|
+
console.log(chalk.yellow('⚠ ' + e.message.slice(0, 100)));
|
|
36
36
|
}
|
|
37
37
|
|
|
38
38
|
// Stats
|
|
39
39
|
if (fs.existsSync(dbPath)) {
|
|
40
40
|
try {
|
|
41
|
-
const output = execSync(`node "${grafo}" stats`, { stdio: 'pipe', cwd: projectPath }).toString();
|
|
41
|
+
const output = execSync(`node "${grafo}" stats`, { stdio: ['pipe', 'pipe', 'pipe'], cwd: projectPath }).toString();
|
|
42
42
|
console.log(output);
|
|
43
43
|
} catch (e) {
|
|
44
|
-
console.log(chalk.red(' Error: ' + e.
|
|
44
|
+
console.log(chalk.red(' Error stats: ' + e.stderr?.toString().slice(0, 200)));
|
|
45
45
|
}
|
|
46
46
|
} else {
|
|
47
|
-
console.log(chalk.
|
|
47
|
+
console.log(chalk.yellow(` DB no encontrada en: ${dbPath}\n`));
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
// Métricas si hay ciclos
|
package/src/init.js
CHANGED
|
@@ -201,7 +201,20 @@ async function init() {
|
|
|
201
201
|
spinner.text = 'Instalando archivos...';
|
|
202
202
|
copyAgenticFiles(sourcePath, projectPath);
|
|
203
203
|
fs.removeSync(TEMP_DIR);
|
|
204
|
-
|
|
204
|
+
|
|
205
|
+
// Instalar better-sqlite3 para el grafo SQLite
|
|
206
|
+
spinner.text = 'Instalando dependencias del grafo...';
|
|
207
|
+
try {
|
|
208
|
+
require('child_process').execSync('npm install better-sqlite3 --save', {
|
|
209
|
+
stdio: 'pipe', cwd: projectPath
|
|
210
|
+
});
|
|
211
|
+
spinner.succeed(chalk.green('Archivos instalados + better-sqlite3'));
|
|
212
|
+
} catch(e) {
|
|
213
|
+
spinner.warn(chalk.yellow('Archivos instalados (sin better-sqlite3)'));
|
|
214
|
+
console.log(chalk.gray('\n El grafo usará node:sqlite integrado en Node.js 22+'));
|
|
215
|
+
console.log(chalk.gray(' Para máximo rendimiento instala las build tools:'));
|
|
216
|
+
console.log(chalk.gray(' https://visualstudio.microsoft.com/visual-cpp-build-tools/\n'));
|
|
217
|
+
}
|
|
205
218
|
} catch (err) {
|
|
206
219
|
spinner.fail(chalk.red('Error en la instalación'));
|
|
207
220
|
console.error(chalk.red('\n ' + err.message + '\n'));
|
|
@@ -12,51 +12,72 @@ const crypto = require('crypto');
|
|
|
12
12
|
let dbAdapter = null; // 'better-sqlite3' | 'sqljs'
|
|
13
13
|
|
|
14
14
|
function getDB(dbPath) {
|
|
15
|
-
// Intentar better-sqlite3 primero
|
|
16
|
-
if (dbAdapter !== 'sqljs') {
|
|
15
|
+
// Intentar better-sqlite3 primero (nativo, rápido)
|
|
16
|
+
if (dbAdapter !== 'node-sqlite' && dbAdapter !== 'sqljs') {
|
|
17
17
|
try {
|
|
18
18
|
const BS3 = require('better-sqlite3');
|
|
19
19
|
const db = new BS3(dbPath);
|
|
20
|
-
db.pragma('journal_mode =
|
|
21
|
-
db.pragma('synchronous =
|
|
22
|
-
db.pragma('cache_size = -64000');
|
|
23
|
-
db.pragma('temp_store = MEMORY');
|
|
20
|
+
db.pragma('journal_mode = DELETE');
|
|
21
|
+
db.pragma('synchronous = FULL');
|
|
24
22
|
dbAdapter = 'better-sqlite3';
|
|
25
23
|
return { db, type: 'better-sqlite3' };
|
|
26
|
-
} catch(e) {
|
|
27
|
-
dbAdapter = 'sqljs';
|
|
28
|
-
}
|
|
24
|
+
} catch(e) {}
|
|
29
25
|
}
|
|
30
26
|
|
|
31
|
-
// Fallback:
|
|
32
|
-
|
|
33
|
-
const initSqlJs = require('sql.js');
|
|
34
|
-
// sql.js es async — usamos sync wrapper
|
|
35
|
-
const SQL = require('sql.js/dist/sql-wasm.js');
|
|
36
|
-
let buffer = null;
|
|
37
|
-
if (fs.existsSync(dbPath)) {
|
|
38
|
-
buffer = fs.readFileSync(dbPath);
|
|
39
|
-
}
|
|
40
|
-
const db = new SQL.Database(buffer);
|
|
41
|
-
dbAdapter = 'sqljs';
|
|
42
|
-
return { db, type: 'sqljs', path: dbPath };
|
|
43
|
-
} catch(e) {
|
|
44
|
-
// Si ninguno está disponible, instalar sql.js automáticamente
|
|
45
|
-
console.log(' Instalando dependencias del grafo...');
|
|
27
|
+
// Fallback 1: node:sqlite — integrado en Node.js 22+
|
|
28
|
+
if (dbAdapter !== 'sqljs') {
|
|
46
29
|
try {
|
|
47
|
-
require('
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
30
|
+
const { DatabaseSync } = require('node:sqlite');
|
|
31
|
+
const db = new DatabaseSync(dbPath);
|
|
32
|
+
dbAdapter = 'node-sqlite';
|
|
33
|
+
return { db, type: 'node-sqlite' };
|
|
34
|
+
} catch(e) {}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Fallback 2: sql.js — puro JS, cualquier Node version
|
|
38
|
+
const projectRoot = path.join(__dirname, '..', '..');
|
|
39
|
+
const searchPaths = [
|
|
40
|
+
path.join(projectRoot, 'node_modules', 'sql.js', 'dist', 'sql-wasm.js'),
|
|
41
|
+
path.join(__dirname, 'node_modules', 'sql.js', 'dist', 'sql-wasm.js'),
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
for (const sqlPath of searchPaths) {
|
|
45
|
+
if (fs.existsSync(sqlPath)) {
|
|
46
|
+
try {
|
|
47
|
+
// sql.js sync workaround usando Worker
|
|
48
|
+
const SQL = require(sqlPath);
|
|
49
|
+
let DbClass = null;
|
|
50
|
+
if (SQL && SQL.Database) DbClass = SQL.Database;
|
|
51
|
+
else if (typeof SQL === 'function') {
|
|
52
|
+
// Intentar llamar sync
|
|
53
|
+
let resolved = null;
|
|
54
|
+
SQL({}).then(s => { resolved = s; }).catch(() => {});
|
|
55
|
+
// Esperar máximo 3 segundos
|
|
56
|
+
const start = Date.now();
|
|
57
|
+
while (!resolved && Date.now() - start < 3000) {
|
|
58
|
+
require('child_process').spawnSync('node', ['-e', ''], { timeout: 10 });
|
|
59
|
+
}
|
|
60
|
+
if (resolved && resolved.Database) DbClass = resolved.Database;
|
|
61
|
+
}
|
|
62
|
+
if (DbClass) {
|
|
63
|
+
let buffer = null;
|
|
64
|
+
if (fs.existsSync(dbPath)) buffer = fs.readFileSync(dbPath);
|
|
65
|
+
const db = buffer ? new DbClass(buffer) : new DbClass();
|
|
66
|
+
dbAdapter = 'sqljs';
|
|
67
|
+
return { db, type: 'sqljs', path: dbPath };
|
|
68
|
+
}
|
|
69
|
+
} catch(e) {}
|
|
58
70
|
}
|
|
59
71
|
}
|
|
72
|
+
|
|
73
|
+
throw new Error(
|
|
74
|
+
'No se pudo inicializar el grafo SQLite.\n' +
|
|
75
|
+
' Tu versión de Node.js: ' + process.version + '\n' +
|
|
76
|
+
' Opciones:\n' +
|
|
77
|
+
' 1. Usa Node.js 22+ (ya incluye SQLite integrado)\n' +
|
|
78
|
+
' 2. Corre: npm install sql.js\n' +
|
|
79
|
+
' 3. Instala Visual Studio Build Tools para better-sqlite3'
|
|
80
|
+
);
|
|
60
81
|
}
|
|
61
82
|
|
|
62
83
|
// Wrapper unificado que abstrae las diferencias entre better-sqlite3 y sql.js
|
|
@@ -64,19 +85,31 @@ function createAdapter(dbPath) {
|
|
|
64
85
|
const { db, type, path: sqlPath } = getDB(dbPath);
|
|
65
86
|
|
|
66
87
|
if (type === 'better-sqlite3') {
|
|
67
|
-
// API nativa — synchronous, más rápida
|
|
68
88
|
return {
|
|
69
|
-
exec: (sql) => db.exec(sql),
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
run: (sql, ...params) => db.prepare(sql).run(...params),
|
|
89
|
+
exec: (sql) => { try { db.exec(sql); } catch(e) {} },
|
|
90
|
+
all: (sql, ...params) => { try { return db.prepare(sql).all(...params.flat()); } catch(e) { return []; } },
|
|
91
|
+
get: (sql, ...params) => { try { return db.prepare(sql).get(...params.flat()); } catch(e) { return null; } },
|
|
92
|
+
run: (sql, ...params) => { try { db.prepare(sql).run(...params.flat()); } catch(e) {} },
|
|
74
93
|
transaction: (fn) => db.transaction(fn),
|
|
75
|
-
pragma: (p) => db.pragma(p),
|
|
76
|
-
close: () => db.close(),
|
|
94
|
+
pragma: (p) => { try { db.pragma(p); } catch(e) {} },
|
|
95
|
+
close: () => { try { db.close(); } catch(e) {} },
|
|
77
96
|
type: 'better-sqlite3'
|
|
78
97
|
};
|
|
79
|
-
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (type === 'node-sqlite') {
|
|
101
|
+
// node:sqlite API — similar a better-sqlite3
|
|
102
|
+
return {
|
|
103
|
+
exec: (sql) => { try { db.exec(sql); } catch(e) {} },
|
|
104
|
+
all: (sql, ...params) => { try { return db.prepare(sql).all(...params.flat()); } catch(e) { return []; } },
|
|
105
|
+
get: (sql, ...params) => { try { return db.prepare(sql).get(...params.flat()); } catch(e) { return null; } },
|
|
106
|
+
run: (sql, ...params) => { try { db.prepare(sql).run(...params.flat()); } catch(e) {} },
|
|
107
|
+
transaction: (fn) => (...args) => { try { fn(...args); } catch(e) {} },
|
|
108
|
+
pragma: () => {},
|
|
109
|
+
close: () => { try { db.close(); } catch(e) {} },
|
|
110
|
+
type: 'node-sqlite'
|
|
111
|
+
};
|
|
112
|
+
}
|
|
80
113
|
// sql.js API — puro JS, necesita guardar el archivo manualmente
|
|
81
114
|
const saveDB = () => {
|
|
82
115
|
try {
|
|
@@ -124,7 +157,6 @@ function createAdapter(dbPath) {
|
|
|
124
157
|
type: 'sqljs',
|
|
125
158
|
save: saveDB
|
|
126
159
|
};
|
|
127
|
-
}
|
|
128
160
|
}
|
|
129
161
|
|
|
130
162
|
const ROOT = path.join(__dirname, '..', '..');
|
|
@@ -135,11 +167,20 @@ const MEMORIA_PATH= path.join(ROOT, '.agentic', 'memoria');
|
|
|
135
167
|
// ─── INIT ──────────────────────────────────────────────────────────────────────
|
|
136
168
|
function initDB() {
|
|
137
169
|
const adapter = createAdapter(DB_PATH);
|
|
138
|
-
|
|
139
|
-
//
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
170
|
+
// Usar exec directo — más confiable que split por ;
|
|
171
|
+
// porque el schema tiene comentarios -- inline dentro de CREATE TABLE
|
|
172
|
+
try {
|
|
173
|
+
const schema = fs.readFileSync(SCHEMA_PATH, 'utf8');
|
|
174
|
+
adapter.exec(schema);
|
|
175
|
+
} catch(e) {
|
|
176
|
+
// Si falla el schema completo, intentar statement por statement
|
|
177
|
+
const schema = fs.readFileSync(SCHEMA_PATH, 'utf8');
|
|
178
|
+
// Remover comentarios inline antes de hacer split
|
|
179
|
+
const clean = schema.replace(/--[^\n]*/g, '').replace(/\n\s*\n/g, '\n');
|
|
180
|
+
clean.split(';').map(s => s.trim()).filter(s => s.length > 5).forEach(s => {
|
|
181
|
+
try { adapter.exec(s + ';'); } catch(e) {}
|
|
182
|
+
});
|
|
183
|
+
}
|
|
143
184
|
migrateDB(adapter);
|
|
144
185
|
return adapter;
|
|
145
186
|
}
|
|
@@ -253,9 +294,11 @@ function sincronizar() {
|
|
|
253
294
|
}
|
|
254
295
|
if (db.type === 'sqljs' && db.save) db.save();
|
|
255
296
|
detectarRelaciones(db);
|
|
297
|
+
// Forzar checkpoint WAL para que los datos queden en la DB principal
|
|
298
|
+
try { db.exec('PRAGMA wal_checkpoint(TRUNCATE)'); } catch(e) {}
|
|
256
299
|
db.close();
|
|
257
300
|
console.log(`\n Grafo sincronizado — ${total} nodos (${nuevos} nuevos, ${actualizados} actualizados)`);
|
|
258
|
-
console.log(` Motor: ${dbAdapter === 'better-sqlite3' ? 'nativo (<5ms)' : 'sql.js (<20ms)'}\n`);
|
|
301
|
+
console.log(` Motor: ${dbAdapter === 'better-sqlite3' ? 'nativo (<5ms)' : dbAdapter === 'node-sqlite' ? 'node:sqlite (Node.js 22+)' : 'sql.js (<20ms)'}\n`);
|
|
259
302
|
}
|
|
260
303
|
|
|
261
304
|
// ─── RELACIONES ────────────────────────────────────────────────────────────────
|
|
@@ -311,7 +354,7 @@ function stats() {
|
|
|
311
354
|
db.close();
|
|
312
355
|
|
|
313
356
|
console.log('\n GRAFO DE CONOCIMIENTO — Agentic KDD\n');
|
|
314
|
-
console.log(` Motor: ${dbAdapter === 'better-sqlite3' ? 'better-sqlite3 nativo' : 'sql.js (compatible Windows)'}`);
|
|
357
|
+
console.log(` Motor: ${dbAdapter === 'better-sqlite3' ? 'better-sqlite3 nativo' : dbAdapter === 'node-sqlite' ? 'node:sqlite (Node.js 22+)' : 'sql.js (compatible Windows)'}`);
|
|
315
358
|
console.log(` Total nodos: ${totalNodos} | Relaciones: ${totalRels}`);
|
|
316
359
|
if (porTipo.length) { console.log('\n Por tipo:'); porTipo.forEach(r => console.log(` ${r.tipo}: ${r.n}`)); }
|
|
317
360
|
if (porConf.length) { console.log('\n Por confianza:'); porConf.forEach(r => console.log(` ${r.confianza}: ${r.n}`)); }
|
|
@@ -500,6 +543,10 @@ const arg2 = process.argv[4];
|
|
|
500
543
|
|
|
501
544
|
switch(cmd) {
|
|
502
545
|
case 'sync': sincronizar(); break;
|
|
546
|
+
case 'sync-stats':
|
|
547
|
+
sincronizar();
|
|
548
|
+
stats();
|
|
549
|
+
break;
|
|
503
550
|
case 'query':
|
|
504
551
|
const {resultados,trace} = consultar(arg1,arg2);
|
|
505
552
|
console.log(JSON.stringify({resultados,trace},null,2)); break;
|
|
@@ -517,8 +564,355 @@ switch(cmd) {
|
|
|
517
564
|
break;
|
|
518
565
|
case 'snapshot':
|
|
519
566
|
const db2=initDB(); console.log(JSON.stringify(snapshotMemoria(db2),null,2)); db2.close(); break;
|
|
567
|
+
case 'analizar':
|
|
568
|
+
analizarProyecto(); break;
|
|
520
569
|
default:
|
|
521
|
-
console.log('Uso: node grafo.cjs [sync|query|stats|metricas|ciclo|semantico|snapshot]');
|
|
570
|
+
console.log('Uso: node grafo.cjs [sync|query|stats|metricas|ciclo|semantico|snapshot|analizar]');
|
|
571
|
+
}
|
|
572
|
+
|
|
573
|
+
|
|
574
|
+
// ─── ANÁLISIS AUTOMÁTICO DEL PROYECTO ────────────────────────────────────────
|
|
575
|
+
// Recorre el código real y construye el grafo sin esperar ciclos aa:
|
|
576
|
+
// Similar al GRAPH_REPORT.md de Graphify
|
|
577
|
+
|
|
578
|
+
function analizarProyecto() {
|
|
579
|
+
console.log('\n Analizando proyecto...\n');
|
|
580
|
+
|
|
581
|
+
const db = initDB();
|
|
582
|
+
const ignorar = new Set(['node_modules','.git','.next','dist','build','vendor',
|
|
583
|
+
'coverage','.cache','tmp','temp','.turbo','out','.output','public','static',
|
|
584
|
+
'__pycache__','.pytest_cache','.mypy_cache']);
|
|
585
|
+
const extsCodigo = new Set(['.ts','.tsx','.js','.jsx','.php','.py','.vue','.svelte','.rb','.go','.java']);
|
|
586
|
+
const extsDocs = new Set(['.md','.txt','.pdf']);
|
|
587
|
+
|
|
588
|
+
let archivosAnalizados = 0, nodosPrevios = 0, nodosCreados = 0;
|
|
589
|
+
|
|
590
|
+
// Contar nodos previos
|
|
591
|
+
try { nodosPrevios = (db.get('SELECT COUNT(*) as n FROM nodos') || {}).n || 0; } catch(e) {}
|
|
592
|
+
|
|
593
|
+
// ── 1. DETECTAR MÓDULOS desde estructura de carpetas ───────────────────────
|
|
594
|
+
function detectarModulos(dir, nivel) {
|
|
595
|
+
if (nivel > 4) return [];
|
|
596
|
+
let items; try { items = fs.readdirSync(dir); } catch(e) { return []; }
|
|
597
|
+
const modulos = [];
|
|
598
|
+
|
|
599
|
+
for (const item of items) {
|
|
600
|
+
if (ignorar.has(item) || item.startsWith('.') || item.startsWith('_')) continue;
|
|
601
|
+
const full = path.join(dir, item);
|
|
602
|
+
let stat; try { stat = fs.statSync(full); } catch(e) { continue; }
|
|
603
|
+
|
|
604
|
+
if (stat.isDirectory()) {
|
|
605
|
+
// Es módulo si tiene archivos de código dentro
|
|
606
|
+
let tieneCode = false;
|
|
607
|
+
try {
|
|
608
|
+
const sub = fs.readdirSync(full);
|
|
609
|
+
tieneCode = sub.some(f => extsCodigo.has(path.extname(f).toLowerCase()));
|
|
610
|
+
} catch(e) {}
|
|
611
|
+
|
|
612
|
+
if (tieneCode) {
|
|
613
|
+
modulos.push({ nombre: item, ruta: full, nivel });
|
|
614
|
+
}
|
|
615
|
+
modulos.push(...detectarModulos(full, nivel + 1));
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return modulos;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
// ── 2. ANALIZAR ARCHIVOS de código ─────────────────────────────────────────
|
|
622
|
+
function analizarArchivo(filePath) {
|
|
623
|
+
let content; try { content = fs.readFileSync(filePath, 'utf8'); } catch(e) { return null; }
|
|
624
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
625
|
+
const nombre = path.basename(filePath);
|
|
626
|
+
const info = { imports: [], exports: [], patrones: [], decisiones: [] };
|
|
627
|
+
|
|
628
|
+
// Detectar imports/dependencias
|
|
629
|
+
const importRegexes = [
|
|
630
|
+
/import\s+.*?\s+from\s+['"]([^'"]+)['"]/g, // ES6 import
|
|
631
|
+
/require\(['"]([^'"]+)['"]\)/g, // CommonJS require
|
|
632
|
+
/use\s+([A-Z][a-zA-Z]+)/g, // PHP use
|
|
633
|
+
/import\s+([a-zA-Z.]+)/g, // Python import
|
|
634
|
+
];
|
|
635
|
+
importRegexes.forEach(rx => {
|
|
636
|
+
let m; while ((m = rx.exec(content)) !== null) {
|
|
637
|
+
if (!m[1].startsWith('.') && !m[1].startsWith('@')) info.imports.push(m[1]);
|
|
638
|
+
}
|
|
639
|
+
});
|
|
640
|
+
|
|
641
|
+
// Detectar patrones de naming
|
|
642
|
+
if (/export\s+default\s+function\s+([A-Z][a-zA-Z]+)/.test(content)) info.patrones.push('componente React');
|
|
643
|
+
if (/\bconst\s+\w+\s*=\s*async\s*\(/.test(content)) info.patrones.push('funciones async/await');
|
|
644
|
+
if (/\.env\b/.test(content) || /process\.env/.test(content)) info.patrones.push('variables de entorno');
|
|
645
|
+
if (/createClient|supabase/.test(content)) info.patrones.push('Supabase client');
|
|
646
|
+
if (/prisma\.|PrismaClient/.test(content)) info.patrones.push('Prisma ORM');
|
|
647
|
+
if (/SELECT|INSERT|UPDATE|DELETE/i.test(content)) info.patrones.push('queries SQL directas');
|
|
648
|
+
if (/middleware/.test(nombre.toLowerCase())) info.patrones.push('middleware pattern');
|
|
649
|
+
if (/\.test\.|\.spec\./.test(nombre)) info.patrones.push('archivo de test');
|
|
650
|
+
if (/interface\s+[A-Z]|type\s+[A-Z]/.test(content)) info.patrones.push('TypeScript types/interfaces');
|
|
651
|
+
if (/zod\.|yup\.|joi\./.test(content)) info.patrones.push('validación de schemas');
|
|
652
|
+
if (/useQuery|useMutation|useEffect/.test(content)) info.patrones.push('React hooks');
|
|
653
|
+
if (/@Injectable|@Controller|@Module/.test(content)) info.patrones.push('NestJS decorators');
|
|
654
|
+
if (/artisan|Eloquent|->where/.test(content)) info.patrones.push('Laravel Eloquent');
|
|
655
|
+
|
|
656
|
+
// Detectar decisiones arquitectónicas implícitas
|
|
657
|
+
if (/lib\/|utils\/|helpers\//.test(content)) info.decisiones.push('utilidades centralizadas');
|
|
658
|
+
if (/only.*server|server.*only/i.test(content) || /use server/.test(content)) info.decisiones.push('Server Components (Next.js)');
|
|
659
|
+
if (/use client/.test(content)) info.decisiones.push('Client Components (Next.js)');
|
|
660
|
+
|
|
661
|
+
archivosAnalizados++;
|
|
662
|
+
return info;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// ── 3. INFERIR ÁREA desde la ruta del archivo ──────────────────────────────
|
|
666
|
+
function inferirArea(ruta) {
|
|
667
|
+
const parts = ruta.toLowerCase().split(path.sep);
|
|
668
|
+
const areaMap = {
|
|
669
|
+
'auth': 'auth', 'authentication': 'auth', 'login': 'auth',
|
|
670
|
+
'api': 'api', 'routes': 'api', 'endpoints': 'api', 'controllers': 'api',
|
|
671
|
+
'components': 'frontend', 'ui': 'frontend', 'pages': 'frontend', 'views': 'frontend',
|
|
672
|
+
'lib': 'core', 'utils': 'core', 'helpers': 'core', 'shared': 'core',
|
|
673
|
+
'database': 'database', 'db': 'database', 'models': 'database', 'migrations': 'database',
|
|
674
|
+
'middleware': 'middleware', 'hooks': 'frontend', 'services': 'services',
|
|
675
|
+
'payment': 'payments', 'stripe': 'payments', 'billing': 'payments',
|
|
676
|
+
'email': 'notifications', 'notifications': 'notifications', 'mailer': 'notifications',
|
|
677
|
+
'tests': 'testing', 'test': 'testing', 'spec': 'testing',
|
|
678
|
+
};
|
|
679
|
+
for (const part of parts) {
|
|
680
|
+
if (areaMap[part]) return areaMap[part];
|
|
681
|
+
}
|
|
682
|
+
return 'global';
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// ── 4. RECORRER EL PROYECTO ────────────────────────────────────────────────
|
|
686
|
+
const patronesEncontrados = {}; // patrón → { count, areas }
|
|
687
|
+
const decisionesEncontradas = {};
|
|
688
|
+
const areasConCodigo = new Set();
|
|
689
|
+
|
|
690
|
+
function recorrer(dir, nivel) {
|
|
691
|
+
if (nivel > 5) return;
|
|
692
|
+
let items; try { items = fs.readdirSync(dir); } catch(e) { return; }
|
|
693
|
+
|
|
694
|
+
for (const item of items) {
|
|
695
|
+
if (ignorar.has(item) || item.startsWith('.')) continue;
|
|
696
|
+
const full = path.join(dir, item);
|
|
697
|
+
let stat; try { stat = fs.statSync(full); } catch(e) { continue; }
|
|
698
|
+
|
|
699
|
+
if (stat.isDirectory()) {
|
|
700
|
+
recorrer(full, nivel + 1);
|
|
701
|
+
} else if (stat.isFile()) {
|
|
702
|
+
const ext = path.extname(item).toLowerCase();
|
|
703
|
+
if (!extsCodigo.has(ext)) continue;
|
|
704
|
+
|
|
705
|
+
const info = analizarArchivo(full);
|
|
706
|
+
if (!info) continue;
|
|
707
|
+
|
|
708
|
+
const area = inferirArea(full);
|
|
709
|
+
areasConCodigo.add(area);
|
|
710
|
+
|
|
711
|
+
info.patrones.forEach(p => {
|
|
712
|
+
if (!patronesEncontrados[p]) patronesEncontrados[p] = { count: 0, areas: new Set() };
|
|
713
|
+
patronesEncontrados[p].count++;
|
|
714
|
+
patronesEncontrados[p].areas.add(area);
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
info.decisiones.forEach(d => {
|
|
718
|
+
if (!decisionesEncontradas[d]) decisionesEncontradas[d] = { count: 0, areas: new Set() };
|
|
719
|
+
decisionesEncontradas[d].count++;
|
|
720
|
+
decisionesEncontradas[d].areas.add(area);
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
recorrer(ROOT, 0);
|
|
727
|
+
|
|
728
|
+
// ── 5. DETECTAR STACK desde package.json / composer.json ──────────────────
|
|
729
|
+
const stackInfo = {};
|
|
730
|
+
const pkgPath = path.join(ROOT, 'package.json');
|
|
731
|
+
if (fs.existsSync(pkgPath)) {
|
|
732
|
+
try {
|
|
733
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
734
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
735
|
+
if (deps['next']) { stackInfo['Next.js'] = Object.keys(deps['next']||{}).length > 0; }
|
|
736
|
+
if (deps['react']) stackInfo['React'] = true;
|
|
737
|
+
if (deps['@supabase/supabase-js']) stackInfo['Supabase'] = true;
|
|
738
|
+
if (deps['prisma'] || deps['@prisma/client']) stackInfo['Prisma'] = true;
|
|
739
|
+
if (deps['express']) stackInfo['Express'] = true;
|
|
740
|
+
if (deps['typescript']) stackInfo['TypeScript'] = true;
|
|
741
|
+
if (deps['tailwindcss']) stackInfo['Tailwind CSS'] = true;
|
|
742
|
+
if (deps['zustand']) stackInfo['Zustand'] = true;
|
|
743
|
+
if (deps['zod']) stackInfo['Zod'] = true;
|
|
744
|
+
if (deps['stripe']) stackInfo['Stripe'] = true;
|
|
745
|
+
if (deps['resend'] || deps['nodemailer']) stackInfo['Email service'] = true;
|
|
746
|
+
} catch(e) {}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// ── 6. GUARDAR EN MEMORIA ──────────────────────────────────────────────────
|
|
750
|
+
const fecha = new Date().toISOString().split('T')[0];
|
|
751
|
+
|
|
752
|
+
// Guardar patrones detectados (solo los que aparecen 2+ veces)
|
|
753
|
+
Object.entries(patronesEncontrados).forEach(([patron, data]) => {
|
|
754
|
+
if (data.count < 1) return;
|
|
755
|
+
const area = data.areas.size === 1 ? [...data.areas][0] : 'global';
|
|
756
|
+
const confianza = data.count >= 5 ? 'MEDIA' : 'BAJA';
|
|
757
|
+
const titulo = `[AUTO] ${patron}`;
|
|
758
|
+
const contenido = `## ${fecha} [AUTO] ${patron}
|
|
759
|
+
Área: ${area}
|
|
760
|
+
Confianza: ${confianza}
|
|
761
|
+
Aplicado: ${data.count}
|
|
762
|
+
Útil: 0
|
|
763
|
+
Estado: ACTIVO
|
|
764
|
+
Última validación: ${fecha}
|
|
765
|
+
Creado: ${fecha}
|
|
766
|
+
Origen: akdd analyze — detectado en ${data.count} archivos
|
|
767
|
+
Regla: Patrón usado consistentemente en el proyecto
|
|
768
|
+
Áreas: ${[...data.areas].join(', ')}`;
|
|
769
|
+
|
|
770
|
+
try {
|
|
771
|
+
const ex = db.get('SELECT id FROM nodos WHERE tipo=? AND titulo=?', 'patron', titulo);
|
|
772
|
+
if (!ex) {
|
|
773
|
+
db.run("INSERT INTO nodos (tipo,titulo,contenido,area,confianza,aplicado,util,estado,ultima_validacion,fecha_update) VALUES (?,?,?,?,?,?,?,?,?,datetime('now'))",
|
|
774
|
+
'patron', titulo, contenido, area, confianza, data.count, 0, 'ACTIVO', fecha);
|
|
775
|
+
nodosCreados++;
|
|
776
|
+
}
|
|
777
|
+
} catch(e) {}
|
|
778
|
+
});
|
|
779
|
+
|
|
780
|
+
// Guardar decisiones detectadas
|
|
781
|
+
Object.entries(decisionesEncontradas).forEach(([decision, data]) => {
|
|
782
|
+
if (data.count < 1) return;
|
|
783
|
+
const area = data.areas.size === 1 ? [...data.areas][0] : 'global';
|
|
784
|
+
const titulo = `[AUTO] ${decision}`;
|
|
785
|
+
const contenido = `## ${fecha} [AUTO] ${decision}
|
|
786
|
+
Área: ${area}
|
|
787
|
+
Confianza: BAJA
|
|
788
|
+
Estado: ACTIVO
|
|
789
|
+
Última validación: ${fecha}
|
|
790
|
+
Creado: ${fecha}
|
|
791
|
+
Origen: akdd analyze — inferido del código (${data.count} referencias)
|
|
792
|
+
Decisión: ${decision}
|
|
793
|
+
Razón: Detectado automáticamente — verificar con el equipo
|
|
794
|
+
Áreas: ${[...data.areas].join(', ')}`;
|
|
795
|
+
|
|
796
|
+
try {
|
|
797
|
+
const ex = db.get('SELECT id FROM nodos WHERE tipo=? AND titulo=?', 'decision', titulo);
|
|
798
|
+
if (!ex) {
|
|
799
|
+
db.run("INSERT INTO nodos (tipo,titulo,contenido,area,confianza,aplicado,util,estado,ultima_validacion,fecha_update) VALUES (?,?,?,?,?,?,?,?,?,datetime('now'))",
|
|
800
|
+
'decision', titulo, contenido, area, 'BAJA', 0, 0, 'ACTIVO', fecha);
|
|
801
|
+
nodosCreados++;
|
|
802
|
+
}
|
|
803
|
+
} catch(e) {}
|
|
804
|
+
});
|
|
805
|
+
|
|
806
|
+
// Guardar stack como decisiones
|
|
807
|
+
Object.keys(stackInfo).forEach(tech => {
|
|
808
|
+
const titulo = `[AUTO] Stack: ${tech}`;
|
|
809
|
+
const contenido = `## ${fecha} [AUTO] Stack: ${tech}
|
|
810
|
+
Área: global
|
|
811
|
+
Confianza: MEDIA
|
|
812
|
+
Estado: ACTIVO
|
|
813
|
+
Última validación: ${fecha}
|
|
814
|
+
Creado: ${fecha}
|
|
815
|
+
Origen: akdd analyze — detectado en package.json
|
|
816
|
+
Decisión: El proyecto usa ${tech}
|
|
817
|
+
Razón: Dependencia confirmada en package.json`;
|
|
818
|
+
|
|
819
|
+
try {
|
|
820
|
+
const ex = db.get('SELECT id FROM nodos WHERE tipo=? AND titulo=?', 'decision', titulo);
|
|
821
|
+
if (!ex) {
|
|
822
|
+
db.run("INSERT INTO nodos (tipo,titulo,contenido,area,confianza,aplicado,util,estado,ultima_validacion,fecha_update) VALUES (?,?,?,?,?,?,?,?,?,datetime('now'))",
|
|
823
|
+
'decision', titulo, contenido, 'global', 'MEDIA', 0, 0, 'ACTIVO', fecha);
|
|
824
|
+
nodosCreados++;
|
|
825
|
+
}
|
|
826
|
+
} catch(e) {}
|
|
827
|
+
});
|
|
828
|
+
|
|
829
|
+
// Detectar relaciones entre nodos nuevos
|
|
830
|
+
detectarRelaciones(db);
|
|
831
|
+
if (db.type === 'sqljs' && db.save) db.save();
|
|
832
|
+
|
|
833
|
+
// Actualizar archivos .md de memoria con lo detectado
|
|
834
|
+
actualizarMemoriaMd(patronesEncontrados, decisionesEncontradas, stackInfo, fecha);
|
|
835
|
+
|
|
836
|
+
const totalNodos = (db.get('SELECT COUNT(*) as n FROM nodos') || {}).n || 0;
|
|
837
|
+
db.close();
|
|
838
|
+
|
|
839
|
+
// ── OUTPUT ─────────────────────────────────────────────────────────────────
|
|
840
|
+
console.log(` Archivos analizados: ${archivosAnalizados}`);
|
|
841
|
+
console.log(` Areas detectadas: ${[...areasConCodigo].join(', ') || 'ninguna'}`);
|
|
842
|
+
console.log(`\n Stack detectado:`);
|
|
843
|
+
Object.keys(stackInfo).forEach(t => console.log(` ✓ ${t}`));
|
|
844
|
+
console.log(`\n Patrones encontrados: ${Object.keys(patronesEncontrados).length}`);
|
|
845
|
+
Object.entries(patronesEncontrados)
|
|
846
|
+
.sort((a,b) => b[1].count - a[1].count)
|
|
847
|
+
.slice(0,8)
|
|
848
|
+
.forEach(([p,d]) => console.log(` [${d.count}x] ${p}`));
|
|
849
|
+
console.log(`\n Decisiones inferidas: ${Object.keys(decisionesEncontradas).length}`);
|
|
850
|
+
Object.keys(decisionesEncontradas).forEach(d => console.log(` ~ ${d}`));
|
|
851
|
+
console.log(`\n Nodos nuevos en grafo: ${nodosCreados} (total: ${totalNodos})`);
|
|
852
|
+
console.log(`\n Dashboard actualizado — corre: node dashboard.cjs\n`);
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
// ── Actualizar archivos .md de memoria con lo detectado ────────────────────
|
|
856
|
+
function actualizarMemoriaMd(patrones, decisiones, stack, fecha) {
|
|
857
|
+
try {
|
|
858
|
+
// Actualizar patrones.md
|
|
859
|
+
const patronesPath = path.join(MEMORIA_PATH, 'patrones.md');
|
|
860
|
+
let patronesContent = fs.existsSync(patronesPath) ? fs.readFileSync(patronesPath, 'utf8') : '# Patrones — Agentic KDD\n\n';
|
|
861
|
+
|
|
862
|
+
Object.entries(patrones)
|
|
863
|
+
.filter(([p]) => !patronesContent.includes(`[AUTO] ${p}`))
|
|
864
|
+
.sort((a,b) => b[1].count - a[1].count)
|
|
865
|
+
.slice(0, 10)
|
|
866
|
+
.forEach(([patron, data]) => {
|
|
867
|
+
const area = data.areas.size === 1 ? [...data.areas][0] : 'global';
|
|
868
|
+
patronesContent += `\n## ${fecha} [AUTO] ${patron}
|
|
869
|
+
Área: ${area}
|
|
870
|
+
Confianza: ${data.count >= 5 ? 'MEDIA' : 'BAJA'}
|
|
871
|
+
Aplicado: ${data.count}
|
|
872
|
+
Útil: 0
|
|
873
|
+
Estado: ACTIVO
|
|
874
|
+
Última validación: ${fecha}
|
|
875
|
+
Creado: ${fecha}
|
|
876
|
+
Origen: akdd analyze
|
|
877
|
+
Regla: Patrón detectado automáticamente en ${data.count} archivos\n`;
|
|
878
|
+
});
|
|
879
|
+
fs.writeFileSync(patronesPath, patronesContent);
|
|
880
|
+
|
|
881
|
+
// Actualizar decisiones.md
|
|
882
|
+
const decisionesPath = path.join(MEMORIA_PATH, 'decisiones.md');
|
|
883
|
+
let decisionesContent = fs.existsSync(decisionesPath) ? fs.readFileSync(decisionesPath, 'utf8') : '# Decisiones — Agentic KDD\n\n';
|
|
884
|
+
|
|
885
|
+
// Stack como decisiones
|
|
886
|
+
Object.keys(stack)
|
|
887
|
+
.filter(t => !decisionesContent.includes(`Stack: ${t}`))
|
|
888
|
+
.forEach(tech => {
|
|
889
|
+
decisionesContent += `\n## ${fecha} [AUTO] Stack: ${tech}
|
|
890
|
+
Área: global
|
|
891
|
+
Confianza: MEDIA
|
|
892
|
+
Estado: ACTIVO
|
|
893
|
+
Última validación: ${fecha}
|
|
894
|
+
Creado: ${fecha}
|
|
895
|
+
Origen: akdd analyze
|
|
896
|
+
Decisión: El proyecto usa ${tech}
|
|
897
|
+
Razón: Dependencia confirmada en package.json\n`;
|
|
898
|
+
});
|
|
899
|
+
|
|
900
|
+
Object.entries(decisiones)
|
|
901
|
+
.filter(([d]) => !decisionesContent.includes(`[AUTO] ${d}`))
|
|
902
|
+
.forEach(([decision, data]) => {
|
|
903
|
+
const area = data.areas.size === 1 ? [...data.areas][0] : 'global';
|
|
904
|
+
decisionesContent += `\n## ${fecha} [AUTO] ${decision}
|
|
905
|
+
Área: ${area}
|
|
906
|
+
Confianza: BAJA
|
|
907
|
+
Estado: ACTIVO
|
|
908
|
+
Última validación: ${fecha}
|
|
909
|
+
Creado: ${fecha}
|
|
910
|
+
Origen: akdd analyze
|
|
911
|
+
Decisión: ${decision}
|
|
912
|
+
Razón: Inferido del código — verificar con el equipo\n`;
|
|
913
|
+
});
|
|
914
|
+
fs.writeFileSync(decisionesPath, decisionesContent);
|
|
915
|
+
} catch(e) {}
|
|
522
916
|
}
|
|
523
917
|
|
|
524
|
-
module.exports = { sincronizar, consultar, stats, metricas, registrarCiclo, buscarSemantico, snapshotMemoria };
|
|
918
|
+
module.exports = { sincronizar, consultar, stats, metricas, registrarCiclo, buscarSemantico, snapshotMemoria, analizarProyecto };
|