agentic-kdd 3.0.1 → 3.0.3

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
@@ -6,6 +6,7 @@ const { update } = require('../src/update');
6
6
  const { graph } = require('../src/graph');
7
7
  const { dashboard } = require('../src/dashboard');
8
8
  const { analyze } = require('../src/analyze');
9
+ const { mcpSetup, mcpStatus } = require('../src/mcp-setup');
9
10
  const pkg = require('../package.json');
10
11
  const path = require('path');
11
12
  const fs = require('fs');
@@ -61,6 +62,13 @@ const HELP = `
61
62
  akdd trail <ciclo_id> Full trail of a specific cycle
62
63
  akdd trail why <f> Why does this file/entity exist?
63
64
 
65
+ Collaborative Mode (Legion):
66
+ akdd collab init Activate collaborative mode — creates shared DB automatically
67
+ akdd collab join <url> <token> Join an existing team's shared memory
68
+ akdd collab push Push your learnings to the team
69
+ akdd collab pull Pull team's latest learnings
70
+ akdd collab status Check collaborative sync status
71
+
64
72
  Intelligence v2.2:
65
73
  akdd git-context Analyze git diff + risk assessment
66
74
  akdd predict Predictive risk patterns from episodic memory
@@ -73,9 +81,10 @@ const HELP = `
73
81
  Dashboard:
74
82
  akdd dashboard Open visual dashboard in browser
75
83
 
76
- MCP Server (Cursor / Claude Code):
77
- node .agentic/grafo/mcp-server.cjs
78
- See: github.com/Adrianlpz211/Agentic-KDD-MCP
84
+ MCP Setup (Cursor / Claude Code / VS Code):
85
+ akdd mcp Auto-configure MCP in all IDEs (recommended)
86
+ akdd mcp --global Configure MCP globally for all projects
87
+ akdd mcp status Check MCP configuration status
79
88
 
80
89
  akdd --version / akdd --help
