agentic-kdd 3.0.2 → 3.0.4

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
@@ -62,6 +62,14 @@ const HELP = `
62
62
  akdd trail <ciclo_id> Full trail of a specific cycle
63
63
  akdd trail why <f> Why does this file/entity exist?
64
64
 
65
+ Collaborative Mode (Legion):
66
+ akdd collab init Activate collaborative mode — creates shared DB automatically
67
+ akdd collab invite Generate a 6-char invite code for a team member (24h, one-use)
68
+ akdd collab join <code> Join the team with an invite code (e.g. LUMO-X7K2P4)
69
+ akdd collab push Push your learnings to the team
70
+ akdd collab pull Pull team's latest learnings
71
+ akdd collab status Check collaborative sync status
72
+
65
73
  Intelligence v2.2:
66
74
  akdd git-context Analyze git diff + risk assessment
67
75
  akdd predict Predictive risk patterns from episodic memory
@@ -78,7 +86,6 @@ const HELP = `
78
86
  akdd mcp Auto-configure MCP in all IDEs (recommended)
79
87
  akdd mcp --global Configure MCP globally for all projects
80
88
  akdd mcp status Check MCP configuration status
81
- (manual fallback: imprime el JSON exacto con tu ruta real)
82
89
 
83
90
  akdd --version / akdd --help
