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 CHANGED
@@ -94,4 +94,4 @@ switch (command) {
94
94
  console.log(`\n Unknown command: ${command}`);
95
95
  console.log(' Run akdd --help for usage\n');
96
96
  process.exit(1);
97
- }
97
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-kdd",
3
- "version": "2.1.7",
3
+ "version": "2.1.8",
4
4
  "description": "Autonomous development pipeline with KDD — aa: · ag: · audit: · Visual Dashboard. Works with Cursor and Claude Code.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
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('⚠ No se pudo sincronizar'));
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.message));
44
+ console.log(chalk.red(' Error stats: ' + e.stderr?.toString().slice(0, 200)));
45
45
  }
46
46
  } else {
47
- console.log(chalk.gray(' Sin datos aún — usa aa: para empezar\n'));
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
- spinner.succeed(chalk.green('Archivos instalados'));
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 = WAL');
21
- db.pragma('synchronous = NORMAL');
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: sql.js (puro JavaScript, sin compilación, funciona en cualquier Windows)
32
- try {
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('child_process').execSync('npm install sql.js --save', {
48
- stdio: 'pipe', cwd: path.join(__dirname, '..', '..')
49
- });
50
- const SQL = require('sql.js/dist/sql-wasm.js');
51
- let buffer = null;
52
- if (fs.existsSync(dbPath)) buffer = fs.readFileSync(dbPath);
53
- const db = new SQL.Database(buffer);
54
- dbAdapter = 'sqljs';
55
- return { db, type: 'sqljs', path: dbPath };
56
- } catch(e2) {
57
- throw new Error('No se pudo inicializar el grafo SQLite. Corre: npm install sql.js');
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
- prepare: (sql) => db.prepare(sql),
71
- all: (sql, ...params) => db.prepare(sql).all(...params),
72
- get: (sql, ...params) => db.prepare(sql).get(...params),
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
- } else {
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
- const schema = fs.readFileSync(SCHEMA_PATH, 'utf8');
139
- // Ejecutar schema línea por línea para compatibilidad con sql.js
140
- schema.split(';').map(s => s.trim()).filter(s => s && !s.startsWith('--')).forEach(s => {
141
- try { adapter.exec(s + ';'); } catch(e) {}
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 };