81
90
  `;
@@ -96,11 +105,7 @@ function runGrafo(cmd, extra) {
96
105
  function runModule(name, cmd, extra) {
97
106
  const p = path.join(process.cwd(), '.agentic', 'grafo', name);
98
107
  if (!fs.existsSync(p)) { console.log(`\n ${name} not found. Run: akdd update\n`); process.exit(1); }
99
- const fullCmd = [
100
- `node "${p}"`,
101
- cmd || '',
102
- extra || ''
103
- ].join(' ').trim().replace(/\s+/g,' ');
108
+ const fullCmd = [`node "${p}"`, cmd || '', extra || ''].join(' ').trim().replace(/\s+/g,' ');
104
109
  try { execSync(fullCmd, { stdio: 'inherit', cwd: process.cwd() }); }
105
110
  catch(e) { process.exit(e.status || 1); }
106
111
  }
@@ -111,14 +116,14 @@ switch (command) {
111
116
  case 'update': update(); break;
112
117
  case 'analyze': analyze(); break;
113
118
 
114
- // ── v3.0: Health ────────────────────────────────────────────────────────
119
+ // ── v3.0: Health ──────────────────────────────────────────────────────
115
120
  case 'health': {
116
121
  const fixFlag = args.includes('--fix') ? '--fix' : '';
117
122
  runModule('health-check.cjs', fixFlag);
118
123
  break;
119
124
  }
120
125
 
121
- // ── Core memory ────────────────────────────────────────────────────────
126
+ // ── Core memory ───────────────────────────────────────────────────────
122
127
  case 'sync': runGrafo('sync'); break;
123
128
  case 'graph': graph(); break;
124
129
  case 'stats': runGrafo('stats'); break;
@@ -136,7 +141,7 @@ switch (command) {
136
141
  runGrafo('impacto', `"${arg1}"`);
137
142
  break;
138
143
 
139
- // ── v3.0: Memory Audit ─────────────────────────────────────────────────
144
+ // ── v3.0: Memory Audit ────────────────────────────────────────────────
140
145
  case 'audit': runModule('memory-audit.cjs', 'report'); break;
141
146
 
142
147
  case 'forget': {
@@ -147,11 +152,11 @@ switch (command) {
147
152
  break;
148
153
  }
149
154
 
150
- // ── v3.0: AST ──────────────────────────────────────────────────────────
155
+ // ── v3.0: AST ─────────────────────────────────────────────────────────
151
156
  case 'ast': {
152
157
  const sub = arg1 || 'index';
153
158
  const tgt = arg2 || '';
154
- if (sub === 'stats') runModule('ast-indexer.cjs', 'stats');
159
+ if (sub === 'stats') runModule('ast-indexer.cjs', 'stats');
155
160
  else if (sub === 'symbols') {
156
161
  if (!tgt) { console.log('\n Uso: akdd ast symbols <archivo>\n'); break; }
157
162
  runModule('ast-indexer.cjs', 'symbols', `"${tgt}"`);
@@ -171,17 +176,17 @@ switch (command) {
171
176
  runModule('decision-trail.cjs', 'why', `"${arg1}"`);
172
177
  break;
173
178
 
174
- // ── v3.0: Specs ────────────────────────────────────────────────────────
179
+ // ── v3.0: Specs ───────────────────────────────────────────────────────
175
180
  case 'spec': {
176
181
  const sub = arg1;
177
182
  const mod = arg2;
178
- if (!sub || sub === 'list') runModule('spec-manager.cjs', 'list');
183
+ if (!sub || sub === 'list') runModule('spec-manager.cjs', 'list');
179
184
  else if (sub === 'create') {
180
185
  if (!mod) { console.log('\n Uso: akdd spec create <módulo> [--bugfix]\n'); break; }
181
186
  runModule('spec-manager.cjs', 'create', `"${mod}"${args.includes('--bugfix') ? ' --bugfix' : ''}`);
182
187
  }
183
- else if (sub === 'waves') { if (!mod) { console.log('\n Uso: akdd spec waves <módulo>\n'); break; } runModule('spec-manager.cjs', 'waves', `"${mod}"`); }
184
- else if (sub === 'validate') { if (!mod) { console.log('\n Uso: akdd spec validate <módulo>\n'); break; } runModule('spec-manager.cjs', 'validate', `"${mod}"`); }
188
+ else if (sub === 'waves') { if (!mod) { console.log('\n Uso: akdd spec waves <módulo>\n'); break; } runModule('spec-manager.cjs', 'waves', `"${mod}"`); }
189
+ else if (sub === 'validate') { if (!mod) { console.log('\n Uso: akdd spec validate <módulo>\n'); break; } runModule('spec-manager.cjs', 'validate', `"${mod}"`); }
185
190
  else runModule('spec-manager.cjs', 'status', `"${sub}"`);
186
191
  break;
187
192
  }
@@ -191,7 +196,7 @@ switch (command) {
191
196
  runModule('spec-manager.cjs', 'create', `"${arg1}"${args.includes('--bugfix') ? ' --bugfix' : ''}`);
192
197
  break;
193
198
 
194
- // ── v3.0: Knowledge ────────────────────────────────────────────────────
199
+ // ── v3.0: Knowledge ───────────────────────────────────────────────────
195
200
  case 'adr':
196
201
  runModule('adr-ingestor.cjs', 'ingest', arg1 || 'docs/adr');
197
202
  break;
@@ -200,24 +205,45 @@ switch (command) {
200
205
  runModule('knowledge-ingestor.cjs', 'ingest', arg1 || '');
201
206
  break;
202
207
 
203
- // ── v3.0: Metrics ──────────────────────────────────────────────────────
208
+ // ── v3.0: Metrics ─────────────────────────────────────────────────────
204
209
  case 'metrics':
205
210
  runModule('metrics.cjs', arg1 || 'summary');
206
211
  break;
207
212
 
208
- // ── v3.0: Decision Trail ───────────────────────────────────────────────
213
+ // ── v3.0: Decision Trail ──────────────────────────────────────────────
209
214
  case 'trail': {
210
- if (!arg1) runModule('decision-trail.cjs', 'recent', '5');
211
- else if (arg1 === 'why') { if (!arg2) { console.log('\n Uso: akdd trail why <entidad>\n'); break; } runModule('decision-trail.cjs', 'why', `"${arg2}"`); }
212
- else if (arg1 === 'timeline'){ if (!arg2) { console.log('\n Uso: akdd trail timeline <módulo>\n'); break; } runModule('decision-trail.cjs', 'timeline', `"${arg2}"`); }
213
- else runModule('decision-trail.cjs', 'ciclo', `"${arg1}"`);
215
+ if (!arg1) runModule('decision-trail.cjs', 'recent', '5');
216
+ else if (arg1 === 'why') { if (!arg2) { console.log('\n Uso: akdd trail why <entidad>\n'); break; } runModule('decision-trail.cjs', 'why', `"${arg2}"`); }
217
+ else if (arg1 === 'timeline') { if (!arg2) { console.log('\n Uso: akdd trail timeline <módulo>\n'); break; } runModule('decision-trail.cjs', 'timeline', `"${arg2}"`); }
218
+ else runModule('decision-trail.cjs', 'ciclo', `"${arg1}"`);
219
+ break;
220
+ }
221
+
222
+ // ── v3.0: Collaborative Mode (Legion) ────────────────────────────────
223
+ case 'collab': {
224
+ const sub = arg1 || 'status';
225
+ if (sub === 'init') {
226
+ runModule('collab-manager.cjs', 'init');
227
+ } else if (sub === 'join') {
228
+ if (!arg2 || !args[3]) {
229
+ console.log('\n Uso: akdd collab join <url> <token>\n');
230
+ break;
231
+ }
232
+ runModule('collab-manager.cjs', 'join', `"${arg2}" "${args[3]}"`);
233
+ } else if (sub === 'push') {
234
+ runModule('collab-manager.cjs', 'push');
235
+ } else if (sub === 'pull') {
236
+ runModule('collab-manager.cjs', 'pull');
237
+ } else {
238
+ runModule('collab-manager.cjs', 'status');
239
+ }
214
240
  break;
215
241
  }
216
242
 
217
- // ── Dashboard ──────────────────────────────────────────────────────────
243
+ // ── Dashboard ─────────────────────────────────────────────────────────
218
244
  case 'dashboard': dashboard(); break;
219
245
 
220
- // ── v2.2: Intelligence ─────────────────────────────────────────────────
246
+ // ── v2.2: Intelligence ────────────────────────────────────────────────
221
247
  case 'git-context': runGrafo('git-context', args.includes('--install-hook') ? '--install-hook' : ''); break;
222
248
  case 'predict': runGrafo('predict'); break;
223
249
  case 'embed-status': runGrafo('embed-status'); break;
@@ -242,6 +268,15 @@ switch (command) {
242
268
  break;
243
269
  }
244
270
 
271
+ // ── v3.0: MCP Setup ───────────────────────────────────────────────────
272
+ case 'mcp': {
273
+ const sub = arg1;
274
+ const opts = { global: args.includes('--global') };
275
+ if (sub === 'status') mcpStatus(process.cwd());
276
+ else mcpSetup(process.cwd(), opts);
277
+ break;
278
+ }
279
+
245
280
  case '--version': case '-v':
246
281
  console.log(pkg.version); break;
247
282
 
@@ -0,0 +1,403 @@
1
+ /**
2
+ * Agentic KDD — Collab Manager v2.0
3
+ * Modo colaborativo automático via Turso + Cloudflare Worker.
4
+ *
5
+ * El usuario solo corre: akdd collab init
6
+ * El sistema crea la DB en Turso automáticamente.
7
+ * El usuario nunca sabe que Turso existe.
8
+ *
9
+ * Uso:
10
+ * node .agentic/grafo/collab-manager.cjs init
11
+ * node .agentic/grafo/collab-manager.cjs push
12
+ * node .agentic/grafo/collab-manager.cjs pull
13
+ * node .agentic/grafo/collab-manager.cjs status
14
+ * node .agentic/grafo/collab-manager.cjs join <url> <token>
15
+ */
16
+
17
+ 'use strict';
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const os = require('os');
22
+ const { execSync } = require('child_process');
23
+
24
+ // URL de tu Cloudflare Worker — actualizar después de deploy
25
+ const PROVISIONER_URL = 'https://agentic-collab.adrianlpz-game.workers.dev';
26
+
27
+ const COLLAB_CONFIG_PATH = '.agentic/collab.json';
28
+
29
+ const SYNC_TABLES = [
30
+ 'nodos',
31
+ 'relaciones_semanticas',
32
+ 'episodios',
33
+ 'knowledge_docs',
34
+ 'ast_symbols',
35
+ 'ast_edges',
36
+ ];
37
+
38
+ // ─── DB LOCAL ──────────────────────────────────────────────────────────────────
39
+
40
+ function openLocalDB(projectRoot) {
41
+ const dbPath = path.join(projectRoot, '.agentic/memoria.db');
42
+ try { return new (require('better-sqlite3'))(dbPath); } catch {}
43
+ try { const { DatabaseSync } = require('node:sqlite'); return new DatabaseSync(dbPath); } catch {}
44
+ throw new Error('No SQLite driver disponible');
45
+ }
46
+
47
+ // ─── CONFIG ───────────────────────────────────────────────────────────────────
48
+
49
+ function loadConfig(projectRoot) {
50
+ const configPath = path.join(projectRoot, COLLAB_CONFIG_PATH);
51
+ if (!fs.existsSync(configPath)) return null;
52
+ try { return JSON.parse(fs.readFileSync(configPath, 'utf8')); } catch { return null; }
53
+ }
54
+
55
+ function saveConfig(projectRoot, config) {
56
+ const configPath = path.join(projectRoot, COLLAB_CONFIG_PATH);
57
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
58
+
59
+ // Asegurar que collab.json está en .gitignore
60
+ const gitignorePath = path.join(projectRoot, '.gitignore');
61
+ if (fs.existsSync(gitignorePath)) {
62
+ const gi = fs.readFileSync(gitignorePath, 'utf8');
63
+ if (!gi.includes('collab.json')) {
64
+ fs.appendFileSync(gitignorePath, '\n# Agentic KDD collab credentials\n.agentic/collab.json\n');
65
+ }
66
+ }
67
+ }
68
+
69
+ // ─── GENERAR PROJECT ID ───────────────────────────────────────────────────────
70
+
71
+ function getProjectId(projectRoot) {
72
+ // Usar el nombre del directorio + un hash corto para que sea único y legible
73
+ const dirName = path.basename(projectRoot).toLowerCase().replace(/[^a-z0-9]/g, '-');
74
+ const hash = require('crypto')
75
+ .createHash('md5')
76
+ .update(projectRoot)
77
+ .digest('hex')
78
+ .substring(0, 6);
79
+ return `${dirName}-${hash}`;
80
+ }
81
+
82
+ // ─── INIT — PROVISIONAR DB AUTOMÁTICAMENTE ───────────────────────────────────
83
+
84
+ async function collabInit(projectRoot) {
85
+ console.log('\n[COLLAB] Activando modo colaborativo...\n');
86
+
87
+ // Verificar si ya está configurado
88
+ const existing = loadConfig(projectRoot);
89
+ if (existing?.enabled && existing?.url) {
90
+ console.log('[COLLAB] Ya configurado. URL:', existing.url);
91
+ console.log('[COLLAB] Para re-configurar: borrar .agentic/collab.json y correr de nuevo.');
92
+ return existing;
93
+ }
94
+
95
+ const projectId = getProjectId(projectRoot);
96
+ console.log(`[COLLAB] Project ID: ${projectId}`);
97
+ console.log('[COLLAB] Creando base de datos compartida...');
98
+
99
+ // Llamar al Cloudflare Worker para provisionar la DB
100
+ let provisionResult;
101
+ try {
102
+ const response = await fetch(PROVISIONER_URL, {
103
+ method: 'POST',
104
+ headers: { 'Content-Type': 'application/json' },
105
+ body: JSON.stringify({ projectId }),
106
+ });
107
+
108
+ if (!response.ok) {
109
+ const err = await response.text();
110
+ throw new Error(`Provisioner error: ${err}`);
111
+ }
112
+
113
+ provisionResult = await response.json();
114
+ } catch (e) {
115
+ console.error('[COLLAB] Error conectando con el servidor de provisión:', e.message);
116
+ console.error('[COLLAB] Verificar que el Cloudflare Worker está activo.');
117
+ process.exit(1);
118
+ }
119
+
120
+ if (!provisionResult.ok) {
121
+ console.error('[COLLAB] Error creando DB:', provisionResult.error);
122
+ process.exit(1);
123
+ }
124
+
125
+ // Guardar credenciales localmente
126
+ const config = {
127
+ enabled: true,
128
+ url: provisionResult.url,
129
+ token: provisionResult.token,
130
+ db: provisionResult.db,
131
+ project_id: projectId,
132
+ member_id: os.userInfo().username,
133
+ sync_on_cycle_end: true,
134
+ last_sync: null,
135
+ created_at: new Date().toISOString(),
136
+ };
137
+
138
+ saveConfig(projectRoot, config);
139
+
140
+ console.log('\n[COLLAB] ✅ Modo colaborativo activado.');
141
+ console.log(`[COLLAB] DB: ${provisionResult.db}`);
142
+ console.log('[COLLAB] Credenciales guardadas en .agentic/collab.json (gitignored)');
143
+ console.log('\n[COLLAB] Para que otros miembros del equipo se unan:');
144
+ console.log(` Compárteles: URL y token de .agentic/collab.json`);
145
+ console.log(` Ellos corren: akdd collab join <url> <token>\n`);
146
+
147
+ // Primer push
148
+ console.log('[COLLAB] Sincronizando memoria local → compartida...');
149
+ await syncUp(projectRoot);
150
+
151
+ return config;
152
+ }
153
+
154
+ // ─── JOIN — PARA EL DEV QUE SE UNE AL EQUIPO ─────────────────────────────────
155
+
156
+ async function collabJoin(projectRoot, url, token) {
157
+ console.log('\n[COLLAB] Uniéndose al equipo...\n');
158
+
159
+ const config = {
160
+ enabled: true,
161
+ url,
162
+ token,
163
+ member_id: os.userInfo().username,
164
+ sync_on_cycle_end: true,
165
+ last_sync: null,
166
+ joined_at: new Date().toISOString(),
167
+ };
168
+
169
+ saveConfig(projectRoot, config);
170
+
171
+ console.log('[COLLAB] ✅ Conectado al equipo.');
172
+ console.log('[COLLAB] Descargando memoria del equipo...');
173
+
174
+ await syncDown(projectRoot);
175
+
176
+ console.log('[COLLAB] ✅ Listo. Ya tienes toda la memoria del equipo.\n');
177
+ }
178
+
179
+ // ─── SYNC UP (local → Turso) ──────────────────────────────────────────────────
180
+
181
+ async function syncUp(projectRoot) {
182
+ const config = loadConfig(projectRoot);
183
+ if (!config?.enabled) {
184
+ console.log('[COLLAB] Modo colaborativo no activado. Correr: akdd collab init');
185
+ return { synced: false };
186
+ }
187
+
188
+ let client;
189
+ try {
190
+ const { createClient } = require('@libsql/client');
191
+ client = createClient({ url: config.url, authToken: config.token });
192
+ } catch {
193
+ console.log('[COLLAB] @libsql/client no instalado. Correr: npm install @libsql/client');
194
+ return { synced: false };
195
+ }
196
+
197
+ const localDB = openLocalDB(projectRoot);
198
+ const lastSync = config.last_sync || '1970-01-01T00:00:00.000Z';
199
+ let totalRows = 0;
200
+
201
+ // Schema push — crear tablas en remoto si no existen
202
+ await pushSchema(client, localDB);
203
+
204
+ for (const table of SYNC_TABLES) {
205
+ try {
206
+ // Solo filas nuevas/modificadas desde el último sync
207
+ const dateField = getDateField(table);
208
+ const rows = localDB.prepare(
209
+ `SELECT * FROM ${table} WHERE ${dateField} > ? LIMIT 500`
210
+ ).all(lastSync);
211
+
212
+ if (rows.length === 0) continue;
213
+
214
+ console.log(`[COLLAB] ${table}: ${rows.length} filas a sincronizar`);
215
+
216
+ for (const row of rows) {
217
+ const keys = Object.keys(row);
218
+ const vals = Object.values(row);
219
+ const placeholders = keys.map(() => '?').join(', ');
220
+ try {
221
+ await client.execute({
222
+ sql: `INSERT OR REPLACE INTO ${table} (${keys.join(', ')}) VALUES (${placeholders})`,
223
+ args: vals,
224
+ });
225
+ totalRows++;
226
+ } catch {}
227
+ }
228
+ } catch {}
229
+ }
230
+
231
+ // Actualizar last_sync
232
+ config.last_sync = new Date().toISOString();
233
+ saveConfig(projectRoot, config);
234
+
235
+ console.log(`[COLLAB] ✅ Sync up: ${totalRows} filas enviadas.`);
236
+ return { synced: true, rows: totalRows };
237
+ }
238
+
239
+ // ─── SYNC DOWN (Turso → local) ────────────────────────────────────────────────
240
+
241
+ async function syncDown(projectRoot) {
242
+ const config = loadConfig(projectRoot);
243
+ if (!config?.enabled) return { synced: false };
244
+
245
+ let client;
246
+ try {
247
+ const { createClient } = require('@libsql/client');
248
+ client = createClient({ url: config.url, authToken: config.token });
249
+ } catch {
250
+ console.log('[COLLAB] @libsql/client no instalado. Correr: npm install @libsql/client');
251
+ return { synced: false };
252
+ }
253
+
254
+ const localDB = openLocalDB(projectRoot);
255
+ let totalRows = 0;
256
+
257
+ for (const table of SYNC_TABLES) {
258
+ try {
259
+ const dateField = getDateField(table);
260
+ const lastSync = config.last_sync || '1970-01-01T00:00:00.000Z';
261
+
262
+ const rs = await client.execute(
263
+ `SELECT * FROM ${table} WHERE ${dateField} > ? LIMIT 1000`,
264
+ [lastSync]
265
+ );
266
+
267
+ if (!rs.rows?.length) continue;
268
+
269
+ console.log(`[COLLAB] ${table}: recibiendo ${rs.rows.length} filas`);
270
+
271
+ for (const row of rs.rows) {
272
+ const keys = Object.keys(row);
273
+ const vals = keys.map(k => row[k]);
274
+ const placeholders = keys.map(() => '?').join(', ');
275
+
276
+ // Estrategia por tabla:
277
+ // episodios → INSERT OR IGNORE (son aditivos, no sobrescribir locales)
278
+ // resto → INSERT OR REPLACE (toma la versión más reciente)
279
+ const strategy = table === 'episodios' ? 'OR IGNORE' : 'OR REPLACE';
280
+
281
+ try {
282
+ localDB.prepare(
283
+ `INSERT ${strategy} INTO ${table} (${keys.join(', ')}) VALUES (${placeholders})`
284
+ ).run(vals);
285
+ totalRows++;
286
+ } catch {}
287
+ }
288
+ } catch {}
289
+ }
290
+
291
+ // Actualizar last_sync
292
+ config.last_sync = new Date().toISOString();
293
+ saveConfig(projectRoot, config);
294
+
295
+ console.log(`[COLLAB] ✅ Sync down: ${totalRows} filas recibidas.`);
296
+ return { synced: true, rows: totalRows };
297
+ }
298
+
299
+ // ─── SCHEMA PUSH ──────────────────────────────────────────────────────────────
300
+ // Crea las tablas en la DB remota si no existen (primera vez)
301
+
302
+ async function pushSchema(client, localDB) {
303
+ const tables = localDB.prepare(
304
+ "SELECT name, sql FROM sqlite_master WHERE type='table' AND sql IS NOT NULL"
305
+ ).all();
306
+
307
+ for (const table of tables) {
308
+ if (!SYNC_TABLES.includes(table.name)) continue;
309
+ try {
310
+ await client.execute(
311
+ table.sql.replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS')
312
+ );
313
+ } catch {}
314
+ }
315
+ }
316
+
317
+ // ─── STATUS ───────────────────────────────────────────────────────────────────
318
+
319
+ async function status(projectRoot) {
320
+ const config = loadConfig(projectRoot);
321
+
322
+ console.log('\n[COLLAB] Estado del modo colaborativo:\n');
323
+
324
+ if (!config?.enabled) {
325
+ console.log(' ❌ No activado.');
326
+ console.log(' Correr: akdd collab init\n');
327
+ return;
328
+ }
329
+
330
+ console.log(` ✅ Activado`);
331
+ console.log(` DB: ${config.db || config.url}`);
332
+ console.log(` Miembro: ${config.member_id}`);
333
+ console.log(` Último sync: ${config.last_sync || 'nunca'}`);
334
+ console.log(` Auto-sync: ${config.sync_on_cycle_end ? 'sí (al final de cada aa:)' : 'no'}`);
335
+
336
+ // Test de conexión
337
+ try {
338
+ const { createClient } = require('@libsql/client');
339
+ const client = createClient({ url: config.url, authToken: config.token });
340
+ await client.execute('SELECT 1');
341
+ console.log(' Conexión: ✅ OK\n');
342
+ } catch {
343
+ console.log(' Conexión: ❌ Error — verificar credenciales\n');
344
+ }
345
+ }
346
+
347
+ // ─── HELPER ───────────────────────────────────────────────────────────────────
348
+
349
+ function getDateField(table) {
350
+ const fields = {
351
+ nodos: 'fecha_update',
352
+ episodios: 'fecha',
353
+ relaciones_semanticas: 'valid_at',
354
+ knowledge_docs: 'last_indexed',
355
+ ast_symbols: 'last_indexed',
356
+ ast_edges: 'last_indexed',
357
+ };
358
+ return fields[table] || 'rowid';
359
+ }
360
+
361
+ // ─── CLI ──────────────────────────────────────────────────────────────────────
362
+
363
+ if (require.main === module) {
364
+ const [,, cmd, arg1, arg2] = process.argv;
365
+ const projectRoot = process.cwd();
366
+
367
+ switch (cmd) {
368
+ case 'init':
369
+ collabInit(projectRoot).catch(console.error);
370
+ break;
371
+ case 'join':
372
+ if (!arg1 || !arg2) {
373
+ console.error('Uso: collab-manager.cjs join <url> <token>');
374
+ process.exit(1);
375
+ }
376
+ collabJoin(projectRoot, arg1, arg2).catch(console.error);
377
+ break;
378
+ case 'push':
379
+ syncUp(projectRoot).then(r => {
380
+ process.exit(r.synced ? 0 : 1);
381
+ }).catch(console.error);
382
+ break;
383
+ case 'pull':
384
+ syncDown(projectRoot).then(r => {
385
+ process.exit(r.synced ? 0 : 1);
386
+ }).catch(console.error);
387
+ break;
388
+ case 'status':
389
+ status(projectRoot).catch(console.error);
390
+ break;
391
+ default:
392
+ console.log('Uso: node collab-manager.cjs [init | join <url> <token> | push | pull | status]');
393
+ }
394
+ }
395
+
396
+ module.exports = {
397
+ collabInit,
398
+ collabJoin,
399
+ syncUp,
400
+ syncDown,
401
+ status,
402
+ loadConfig,
403
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-kdd",
3
- "version": "3.0.1",
3
+ "version": "3.0.3",
4
4
  "description": "Autonomous development pipeline — aa: · ag: · audit: · AST graph · Harness · Specs · Impact analysis · Decision trail · Metrics · MCP server. Works with Cursor and Claude Code.",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/init.js CHANGED
@@ -1,4 +1,5 @@
1
1
  'use strict';
2
+ const { mcpSetup } = require('./mcp-setup');
2
3
 
3
4
  const fs = require('fs-extra');
4
5
  const path = require('path');
@@ -278,6 +279,14 @@ Estado: Pendiente aa: configurar
278
279
  console.log(chalk.gray(' CLAUDE.md — activa aa: / ag: / audit:'));
279
280
  console.log(chalk.gray(' .cursorrules — reglas para Cursor'));
280
281
 
282
+ // ── CONFIGURAR MCP AUTOMÁTICAMENTE ─────────────────────────────────────────
283
+ console.log(chalk.bold(" Configurando MCP server..."));
284
+ try {
285
+ await mcpSetup(projectPath, { silent: false });
286
+ } catch(e) {
287
+ console.log(chalk.gray(" (MCP: ejecuta akdd mcp para configurarlo manualmente)"));
288
+ }
289
+
281
290
  // Instrucción final
282
291
  console.log('\n' + chalk.dim(' ─────────────────────────────────────────────'));
283
292
  console.log(chalk.bold(' Último paso — abre este proyecto en'));
@@ -0,0 +1,265 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs-extra');
4
+ const path = require('path');
5
+ const os = require('os');
6
+ const { execSync } = require('child_process');
7
+ const chalk = require('chalk');
8
+
9
+ /**
10
+ * akdd mcp — Configura el MCP server automáticamente.
11
+ *
12
+ * Hace todo lo posible sin intervención del usuario:
13
+ * 1. Escribe .cursor/mcp.json en el proyecto (Cursor lo lee automáticamente)
14
+ * 2. Intenta ejecutar `claude mcp add` si el CLI está disponible
15
+ * 3. Imprime el JSON EXACTO (con ruta real del sistema, no placeholder)
16
+ * para los casos que requieran paso manual
17
+ *
18
+ * El usuario NUNCA tiene que adivinar la ruta — este comando la resuelve por él.
19
+ */
20
+
21
+ async function mcpSetup(projectPath, opts = {}) {
22
+ projectPath = projectPath || process.cwd();
23
+
24
+ // ── Verificar que Agentic está instalado ───────────────────────────────────
25
+ const serverFile = path.join(projectPath, '.agentic', 'grafo', 'mcp-server.cjs');
26
+ if (!fs.existsSync(serverFile)) {
27
+ console.log(chalk.yellow('\n mcp-server.cjs no encontrado.'));
28
+ console.log(chalk.gray(' Ejecuta: akdd update\n'));
29
+ return;
30
+ }
31
+
32
+ // ── Resolver ruta absoluta real (sin placeholders, sin adivinar) ───────────
33
+ // path.resolve() → ruta exacta del sistema actual, con nombre de usuario correcto
34
+ const serverPath = path.resolve(serverFile);
35
+ const serverPathJson = serverPath.replace(/\\/g, '\\\\'); // escaping para JSON en Windows
36
+
37
+ console.log('\n' + chalk.bold.hex('#8b5cf6')(' Agentic KDD — MCP Setup'));
38
+ console.log(chalk.gray(` Ruta del servidor: ${serverPath}\n`));
39
+
40
+ const results = {
41
+ cursor_project: false,
42
+ cursor_global: false,
43
+ claude_code: false,
44
+ };
45
+
46
+ // ══ PASO 1: Cursor — proyecto (automático, siempre funciona) ═══════════════
47
+ const cursorMcpDir = path.join(projectPath, '.cursor');
48
+ const cursorMcpFile = path.join(cursorMcpDir, 'mcp.json');
49
+
50
+ try {
51
+ fs.ensureDirSync(cursorMcpDir);
52
+
53
+ let cursorConfig = {};
54
+ if (fs.existsSync(cursorMcpFile)) {
55
+ try { cursorConfig = JSON.parse(fs.readFileSync(cursorMcpFile, 'utf8')); } catch {}
56
+ }
57
+ if (!cursorConfig.mcpServers) cursorConfig.mcpServers = {};
58
+
59
+ cursorConfig.mcpServers['agentic-kdd'] = {
60
+ command: 'node',
61
+ args: [serverPath],
62
+ };
63
+
64
+ fs.writeFileSync(cursorMcpFile, JSON.stringify(cursorConfig, null, 2));
65
+ results.cursor_project = true;
66
+ console.log(chalk.green(' ✓ Cursor (proyecto) → .cursor/mcp.json actualizado'));
67
+ console.log(chalk.gray(' Reinicia Cursor o recarga la ventana (Ctrl+Shift+P → "Reload Window")'));
68
+ } catch (e) {
69
+ console.log(chalk.yellow(` ⚠ Cursor (proyecto) → Error: ${e.message}`));
70
+ }
71
+
72
+ // ══ PASO 2: Claude Code CLI (automático si está instalado) ═════════════════
73
+ const claudeCliAvailable = isCLIAvailable('claude');
74
+ if (claudeCliAvailable) {
75
+ try {
76
+ // claude mcp add agentic-kdd -- node "/ruta/exacta/mcp-server.cjs"
77
+ execSync(`claude mcp add agentic-kdd -- node "${serverPath}"`, {
78
+ stdio: 'pipe',
79
+ cwd: projectPath,
80
+ });
81
+ results.claude_code = true;
82
+ console.log(chalk.green(' ✓ Claude Code → registrado via "claude mcp add"'));
83
+ } catch (e) {
84
+ // Puede fallar si ya existe — intentar actualizar
85
+ try {
86
+ execSync(`claude mcp remove agentic-kdd`, { stdio: 'pipe', cwd: projectPath });
87
+ execSync(`claude mcp add agentic-kdd -- node "${serverPath}"`, {
88
+ stdio: 'pipe', cwd: projectPath,
89
+ });
90
+ results.claude_code = true;
91
+ console.log(chalk.green(' ✓ Claude Code → actualizado'));
92
+ } catch {
93
+ console.log(chalk.gray(' ~ Claude Code → CLI no disponible o ya configurado'));
94
+ }
95
+ }
96
+ } else {
97
+ console.log(chalk.gray(' ~ Claude Code → CLI no detectado (config manual abajo)'));
98
+ }
99
+
100
+ // ══ PASO 3: Cursor global (opcional, solo si --global) ════════════════════
101
+ if (opts.global) {
102
+ const globalCursorConfig = getGlobalCursorConfigPath();
103
+ if (globalCursorConfig) {
104
+ try {
105
+ fs.ensureDirSync(path.dirname(globalCursorConfig));
106
+ let globalConfig = {};
107
+ if (fs.existsSync(globalCursorConfig)) {
108
+ try { globalConfig = JSON.parse(fs.readFileSync(globalCursorConfig, 'utf8')); } catch {}
109
+ }
110
+ if (!globalConfig.mcpServers) globalConfig.mcpServers = {};
111
+ globalConfig.mcpServers['agentic-kdd'] = { command: 'node', args: [serverPath] };
112
+ fs.writeFileSync(globalCursorConfig, JSON.stringify(globalConfig, null, 2));
113
+ results.cursor_global = true;
114
+ console.log(chalk.green(` ✓ Cursor (global) → ${globalCursorConfig}`));
115
+ } catch (e) {
116
+ console.log(chalk.yellow(` ⚠ Cursor (global) → ${e.message}`));
117
+ }
118
+ }
119
+ }
120
+
121
+ // ══ PASO 4: Imprimir configs manuales con ruta EXACTA ═════════════════════
122
+ console.log('\n' + chalk.bold(' ── Config manual (si necesitas hacerlo tú mismo) ──────────────────'));
123
+
124
+ // Cursor manual
125
+ const cursorJson = JSON.stringify({
126
+ mcpServers: {
127
+ 'agentic-kdd': {
128
+ command: 'node',
129
+ args: [serverPath],
130
+ }
131
+ }
132
+ }, null, 2);
133
+
134
+ console.log('\n' + chalk.cyan(' Cursor → .cursor/mcp.json'));
135
+ console.log(chalk.gray(' (Abre Cursor → Settings → MCP → Add → pega esto:)\n'));
136
+ console.log(chalk.white(cursorJson.split('\n').map(l => ' ' + l).join('\n')));
137
+
138
+ // Claude Code manual
139
+ console.log('\n' + chalk.cyan(' Claude Code → terminal'));
140
+ console.log(chalk.white(` claude mcp add agentic-kdd -- node "${serverPath}"`));
141
+
142
+ // VS Code manual
143
+ console.log('\n' + chalk.cyan(' VS Code → .vscode/settings.json'));
144
+ const vscodeJson = JSON.stringify({
145
+ 'mcp.servers': {
146
+ 'agentic-kdd': {
147
+ command: 'node',
148
+ args: [serverPath],
149
+ type: 'stdio',
150
+ }
151
+ }
152
+ }, null, 2);
153
+ console.log(chalk.white(vscodeJson.split('\n').map(l => ' ' + l).join('\n')));
154
+
155
+ // ══ RESUMEN ════════════════════════════════════════════════════════════════
156
+ console.log('\n' + chalk.dim(' ──────────────────────────────────────────────────────'));
157
+ const autoCount = Object.values(results).filter(Boolean).length;
158
+ if (autoCount > 0) {
159
+ console.log(chalk.bold.green(` ${autoCount} configuración(es) automática(s) completada(s).`));
160
+ }
161
+
162
+ if (results.cursor_project) {
163
+ console.log(chalk.green(' Cursor listo:') + chalk.gray(' Reload Window → las tools aparecen automáticamente.'));
164
+ }
165
+ if (results.claude_code) {
166
+ console.log(chalk.green(' Claude Code listo:') + chalk.gray(' cierra y abre el proyecto.'));
167
+ }
168
+
169
+ console.log('\n' + chalk.gray(' Para configurar globalmente (todos tus proyectos):'));
170
+ console.log(chalk.gray(' akdd mcp --global\n'));
171
+ }
172
+
173
+ // ─── HELPERS ──────────────────────────────────────────────────────────────────
174
+
175
+ function isCLIAvailable(cmd) {
176
+ try {
177
+ execSync(`${cmd} --version`, { stdio: 'pipe', timeout: 5000 });
178
+ return true;
179
+ } catch { return false; }
180
+ }
181
+
182
+ function getGlobalCursorConfigPath() {
183
+ const platform = os.platform();
184
+ const home = os.homedir();
185
+
186
+ if (platform === 'win32') {
187
+ // Windows: %APPDATA%\Cursor\User\globalStorage\mcp.json
188
+ const appData = process.env.APPDATA || path.join(home, 'AppData', 'Roaming');
189
+ return path.join(appData, 'Cursor', 'User', 'globalStorage', 'mcp.json');
190
+ }
191
+ if (platform === 'darwin') {
192
+ // macOS: ~/Library/Application Support/Cursor/User/globalStorage/mcp.json
193
+ return path.join(home, 'Library', 'Application Support', 'Cursor', 'User', 'globalStorage', 'mcp.json');
194
+ }
195
+ // Linux: ~/.config/Cursor/User/globalStorage/mcp.json
196
+ return path.join(home, '.config', 'Cursor', 'User', 'globalStorage', 'mcp.json');
197
+ }
198
+
199
+ /**
200
+ * Verifica y muestra el estado actual de la config MCP.
201
+ * akdd mcp status
202
+ */
203
+ function mcpStatus(projectPath) {
204
+ projectPath = projectPath || process.cwd();
205
+
206
+ const serverFile = path.join(projectPath, '.agentic', 'grafo', 'mcp-server.cjs');
207
+ const cursorProject = path.join(projectPath, '.cursor', 'mcp.json');
208
+ const globalCursor = getGlobalCursorConfigPath();
209
+
210
+ console.log('\n' + chalk.bold(' Agentic KDD — MCP Status\n'));
211
+
212
+ // Server file
213
+ const hasServer = fs.existsSync(serverFile);
214
+ console.log(hasServer
215
+ ? chalk.green(' ✓ mcp-server.cjs encontrado')
216
+ : chalk.red(' ✗ mcp-server.cjs NO encontrado — ejecutar: akdd update'));
217
+
218
+ // Cursor project config
219
+ let cursorProjectOk = false;
220
+ if (fs.existsSync(cursorProject)) {
221
+ try {
222
+ const config = JSON.parse(fs.readFileSync(cursorProject, 'utf8'));
223
+ cursorProjectOk = !!(config?.mcpServers?.['agentic-kdd']);
224
+ } catch {}
225
+ }
226
+ console.log(cursorProjectOk
227
+ ? chalk.green(' ✓ Cursor (proyecto) .cursor/mcp.json configurado')
228
+ : chalk.yellow(' ~ Cursor (proyecto) No configurado — ejecutar: akdd mcp'));
229
+
230
+ // Cursor global config
231
+ let cursorGlobalOk = false;
232
+ if (globalCursor && fs.existsSync(globalCursor)) {
233
+ try {
234
+ const config = JSON.parse(fs.readFileSync(globalCursor, 'utf8'));
235
+ cursorGlobalOk = !!(config?.mcpServers?.['agentic-kdd']);
236
+ } catch {}
237
+ }
238
+ console.log(cursorGlobalOk
239
+ ? chalk.green(' ✓ Cursor (global) configurado')
240
+ : chalk.gray(' ~ Cursor (global) No configurado — opcional: akdd mcp --global'));
241
+
242
+ // Claude Code
243
+ const claudeAvailable = isCLIAvailable('claude');
244
+ if (claudeAvailable) {
245
+ try {
246
+ const mcpList = execSync('claude mcp list', { stdio: 'pipe' }).toString();
247
+ const hasAgentic = mcpList.includes('agentic-kdd');
248
+ console.log(hasAgentic
249
+ ? chalk.green(' ✓ Claude Code registrado')
250
+ : chalk.yellow(' ~ Claude Code No registrado — ejecutar: akdd mcp'));
251
+ } catch {
252
+ console.log(chalk.gray(' ~ Claude Code CLI disponible pero sin listar MCPs'));
253
+ }
254
+ } else {
255
+ console.log(chalk.gray(' ~ Claude Code CLI No instalado'));
256
+ }
257
+
258
+ if (hasServer && !cursorProjectOk) {
259
+ console.log('\n' + chalk.bold(' → Ejecuta: akdd mcp\n'));
260
+ } else if (hasServer && cursorProjectOk) {
261
+ console.log('\n' + chalk.green(' Todo configurado. Recarga la ventana en Cursor si es necesario.\n'));
262
+ }
263
+ }
264
+
265
+ module.exports = { mcpSetup, mcpStatus };