84
91
  `;
@@ -99,11 +106,7 @@ function runGrafo(cmd, extra) {
99
106
  function runModule(name, cmd, extra) {
100
107
  const p = path.join(process.cwd(), '.agentic', 'grafo', name);
101
108
  if (!fs.existsSync(p)) { console.log(`\n ${name} not found. Run: akdd update\n`); process.exit(1); }
102
- const fullCmd = [
103
- `node "${p}"`,
104
- cmd || '',
105
- extra || ''
106
- ].join(' ').trim().replace(/\s+/g,' ');
109
+ const fullCmd = [`node "${p}"`, cmd || '', extra || ''].join(' ').trim().replace(/\s+/g,' ');
107
110
  try { execSync(fullCmd, { stdio: 'inherit', cwd: process.cwd() }); }
108
111
  catch(e) { process.exit(e.status || 1); }
109
112
  }
@@ -114,14 +117,14 @@ switch (command) {
114
117
  case 'update': update(); break;
115
118
  case 'analyze': analyze(); break;
116
119
 
117
- // ── v3.0: Health ────────────────────────────────────────────────────────
120
+ // ── v3.0: Health ──────────────────────────────────────────────────────
118
121
  case 'health': {
119
122
  const fixFlag = args.includes('--fix') ? '--fix' : '';
120
123
  runModule('health-check.cjs', fixFlag);
121
124
  break;
122
125
  }
123
126
 
124
- // ── Core memory ────────────────────────────────────────────────────────
127
+ // ── Core memory ───────────────────────────────────────────────────────
125
128
  case 'sync': runGrafo('sync'); break;
126
129
  case 'graph': graph(); break;
127
130
  case 'stats': runGrafo('stats'); break;
@@ -139,7 +142,7 @@ switch (command) {
139
142
  runGrafo('impacto', `"${arg1}"`);
140
143
  break;
141
144
 
142
- // ── v3.0: Memory Audit ─────────────────────────────────────────────────
145
+ // ── v3.0: Memory Audit ────────────────────────────────────────────────
143
146
  case 'audit': runModule('memory-audit.cjs', 'report'); break;
144
147
 
145
148
  case 'forget': {
@@ -150,11 +153,11 @@ switch (command) {
150
153
  break;
151
154
  }
152
155
 
153
- // ── v3.0: AST ──────────────────────────────────────────────────────────
156
+ // ── v3.0: AST ─────────────────────────────────────────────────────────
154
157
  case 'ast': {
155
158
  const sub = arg1 || 'index';
156
159
  const tgt = arg2 || '';
157
- if (sub === 'stats') runModule('ast-indexer.cjs', 'stats');
160
+ if (sub === 'stats') runModule('ast-indexer.cjs', 'stats');
158
161
  else if (sub === 'symbols') {
159
162
  if (!tgt) { console.log('\n Uso: akdd ast symbols <archivo>\n'); break; }
160
163
  runModule('ast-indexer.cjs', 'symbols', `"${tgt}"`);
@@ -174,17 +177,17 @@ switch (command) {
174
177
  runModule('decision-trail.cjs', 'why', `"${arg1}"`);
175
178
  break;
176
179
 
177
- // ── v3.0: Specs ────────────────────────────────────────────────────────
180
+ // ── v3.0: Specs ───────────────────────────────────────────────────────
178
181
  case 'spec': {
179
182
  const sub = arg1;
180
183
  const mod = arg2;
181
- if (!sub || sub === 'list') runModule('spec-manager.cjs', 'list');
184
+ if (!sub || sub === 'list') runModule('spec-manager.cjs', 'list');
182
185
  else if (sub === 'create') {
183
186
  if (!mod) { console.log('\n Uso: akdd spec create <módulo> [--bugfix]\n'); break; }
184
187
  runModule('spec-manager.cjs', 'create', `"${mod}"${args.includes('--bugfix') ? ' --bugfix' : ''}`);
185
188
  }
186
- else if (sub === 'waves') { if (!mod) { console.log('\n Uso: akdd spec waves <módulo>\n'); break; } runModule('spec-manager.cjs', 'waves', `"${mod}"`); }
187
- else if (sub === 'validate') { if (!mod) { console.log('\n Uso: akdd spec validate <módulo>\n'); break; } runModule('spec-manager.cjs', 'validate', `"${mod}"`); }
189
+ else if (sub === 'waves') { if (!mod) { console.log('\n Uso: akdd spec waves <módulo>\n'); break; } runModule('spec-manager.cjs', 'waves', `"${mod}"`); }
190
+ else if (sub === 'validate') { if (!mod) { console.log('\n Uso: akdd spec validate <módulo>\n'); break; } runModule('spec-manager.cjs', 'validate', `"${mod}"`); }
188
191
  else runModule('spec-manager.cjs', 'status', `"${sub}"`);
189
192
  break;
190
193
  }
@@ -194,7 +197,7 @@ switch (command) {
194
197
  runModule('spec-manager.cjs', 'create', `"${arg1}"${args.includes('--bugfix') ? ' --bugfix' : ''}`);
195
198
  break;
196
199
 
197
- // ── v3.0: Knowledge ────────────────────────────────────────────────────
200
+ // ── v3.0: Knowledge ───────────────────────────────────────────────────
198
201
  case 'adr':
199
202
  runModule('adr-ingestor.cjs', 'ingest', arg1 || 'docs/adr');
200
203
  break;
@@ -203,24 +206,48 @@ switch (command) {
203
206
  runModule('knowledge-ingestor.cjs', 'ingest', arg1 || '');
204
207
  break;
205
208
 
206
- // ── v3.0: Metrics ──────────────────────────────────────────────────────
209
+ // ── v3.0: Metrics ─────────────────────────────────────────────────────
207
210
  case 'metrics':
208
211
  runModule('metrics.cjs', arg1 || 'summary');
209
212
  break;
210
213
 
211
- // ── v3.0: Decision Trail ───────────────────────────────────────────────
214
+ // ── v3.0: Decision Trail ──────────────────────────────────────────────
212
215
  case 'trail': {
213
- if (!arg1) runModule('decision-trail.cjs', 'recent', '5');
214
- else if (arg1 === 'why') { if (!arg2) { console.log('\n Uso: akdd trail why <entidad>\n'); break; } runModule('decision-trail.cjs', 'why', `"${arg2}"`); }
215
- else if (arg1 === 'timeline'){ if (!arg2) { console.log('\n Uso: akdd trail timeline <módulo>\n'); break; } runModule('decision-trail.cjs', 'timeline', `"${arg2}"`); }
216
- else runModule('decision-trail.cjs', 'ciclo', `"${arg1}"`);
216
+ if (!arg1) runModule('decision-trail.cjs', 'recent', '5');
217
+ else if (arg1 === 'why') { if (!arg2) { console.log('\n Uso: akdd trail why <entidad>\n'); break; } runModule('decision-trail.cjs', 'why', `"${arg2}"`); }
218
+ else if (arg1 === 'timeline') { if (!arg2) { console.log('\n Uso: akdd trail timeline <módulo>\n'); break; } runModule('decision-trail.cjs', 'timeline', `"${arg2}"`); }
219
+ else runModule('decision-trail.cjs', 'ciclo', `"${arg1}"`);
217
220
  break;
218
221
  }
219
222
 
220
- // ── Dashboard ──────────────────────────────────────────────────────────
223
+ // ── v3.0: Collaborative Mode (Legion) ────────────────────────────────
224
+ case 'collab': {
225
+ const sub = arg1 || 'status';
226
+ if (sub === 'init') {
227
+ runModule('collab-manager.cjs', 'init');
228
+ } else if (sub === 'invite') {
229
+ runModule('collab-manager.cjs', 'invite');
230
+ } else if (sub === 'join') {
231
+ if (!arg2) {
232
+ console.log('\n Uso: akdd collab join <código>\n');
233
+ console.log(' El código lo genera el jefe con: akdd collab invite\n');
234
+ break;
235
+ }
236
+ runModule('collab-manager.cjs', 'join', `"${arg2}"`);
237
+ } else if (sub === 'push') {
238
+ runModule('collab-manager.cjs', 'push');
239
+ } else if (sub === 'pull') {
240
+ runModule('collab-manager.cjs', 'pull');
241
+ } else {
242
+ runModule('collab-manager.cjs', 'status');
243
+ }
244
+ break;
245
+ }
246
+
247
+ // ── Dashboard ─────────────────────────────────────────────────────────
221
248
  case 'dashboard': dashboard(); break;
222
249
 
223
- // ── v2.2: Intelligence ─────────────────────────────────────────────────
250
+ // ── v2.2: Intelligence ────────────────────────────────────────────────
224
251
  case 'git-context': runGrafo('git-context', args.includes('--install-hook') ? '--install-hook' : ''); break;
225
252
  case 'predict': runGrafo('predict'); break;
226
253
  case 'embed-status': runGrafo('embed-status'); break;
@@ -245,16 +272,12 @@ switch (command) {
245
272
  break;
246
273
  }
247
274
 
248
-
249
- // ── v3.0: MCP Setup ────────────────────────────────────────────────────
275
+ // ── v3.0: MCP Setup ───────────────────────────────────────────────────
250
276
  case 'mcp': {
251
277
  const sub = arg1;
252
278
  const opts = { global: args.includes('--global') };
253
- if (sub === 'status') {
254
- mcpStatus(process.cwd());
255
- } else {
256
- mcpSetup(process.cwd(), opts);
257
- }
279
+ if (sub === 'status') mcpStatus(process.cwd());
280
+ else mcpSetup(process.cwd(), opts);
258
281
  break;
259
282
  }
260
283
 
@@ -0,0 +1,480 @@
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, urlOrCode, token) {
157
+ console.log('\n[COLLAB] Uniéndose al equipo...\n');
158
+
159
+ let finalUrl = urlOrCode;
160
+ let finalToken = token;
161
+
162
+ // Detectar si es un código de invitación (formato: PREFIX-XXXXXX)
163
+ const isInviteCode = !urlOrCode?.startsWith('libsql://') && !urlOrCode?.startsWith('http');
164
+
165
+ if (isInviteCode) {
166
+ console.log(`[COLLAB] Resolviendo código: ${urlOrCode}`);
167
+ try {
168
+ const response = await fetch(`${PROVISIONER_URL}/join`, {
169
+ method: 'POST',
170
+ headers: { 'Content-Type': 'application/json' },
171
+ body: JSON.stringify({ code: urlOrCode }),
172
+ });
173
+ const result = await response.json();
174
+
175
+ if (!result.ok) {
176
+ console.error(`[COLLAB] ❌ ${result.error}`);
177
+ process.exit(1);
178
+ }
179
+
180
+ finalUrl = result.url;
181
+ finalToken = result.token;
182
+ console.log(`[COLLAB] ✅ Código válido. Conectando a: ${result.db}`);
183
+ } catch (e) {
184
+ console.error('[COLLAB] Error resolviendo código:', e.message);
185
+ process.exit(1);
186
+ }
187
+ }
188
+
189
+ const config = {
190
+ enabled: true,
191
+ url: finalUrl,
192
+ token: finalToken,
193
+ member_id: os.userInfo().username,
194
+ sync_on_cycle_end: true,
195
+ last_sync: null,
196
+ joined_at: new Date().toISOString(),
197
+ };
198
+
199
+ saveConfig(projectRoot, config);
200
+
201
+ console.log('[COLLAB] ✅ Conectado al equipo.');
202
+ console.log('[COLLAB] Descargando memoria del equipo...');
203
+
204
+ await syncDown(projectRoot);
205
+
206
+ console.log('[COLLAB] ✅ Listo. Ya tienes toda la memoria del equipo.\n');
207
+ }
208
+
209
+ // ─── INVITE ───────────────────────────────────────────────────────────────────
210
+
211
+ async function collabInvite(projectRoot) {
212
+ const config = loadConfig(projectRoot);
213
+ if (!config?.enabled) {
214
+ console.log('\n[COLLAB] Modo colaborativo no activado. Correr: akdd collab init\n');
215
+ return;
216
+ }
217
+
218
+ const projectId = config.project_id || getProjectId(projectRoot);
219
+ console.log('\n[COLLAB] Generando código de invitación...');
220
+
221
+ let result;
222
+ try {
223
+ const response = await fetch(`${PROVISIONER_URL}/invite`, {
224
+ method: 'POST',
225
+ headers: { 'Content-Type': 'application/json' },
226
+ body: JSON.stringify({ projectId }),
227
+ });
228
+ result = await response.json();
229
+ } catch (e) {
230
+ console.error('[COLLAB] Error:', e.message);
231
+ return;
232
+ }
233
+
234
+ if (!result.ok) {
235
+ console.error('[COLLAB] Error generando código:', result.error);
236
+ return;
237
+ }
238
+
239
+ console.log('\n' + '═'.repeat(50));
240
+ console.log(' 🔑 Código de invitación Agentic KDD');
241
+ console.log('═'.repeat(50));
242
+ console.log(`\n Código: ${result.code}`);
243
+ console.log(` Expira: ${result.expires_in}`);
244
+ console.log(` Un solo uso — expira al usarse o en 24h`);
245
+ console.log('\n Comparte este código por Slack/WhatsApp.');
246
+ console.log(' El miembro del equipo corre:');
247
+ console.log(`\n akdd collab join ${result.code}\n`);
248
+ console.log('═'.repeat(50) + '\n');
249
+ }
250
+
251
+ // ─── SYNC UP (local → Turso) ──────────────────────────────────────────────────
252
+
253
+ async function syncUp(projectRoot) {
254
+ const config = loadConfig(projectRoot);
255
+ if (!config?.enabled) {
256
+ console.log('[COLLAB] Modo colaborativo no activado. Correr: akdd collab init');
257
+ return { synced: false };
258
+ }
259
+
260
+ let client;
261
+ try {
262
+ const { createClient } = require('@libsql/client');
263
+ client = createClient({ url: config.url, authToken: config.token });
264
+ } catch {
265
+ console.log('[COLLAB] @libsql/client no instalado. Correr: npm install @libsql/client');
266
+ return { synced: false };
267
+ }
268
+
269
+ const localDB = openLocalDB(projectRoot);
270
+ const lastSync = config.last_sync || '1970-01-01T00:00:00.000Z';
271
+ let totalRows = 0;
272
+
273
+ // Schema push — crear tablas en remoto si no existen
274
+ await pushSchema(client, localDB);
275
+
276
+ for (const table of SYNC_TABLES) {
277
+ try {
278
+ // Solo filas nuevas/modificadas desde el último sync
279
+ const dateField = getDateField(table);
280
+ const rows = localDB.prepare(
281
+ `SELECT * FROM ${table} WHERE ${dateField} > ? LIMIT 500`
282
+ ).all(lastSync);
283
+
284
+ if (rows.length === 0) continue;
285
+
286
+ console.log(`[COLLAB] ${table}: ${rows.length} filas a sincronizar`);
287
+
288
+ for (const row of rows) {
289
+ const keys = Object.keys(row);
290
+ const vals = Object.values(row);
291
+ const placeholders = keys.map(() => '?').join(', ');
292
+ try {
293
+ await client.execute({
294
+ sql: `INSERT OR REPLACE INTO ${table} (${keys.join(', ')}) VALUES (${placeholders})`,
295
+ args: vals,
296
+ });
297
+ totalRows++;
298
+ } catch {}
299
+ }
300
+ } catch {}
301
+ }
302
+
303
+ // Actualizar last_sync
304
+ config.last_sync = new Date().toISOString();
305
+ saveConfig(projectRoot, config);
306
+
307
+ console.log(`[COLLAB] ✅ Sync up: ${totalRows} filas enviadas.`);
308
+ return { synced: true, rows: totalRows };
309
+ }
310
+
311
+ // ─── SYNC DOWN (Turso → local) ────────────────────────────────────────────────
312
+
313
+ async function syncDown(projectRoot) {
314
+ const config = loadConfig(projectRoot);
315
+ if (!config?.enabled) return { synced: false };
316
+
317
+ let client;
318
+ try {
319
+ const { createClient } = require('@libsql/client');
320
+ client = createClient({ url: config.url, authToken: config.token });
321
+ } catch {
322
+ console.log('[COLLAB] @libsql/client no instalado. Correr: npm install @libsql/client');
323
+ return { synced: false };
324
+ }
325
+
326
+ const localDB = openLocalDB(projectRoot);
327
+ let totalRows = 0;
328
+
329
+ for (const table of SYNC_TABLES) {
330
+ try {
331
+ const dateField = getDateField(table);
332
+ const lastSync = config.last_sync || '1970-01-01T00:00:00.000Z';
333
+
334
+ const rs = await client.execute(
335
+ `SELECT * FROM ${table} WHERE ${dateField} > ? LIMIT 1000`,
336
+ [lastSync]
337
+ );
338
+
339
+ if (!rs.rows?.length) continue;
340
+
341
+ console.log(`[COLLAB] ${table}: recibiendo ${rs.rows.length} filas`);
342
+
343
+ for (const row of rs.rows) {
344
+ const keys = Object.keys(row);
345
+ const vals = keys.map(k => row[k]);
346
+ const placeholders = keys.map(() => '?').join(', ');
347
+
348
+ // Estrategia por tabla:
349
+ // episodios → INSERT OR IGNORE (son aditivos, no sobrescribir locales)
350
+ // resto → INSERT OR REPLACE (toma la versión más reciente)
351
+ const strategy = table === 'episodios' ? 'OR IGNORE' : 'OR REPLACE';
352
+
353
+ try {
354
+ localDB.prepare(
355
+ `INSERT ${strategy} INTO ${table} (${keys.join(', ')}) VALUES (${placeholders})`
356
+ ).run(vals);
357
+ totalRows++;
358
+ } catch {}
359
+ }
360
+ } catch {}
361
+ }
362
+
363
+ // Actualizar last_sync
364
+ config.last_sync = new Date().toISOString();
365
+ saveConfig(projectRoot, config);
366
+
367
+ console.log(`[COLLAB] ✅ Sync down: ${totalRows} filas recibidas.`);
368
+ return { synced: true, rows: totalRows };
369
+ }
370
+
371
+ // ─── SCHEMA PUSH ──────────────────────────────────────────────────────────────
372
+ // Crea las tablas en la DB remota si no existen (primera vez)
373
+
374
+ async function pushSchema(client, localDB) {
375
+ const tables = localDB.prepare(
376
+ "SELECT name, sql FROM sqlite_master WHERE type='table' AND sql IS NOT NULL"
377
+ ).all();
378
+
379
+ for (const table of tables) {
380
+ if (!SYNC_TABLES.includes(table.name)) continue;
381
+ try {
382
+ await client.execute(
383
+ table.sql.replace('CREATE TABLE', 'CREATE TABLE IF NOT EXISTS')
384
+ );
385
+ } catch {}
386
+ }
387
+ }
388
+
389
+ // ─── STATUS ───────────────────────────────────────────────────────────────────
390
+
391
+ async function status(projectRoot) {
392
+ const config = loadConfig(projectRoot);
393
+
394
+ console.log('\n[COLLAB] Estado del modo colaborativo:\n');
395
+
396
+ if (!config?.enabled) {
397
+ console.log(' ❌ No activado.');
398
+ console.log(' Correr: akdd collab init\n');
399
+ return;
400
+ }
401
+
402
+ console.log(` ✅ Activado`);
403
+ console.log(` DB: ${config.db || config.url}`);
404
+ console.log(` Miembro: ${config.member_id}`);
405
+ console.log(` Último sync: ${config.last_sync || 'nunca'}`);
406
+ console.log(` Auto-sync: ${config.sync_on_cycle_end ? 'sí (al final de cada aa:)' : 'no'}`);
407
+
408
+ // Test de conexión
409
+ try {
410
+ const { createClient } = require('@libsql/client');
411
+ const client = createClient({ url: config.url, authToken: config.token });
412
+ await client.execute('SELECT 1');
413
+ console.log(' Conexión: ✅ OK\n');
414
+ } catch {
415
+ console.log(' Conexión: ❌ Error — verificar credenciales\n');
416
+ }
417
+ }
418
+
419
+ // ─── HELPER ───────────────────────────────────────────────────────────────────
420
+
421
+ function getDateField(table) {
422
+ const fields = {
423
+ nodos: 'fecha_update',
424
+ episodios: 'fecha',
425
+ relaciones_semanticas: 'valid_at',
426
+ knowledge_docs: 'last_indexed',
427
+ ast_symbols: 'last_indexed',
428
+ ast_edges: 'last_indexed',
429
+ };
430
+ return fields[table] || 'rowid';
431
+ }
432
+
433
+ // ─── CLI ──────────────────────────────────────────────────────────────────────
434
+
435
+ if (require.main === module) {
436
+ const [,, cmd, arg1, arg2] = process.argv;
437
+ const projectRoot = process.cwd();
438
+
439
+ switch (cmd) {
440
+ case 'init':
441
+ collabInit(projectRoot).catch(console.error);
442
+ break;
443
+ case 'invite':
444
+ collabInvite(projectRoot).catch(console.error);
445
+ break;
446
+ case 'join':
447
+ if (!arg1) {
448
+ console.error('Uso: collab-manager.cjs join <código>');
449
+ console.error(' o: collab-manager.cjs join <url> <token>');
450
+ process.exit(1);
451
+ }
452
+ collabJoin(projectRoot, arg1, arg2).catch(console.error);
453
+ break;
454
+ case 'push':
455
+ syncUp(projectRoot).then(r => {
456
+ process.exit(r.synced ? 0 : 1);
457
+ }).catch(console.error);
458
+ break;
459
+ case 'pull':
460
+ syncDown(projectRoot).then(r => {
461
+ process.exit(r.synced ? 0 : 1);
462
+ }).catch(console.error);
463
+ break;
464
+ case 'status':
465
+ status(projectRoot).catch(console.error);
466
+ break;
467
+ default:
468
+ console.log('Uso: node collab-manager.cjs [init | join <url> <token> | push | pull | status]');
469
+ }
470
+ }
471
+
472
+ module.exports = {
473
+ collabInit,
474
+ collabJoin,
475
+ collabInvite,
476
+ syncUp,
477
+ syncDown,
478
+ status,
479
+ loadConfig,
480
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-kdd",
3
- "version": "3.0.2",
3
+ "version": "3.0.4",
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": {
@@ -44,7 +44,8 @@
44
44
  "chalk": "^4.1.2",
45
45
  "ora": "^5.4.1",
46
46
  "inquirer": "^8.2.6",
47
- "sql.js": "^1.10.3"
47
+ "sql.js": "^1.10.3",
48
+ "@libsql/client": "^0.14.0"
48
49
  },
49
50
  "optionalDependencies": {
50
51
  "better-sqlite3": "^9.4.3"