monomind 2.7.4 → 2.7.5

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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/packages/@monomind/cli/bin/cli.js +17 -1
  3. package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.d.ts +3 -0
  4. package/packages/@monomind/cli/dist/src/commands/doctor-project-checks.js +81 -8
  5. package/packages/@monomind/cli/dist/src/commands/doctor.js +16 -2
  6. package/packages/@monomind/cli/dist/src/commands/memory-crud.js +15 -5
  7. package/packages/@monomind/cli/dist/src/commands/security-misc.js +7 -2
  8. package/packages/@monomind/cli/dist/src/init/helpers-generator.js +5 -0
  9. package/packages/@monomind/cli/dist/src/mcp-client.js +0 -2
  10. package/packages/@monomind/cli/dist/src/mcp-tools/agent-tools.js +1 -1
  11. package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.d.ts +2 -0
  12. package/packages/@monomind/cli/dist/src/mcp-tools/embeddings-tools.js +11 -1
  13. package/packages/@monomind/cli/dist/src/mcp-tools/guidance-tools.js +17 -17
  14. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.d.ts +2 -0
  15. package/packages/@monomind/cli/dist/src/mcp-tools/hive-mind-tools.js +14 -2
  16. package/packages/@monomind/cli/dist/src/mcp-tools/index.d.ts +0 -1
  17. package/packages/@monomind/cli/dist/src/mcp-tools/index.js +0 -1
  18. package/packages/@monomind/cli/dist/src/mcp-tools/monograph-tools.js +1 -2
  19. package/packages/@monomind/cli/dist/src/mcp-tools/neural-tools.js +1 -1
  20. package/packages/@monomind/cli/dist/src/mcp-tools/swarm-tools.js +2 -2
  21. package/packages/@monomind/cli/dist/src/mcp-tools/transfer-tools.d.ts +2 -0
  22. package/packages/@monomind/cli/dist/src/mcp-tools/transfer-tools.js +11 -1
  23. package/packages/@monomind/cli/dist/src/ui/dashboard.html +44 -618
  24. package/packages/@monomind/cli/dist/src/ui/server.mjs +0 -275
  25. package/packages/@monomind/cli/dist/src/utils/cosine-similarity.d.ts +2 -2
  26. package/packages/@monomind/cli/dist/src/utils/cosine-similarity.js +2 -2
  27. package/packages/@monomind/cli/package.json +1 -1
  28. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/causal-infer.d.ts +0 -15
  29. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/causal-infer.js +0 -333
  30. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/coherence-check.d.ts +0 -15
  31. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/coherence-check.js +0 -188
  32. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/consensus-verify.d.ts +0 -14
  33. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/consensus-verify.js +0 -305
  34. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/memory-gate.d.ts +0 -16
  35. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/memory-gate.js +0 -279
  36. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/quantum-topology.d.ts +0 -15
  37. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/quantum-topology.js +0 -400
  38. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/spectral-analyze.d.ts +0 -14
  39. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/spectral-analyze.js +0 -312
  40. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.d.ts +0 -430
  41. package/packages/@monomind/cli/dist/src/mcp-tools/coherence/types.js +0 -144
  42. package/packages/@monomind/cli/dist/src/mcp-tools/coherence-tools.d.ts +0 -9
  43. package/packages/@monomind/cli/dist/src/mcp-tools/coherence-tools.js +0 -25
  44. package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.d.ts +0 -13
  45. package/packages/@monomind/cli/dist/src/mcp-tools/daa-tools.js +0 -553
@@ -1874,22 +1874,6 @@ export async function startServer({ port = 4242, projectDir, openBrowser = true
1874
1874
  return;
1875
1875
  }
1876
1876
 
1877
- // GET /api/adr-file?file=<name>&dir=<dir> — read a single ADR markdown file
1878
- if (req.method === 'GET' && url === '/api/adr-file') {
1879
- try {
1880
- const qs = new URL(req.url, 'http://localhost').searchParams;
1881
- const dir = qs.get('dir') || projectDir || process.cwd();
1882
- const fname = (qs.get('file') || '').replace(/[/\\]/g, '');
1883
- if (!fname || !fname.endsWith('.md')) { res.writeHead(400); res.end('{"error":"invalid file"}'); return; }
1884
- const fpath = path.join(path.resolve(dir), 'docs', 'adrs', fname);
1885
- if (!fs.existsSync(fpath)) { res.writeHead(404); res.end('{"error":"not found"}'); return; }
1886
- const content = fs.readFileSync(fpath, 'utf8');
1887
- res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
1888
- res.end(JSON.stringify({ file: fname, content }));
1889
- } catch (err) { res.writeHead(500); res.end(JSON.stringify({ error: err.message })); }
1890
- return;
1891
- }
1892
-
1893
1877
  // ------------------------------------------------------- GET /api/memory-files
1894
1878
  if (req.method === 'GET' && url === '/api/memory-files') {
1895
1879
  try {
@@ -6331,265 +6315,6 @@ new Sigma(g,document.getElementById('g'),{renderEdgeLabels:false,labelColor:{col
6331
6315
  return;
6332
6316
  }
6333
6317
 
6334
- // GET /api/second-brain-metrics — aggregated injection analytics from second-brain.jsonl
6335
- if (req.method === 'GET' && url === '/api/second-brain-metrics') {
6336
- try {
6337
- const metricsFile = path.join(projectDir || process.cwd(), '.monomind', 'metrics', 'second-brain.jsonl');
6338
- if (!fs.existsSync(metricsFile)) {
6339
- res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
6340
- res.end(JSON.stringify({ total: 0, injected: 0, hitRate: 0, methods: {}, avgScore: 0, sparkline: [], scores: [] }));
6341
- return;
6342
- }
6343
- const raw = fs.readFileSync(metricsFile, 'utf8');
6344
- const entries = raw.split('\n').filter(Boolean).map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
6345
- const total = entries.length;
6346
- const injectedCount = entries.filter(e => e.injected).length;
6347
- const hitRate = total > 0 ? injectedCount / total : 0;
6348
- // Method distribution
6349
- const methods = {};
6350
- for (const e of entries) { const m = e.method || 'unknown'; methods[m] = (methods[m] || 0) + 1; }
6351
- // Average top score (only for entries with hits)
6352
- const withScores = entries.filter(e => typeof e.topScore === 'number' && e.topScore !== null);
6353
- const avgScore = withScores.length > 0 ? withScores.reduce((s, e) => s + e.topScore, 0) / withScores.length : 0;
6354
- // Score distribution buckets
6355
- const scoreBuckets = [0, 0, 0, 0, 0]; // <0.3, 0.3-0.5, 0.5-0.7, 0.7-0.9, 0.9+
6356
- for (const e of withScores) {
6357
- const s = e.topScore;
6358
- if (s < 0.3) scoreBuckets[0]++;
6359
- else if (s < 0.5) scoreBuckets[1]++;
6360
- else if (s < 0.7) scoreBuckets[2]++;
6361
- else if (s < 0.9) scoreBuckets[3]++;
6362
- else scoreBuckets[4]++;
6363
- }
6364
- // Sparkline: group by hour for last 24h, then by day for older
6365
- const now = Date.now();
6366
- const h24 = now - 86400000;
6367
- const sparkline = [];
6368
- // Last 24 hours in 2h buckets
6369
- for (let t = h24; t < now; t += 7200000) {
6370
- const bucket = entries.filter(e => e.ts >= t && e.ts < t + 7200000);
6371
- const inj = bucket.filter(e => e.injected).length;
6372
- sparkline.push({ t, total: bucket.length, injected: inj });
6373
- }
6374
- // Top sources from recent entries (last 200)
6375
- const recentSlice = entries.slice(-200);
6376
- const misses = recentSlice.filter(e => !e.injected).length;
6377
- // Route overrides (misroute counts)
6378
- let routeOverrides = {};
6379
- try {
6380
- const overridesFile = path.join(projectDir || process.cwd(), '.monomind', 'metrics', 'route-overrides.json');
6381
- if (fs.existsSync(overridesFile)) routeOverrides = JSON.parse(fs.readFileSync(overridesFile, 'utf8'));
6382
- } catch {}
6383
- res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
6384
- res.end(JSON.stringify({ total, injected: injectedCount, hitRate: +hitRate.toFixed(3), methods, avgScore: +avgScore.toFixed(3), sparkline, scoreBuckets, recentMisses: misses, recentTotal: recentSlice.length, routeOverrides }));
6385
- } catch (err) {
6386
- res.writeHead(500, { 'Content-Type': 'application/json' });
6387
- res.end(JSON.stringify({ error: err.message }));
6388
- }
6389
- return;
6390
- }
6391
-
6392
- // POST /api/second-brain-route — show query router decision for a given query
6393
- if (req.method === 'POST' && url === '/api/second-brain-route') {
6394
- let body = '';
6395
- req.on('data', c => { body += c; if (body.length > 65536) { req.destroy(); return; } });
6396
- req.on('end', async () => {
6397
- try {
6398
- const { query } = JSON.parse(body || '{}');
6399
- if (!query) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end('{"error":"query required"}'); return; }
6400
- const routerMod = await import('../memory/query-router.js');
6401
- const route = routerMod.routeQuery(String(query).slice(0, 2000));
6402
- const overrides = routerMod.getRouteOverrides(true);
6403
- res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
6404
- res.end(JSON.stringify({ route, overrides }));
6405
- } catch (err) {
6406
- res.writeHead(500, { 'Content-Type': 'application/json' });
6407
- res.end(JSON.stringify({ error: err.message }));
6408
- }
6409
- });
6410
- return;
6411
- }
6412
-
6413
- // GET/PUT /api/second-brain-config — read/write Second Brain config (.monomind/second-brain.json)
6414
- if ((req.method === 'GET' || req.method === 'PUT') && url === '/api/second-brain-config') {
6415
- const configFile = path.join(projectDir || process.cwd(), '.monomind', 'second-brain.json');
6416
- if (req.method === 'GET') {
6417
- try {
6418
- let config = { relevanceFloor: 0.35, injectionLimit: 5 };
6419
- try { config = { ...config, ...JSON.parse(fs.readFileSync(configFile, 'utf8')) }; } catch {}
6420
- // Auto-tune suggestion: analyze telemetry to suggest optimal floor
6421
- let suggestion = null;
6422
- try {
6423
- const metricsFile = path.join(projectDir || process.cwd(), '.monomind', 'metrics', 'second-brain.jsonl');
6424
- if (fs.existsSync(metricsFile)) {
6425
- const entries = fs.readFileSync(metricsFile, 'utf8').split('\n').filter(Boolean)
6426
- .map(l => { try { return JSON.parse(l); } catch { return null; } }).filter(Boolean);
6427
- const withScores = entries.filter(e => typeof e.topScore === 'number' && e.topScore !== null);
6428
- if (withScores.length >= 20) {
6429
- const scores = withScores.map(e => e.topScore).sort((a, b) => a - b);
6430
- const p25 = scores[Math.floor(scores.length * 0.25)];
6431
- const hitRate = entries.filter(e => e.injected).length / entries.length;
6432
- // If hit rate is very low (<30%), suggest lowering floor
6433
- // If hit rate is very high (>80%) with low avg scores, suggest raising
6434
- if (hitRate < 0.3 && p25 > 0.2) suggestion = { floor: +(p25 - 0.05).toFixed(2), reason: 'Low hit rate (' + (hitRate * 100).toFixed(0) + '%) — lowering floor may surface more relevant results' };
6435
- else if (hitRate > 0.8 && scores[Math.floor(scores.length * 0.1)] < 0.35) suggestion = { floor: 0.4, reason: 'High hit rate with low-quality matches — raising floor filters noise' };
6436
- }
6437
- }
6438
- } catch {}
6439
- res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
6440
- res.end(JSON.stringify({ config, suggestion }));
6441
- } catch (err) {
6442
- res.writeHead(500, { 'Content-Type': 'application/json' });
6443
- res.end(JSON.stringify({ error: err.message }));
6444
- }
6445
- return;
6446
- }
6447
- if (req.method === 'PUT') {
6448
- let body = '';
6449
- req.on('data', c => { body += c; if (body.length > 4096) { req.destroy(); return; } });
6450
- req.on('end', () => {
6451
- try {
6452
- const raw = JSON.parse(body || '{}');
6453
- const updates = {};
6454
- if (typeof raw.relevanceFloor === 'number' && raw.relevanceFloor >= 0 && raw.relevanceFloor <= 1) updates.relevanceFloor = raw.relevanceFloor;
6455
- if (typeof raw.injectionLimit === 'number' && raw.injectionLimit >= 1 && raw.injectionLimit <= 50) updates.injectionLimit = Math.floor(raw.injectionLimit);
6456
- if (Object.keys(updates).length === 0) { res.writeHead(400, { 'Content-Type': 'application/json' }); res.end('{"error":"no valid fields to update"}'); return; }
6457
- let config = {};
6458
- try { config = JSON.parse(fs.readFileSync(configFile, 'utf8')); } catch {}
6459
- config = { ...config, ...updates };
6460
- fs.mkdirSync(path.dirname(configFile), { recursive: true });
6461
- fs.writeFileSync(configFile, JSON.stringify(config, null, 2));
6462
- res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
6463
- res.end(JSON.stringify({ ok: true, config }));
6464
- } catch (err) {
6465
- res.writeHead(500, { 'Content-Type': 'application/json' });
6466
- res.end(JSON.stringify({ error: err.message }));
6467
- }
6468
- });
6469
- return;
6470
- }
6471
- }
6472
-
6473
- // GET /api/second-brain-docs — document-level view from doc-metadata.jsonl
6474
- if (req.method === 'GET' && url === '/api/second-brain-docs') {
6475
- try {
6476
- const d = path.resolve(projectDir || process.cwd());
6477
- const metaFile = path.join(d, '.monomind', 'knowledge', 'doc-metadata.jsonl');
6478
- // Also check global brain
6479
- const globalDir = process.env.MONOMIND_GLOBAL_BRAIN_DIR || path.join(os.homedir(), '.monomind', 'global-brain');
6480
- const globalMetaFile = path.join(globalDir, '.monomind', 'knowledge', 'doc-metadata.jsonl');
6481
- const docs = [];
6482
- let totalSize = 0, totalChunks = 0;
6483
- for (const [file, scope] of [[metaFile, 'project'], [globalMetaFile, 'global']]) {
6484
- if (!fs.existsSync(file)) continue;
6485
- const latest = new Map();
6486
- for (const l of fs.readFileSync(file, 'utf8').split('\n')) {
6487
- if (!l.trim()) continue;
6488
- try {
6489
- const m = JSON.parse(l);
6490
- latest.set(m.filePath + ' ' + m.scope, m);
6491
- } catch {}
6492
- }
6493
- for (const m of latest.values()) {
6494
- if (m.chunkCount < 0) continue;
6495
- const exists = fs.existsSync(m.filePath);
6496
- docs.push({ ...m, brainScope: scope, exists, name: path.basename(m.filePath) });
6497
- totalSize += m.size || 0;
6498
- totalChunks += m.chunkCount || 0;
6499
- }
6500
- }
6501
- docs.sort((a, b) => (b.indexedAt || '').localeCompare(a.indexedAt || ''));
6502
- const globalCount = docs.filter(d => d.brainScope === 'global').length;
6503
- res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
6504
- res.end(JSON.stringify({ docs, totalDocs: docs.length, totalSize, totalChunks, globalCount, projectCount: docs.length - globalCount }));
6505
- } catch (err) {
6506
- res.writeHead(500, { 'Content-Type': 'application/json' });
6507
- res.end(JSON.stringify({ error: err.message }));
6508
- }
6509
- return;
6510
- }
6511
-
6512
- // GET /api/second-brain-ingest-stream — SSE stream tailing doc-metadata.jsonl for live ingest events
6513
- if (req.method === 'GET' && url === '/api/second-brain-ingest-stream') {
6514
- res.writeHead(200, {
6515
- 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', 'Connection': 'keep-alive',
6516
- ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {})
6517
- });
6518
- res.write('data: {"type":"connected"}\n\n');
6519
- const metaFile = path.join(projectDir || process.cwd(), '.monomind', 'knowledge', 'doc-metadata.jsonl');
6520
- let lastSize = 0;
6521
- try { lastSize = fs.statSync(metaFile).size; } catch {}
6522
- function _sseTailFile(filePath, lastSizeRef, eventType, transform) {
6523
- try {
6524
- const stat = fs.statSync(filePath);
6525
- if (stat.size < lastSizeRef.v) { lastSizeRef.v = 0; }
6526
- if (stat.size > lastSizeRef.v) {
6527
- let fd;
6528
- try {
6529
- fd = fs.openSync(filePath, 'r');
6530
- const buf = Buffer.alloc(stat.size - lastSizeRef.v);
6531
- fs.readSync(fd, buf, 0, buf.length, lastSizeRef.v);
6532
- lastSizeRef.v = stat.size;
6533
- const lines = buf.toString('utf8').split('\n').filter(Boolean);
6534
- for (const l of lines) {
6535
- try { const parsed = JSON.parse(l); res.write('data: ' + JSON.stringify(transform(parsed)) + '\n\n'); } catch {}
6536
- }
6537
- } finally { if (fd !== undefined) fs.closeSync(fd); }
6538
- }
6539
- } catch {}
6540
- }
6541
- const metaSize = { v: lastSize };
6542
- const interval = setInterval(() => _sseTailFile(metaFile, metaSize, 'ingest', m => ({ type: 'ingest', doc: { ...m, name: path.basename(m.filePath || '') } })), 2000);
6543
- const metricsFile = path.join(projectDir || process.cwd(), '.monomind', 'metrics', 'second-brain.jsonl');
6544
- const metricsSize = { v: 0 };
6545
- try { metricsSize.v = fs.statSync(metricsFile).size; } catch {}
6546
- const metricsInterval = setInterval(() => _sseTailFile(metricsFile, metricsSize, 'injection', e => ({ type: 'injection', event: e })), 3000);
6547
- req.on('close', () => { clearInterval(interval); clearInterval(metricsInterval); });
6548
- return;
6549
- }
6550
-
6551
- // GET /api/second-brain-kg — knowledge graph triplets + stats for the KG explorer
6552
- if (req.method === 'GET' && url === '/api/second-brain-kg') {
6553
- try {
6554
- const qs = new URL(req.url, 'http://localhost').searchParams;
6555
- const query = (qs.get('q') || '').slice(0, 200);
6556
- const limit = Math.min(parseInt(qs.get('limit') || '50', 10), 200);
6557
- const monoDir = path.join(projectDir || process.cwd(), '.monomind');
6558
- const dbPath = path.join(monoDir, 'memory');
6559
- let stats = { nodes: 0, edges: 0 }, triplets = [], glossary = [];
6560
- try {
6561
- const kg = await import('../memory/memory-kg.js');
6562
- stats = await kg.kgStats({ dbPath });
6563
- glossary = await kg.kgGlossary({ dbPath, limit: 30 });
6564
- if (query) {
6565
- const searchResult = await kg.kgSearch({ query, limit, dbPath });
6566
- triplets = searchResult.triplets || searchResult || [];
6567
- } else {
6568
- const searchResult = await kg.kgSearch({ query: '*', limit, dbPath }).catch(() => null);
6569
- triplets = searchResult ? (searchResult.triplets || searchResult) : [];
6570
- }
6571
- } catch {}
6572
- // Also check palace kg.json as fallback
6573
- if (triplets.length === 0) {
6574
- try {
6575
- const palaceDir = path.join(monoDir, 'palace');
6576
- const kgData = JSON.parse(fs.readFileSync(path.join(palaceDir, 'kg.json'), 'utf8'));
6577
- if (Array.isArray(kgData)) {
6578
- triplets = kgData.slice(0, limit);
6579
- stats.nodes = new Set(kgData.flatMap(t => [t.subject || t.s, t.object || t.o])).size;
6580
- stats.edges = kgData.length;
6581
- }
6582
- } catch {}
6583
- }
6584
- res.writeHead(200, { 'Content-Type': 'application/json', ...(corsOrigin ? { 'Access-Control-Allow-Origin': corsOrigin } : {}) });
6585
- res.end(JSON.stringify({ stats, triplets, glossary }));
6586
- } catch (err) {
6587
- res.writeHead(500, { 'Content-Type': 'application/json' });
6588
- res.end(JSON.stringify({ error: err.message }));
6589
- }
6590
- return;
6591
- }
6592
-
6593
6318
  // GET /api/mastermind/metrics — aggregate system metrics from token-summary and swarm-activity
6594
6319
  if (req.method === 'GET' && url === '/api/mastermind/metrics') {
6595
6320
  try {
@@ -5,8 +5,8 @@
5
5
  * - memory/intelligence.ts (ReasoningBank.cosineSim)
6
6
  * - mcp-tools/neural-tools.ts (cosineSimilarity)
7
7
  * - mcp-tools/embeddings-tools.ts (cosineSimilarity)
8
- * - mcp-tools/coherence/types.ts (cosineSimilarity)
9
- * - mcp-tools/coherence/coherence-check.ts (cosineSimilarity)
8
+ * - mcp-tools/coherence/types.ts + coherence-check.ts (both since deleted
9
+ * along with the rest of the uncalled coherence_* family)
10
10
  *
11
11
  * This is the single canonical implementation. Accepts number[] or
12
12
  * Float32Array. Handles dimension mismatches gracefully (uses the shorter
@@ -5,8 +5,8 @@
5
5
  * - memory/intelligence.ts (ReasoningBank.cosineSim)
6
6
  * - mcp-tools/neural-tools.ts (cosineSimilarity)
7
7
  * - mcp-tools/embeddings-tools.ts (cosineSimilarity)
8
- * - mcp-tools/coherence/types.ts (cosineSimilarity)
9
- * - mcp-tools/coherence/coherence-check.ts (cosineSimilarity)
8
+ * - mcp-tools/coherence/types.ts + coherence-check.ts (both since deleted
9
+ * along with the rest of the uncalled coherence_* family)
10
10
  *
11
11
  * This is the single canonical implementation. Accepts number[] or
12
12
  * Float32Array. Handles dimension mismatches gracefully (uses the shorter
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@monoes/monomindcli",
3
- "version": "2.7.4",
3
+ "version": "2.7.5",
4
4
  "type": "module",
5
5
  "description": "CLI engine for Monomind — an open-source MCP server that extends Claude Code with a codebase knowledge graph (tree-sitter + SQLite), persistent memory, multi-agent task coordination, and session hooks. MIT licensed, fully local.",
6
6
  "main": "dist/src/index.js",
@@ -1,15 +0,0 @@
1
- /**
2
- * Causal Graph Analysis Tool - pr_causal_infer
3
- *
4
- * Analyzes causal graph structure to assess identifiability.
5
- * Identifies confounders, finds backdoor paths, and determines
6
- * whether a causal effect can be identified from the graph.
7
- * Does NOT estimate causal effect magnitude (that requires data).
8
- */
9
- import type { MCPTool } from './types.js';
10
- /**
11
- * pr_causal_infer MCP Tool Definition
12
- */
13
- export declare const causalInferTool: MCPTool;
14
- export default causalInferTool;
15
- //# sourceMappingURL=causal-infer.d.ts.map
@@ -1,333 +0,0 @@
1
- /**
2
- * Causal Graph Analysis Tool - pr_causal_infer
3
- *
4
- * Analyzes causal graph structure to assess identifiability.
5
- * Identifies confounders, finds backdoor paths, and determines
6
- * whether a causal effect can be identified from the graph.
7
- * Does NOT estimate causal effect magnitude (that requires data).
8
- */
9
- import { CausalInputSchema, successResult, errorResult, } from './types.js';
10
- // Default logger
11
- const defaultLogger = {
12
- debug: (msg, meta) => console.debug(`[pr_causal_infer] ${msg}`, meta),
13
- info: (msg, meta) => console.info(`[pr_causal_infer] ${msg}`, meta),
14
- warn: (msg, meta) => console.warn(`[pr_causal_infer] ${msg}`, meta),
15
- error: (msg, meta) => console.error(`[pr_causal_infer] ${msg}`, meta),
16
- };
17
- function buildGraphStructure(graph) {
18
- const nodes = new Map();
19
- // Initialize nodes
20
- for (const nodeId of graph.nodes) {
21
- nodes.set(nodeId, {
22
- id: nodeId,
23
- parents: new Set(),
24
- children: new Set(),
25
- });
26
- }
27
- // Add edges
28
- for (const [from, to] of graph.edges) {
29
- const fromNode = nodes.get(from);
30
- const toNode = nodes.get(to);
31
- if (fromNode && toNode) {
32
- fromNode.children.add(to);
33
- toNode.parents.add(from);
34
- }
35
- }
36
- return nodes;
37
- }
38
- /**
39
- * Find all ancestors of a node (including the node itself)
40
- */
41
- function findAncestors(nodeId, graph) {
42
- const ancestors = new Set();
43
- const queue = [nodeId];
44
- while (queue.length > 0) {
45
- const current = queue.shift();
46
- if (ancestors.has(current))
47
- continue;
48
- ancestors.add(current);
49
- const node = graph.get(current);
50
- if (node) {
51
- for (const parent of node.parents) {
52
- queue.push(parent);
53
- }
54
- }
55
- }
56
- return ancestors;
57
- }
58
- /**
59
- * Find all descendants of a node (including the node itself)
60
- */
61
- function findDescendants(nodeId, graph) {
62
- const descendants = new Set();
63
- const queue = [nodeId];
64
- while (queue.length > 0) {
65
- const current = queue.shift();
66
- if (descendants.has(current))
67
- continue;
68
- descendants.add(current);
69
- const node = graph.get(current);
70
- if (node) {
71
- for (const child of node.children) {
72
- queue.push(child);
73
- }
74
- }
75
- }
76
- return descendants;
77
- }
78
- /**
79
- * Identify confounders (common causes of treatment and outcome)
80
- */
81
- function identifyConfounders(treatment, outcome, graph) {
82
- const treatmentAncestors = findAncestors(treatment, graph);
83
- const outcomeAncestors = findAncestors(outcome, graph);
84
- // Confounders are ancestors of both treatment and outcome
85
- // that are not the treatment itself
86
- const confounders = [];
87
- for (const ancestor of treatmentAncestors) {
88
- if (ancestor !== treatment && ancestor !== outcome && outcomeAncestors.has(ancestor)) {
89
- confounders.push(ancestor);
90
- }
91
- }
92
- return confounders;
93
- }
94
- /**
95
- * Find backdoor paths from treatment to outcome
96
- * A backdoor path is a path that goes into the treatment node
97
- */
98
- function findBackdoorPaths(treatment, outcome, graph, maxPaths = 10) {
99
- const backdoorPaths = [];
100
- const treatmentNode = graph.get(treatment);
101
- if (!treatmentNode)
102
- return [];
103
- // Start from each parent of treatment (backdoor)
104
- for (const parent of treatmentNode.parents) {
105
- const paths = findAllPaths(parent, outcome, graph, new Set([treatment]), maxPaths - backdoorPaths.length);
106
- for (const path of paths) {
107
- backdoorPaths.push([treatment, ...path]);
108
- if (backdoorPaths.length >= maxPaths)
109
- break;
110
- }
111
- if (backdoorPaths.length >= maxPaths)
112
- break;
113
- }
114
- return backdoorPaths;
115
- }
116
- /**
117
- * Find all paths from source to target (BFS with path tracking)
118
- */
119
- function findAllPaths(source, target, graph, exclude, maxPaths) {
120
- const paths = [];
121
- const queue = [{ node: source, path: [source] }];
122
- while (queue.length > 0 && paths.length < maxPaths) {
123
- const { node, path } = queue.shift();
124
- if (node === target) {
125
- paths.push(path);
126
- continue;
127
- }
128
- const currentNode = graph.get(node);
129
- if (!currentNode)
130
- continue;
131
- // Consider both parents and children for undirected path finding
132
- const neighbors = new Set([...currentNode.parents, ...currentNode.children]);
133
- for (const neighbor of neighbors) {
134
- if (!path.includes(neighbor) && !exclude.has(neighbor)) {
135
- queue.push({
136
- node: neighbor,
137
- path: [...path, neighbor],
138
- });
139
- }
140
- }
141
- }
142
- return paths;
143
- }
144
- /**
145
- * Check if intervention is valid (treatment must exist and have outgoing edges)
146
- */
147
- function validateIntervention(treatment, outcome, graph) {
148
- const treatmentNode = graph.get(treatment);
149
- const outcomeNode = graph.get(outcome);
150
- if (!treatmentNode || !outcomeNode) {
151
- return false;
152
- }
153
- // Check if there's any path from treatment to outcome
154
- const descendants = findDescendants(treatment, graph);
155
- return descendants.has(outcome);
156
- }
157
- /**
158
- * Assess identifiability of the causal effect based on graph structure.
159
- * Does NOT estimate the causal effect magnitude -- that requires
160
- * observational or experimental data which this tool does not have.
161
- * Returns an identifiability score reflecting how cleanly the effect
162
- * can be estimated if data were available (fewer confounders and
163
- * unblocked backdoor paths = higher identifiability).
164
- */
165
- function assessIdentifiability(confounders, backdoorPaths) {
166
- // Identifiability decreases with confounders and unblocked backdoor paths
167
- const confounderPenalty = Math.min(confounders.length * 0.1, 0.3);
168
- const pathPenalty = Math.min(backdoorPaths.length * 0.05, 0.2);
169
- const identifiability = Math.max(0.1, 1 - confounderPenalty - pathPenalty);
170
- return { identifiability };
171
- }
172
- /**
173
- * Get interpretation of causal graph analysis results
174
- */
175
- function getInterpretation(interventionValid, confounders, backdoorPaths, identifiability) {
176
- if (!interventionValid) {
177
- return 'Intervention is not valid - no causal path exists from treatment to outcome';
178
- }
179
- if (confounders.length === 0 && backdoorPaths.length === 0) {
180
- return 'Clean causal identification - no confounding detected. Effect is identifiable with data.';
181
- }
182
- if (identifiability > 0.8) {
183
- return 'Strong identifiability - confounders can be adjusted for with observational data';
184
- }
185
- if (identifiability > 0.5) {
186
- return `Moderate identifiability - ${confounders.length} confounder(s) require adjustment`;
187
- }
188
- return `Weak identifiability - multiple confounders (${confounders.length}) and backdoor paths (${backdoorPaths.length}) detected`;
189
- }
190
- /**
191
- * Handler for pr_causal_infer tool
192
- */
193
- async function handler(input, context) {
194
- const logger = context?.logger ?? defaultLogger;
195
- const startTime = performance.now();
196
- try {
197
- // Validate input
198
- const validationResult = CausalInputSchema.safeParse(input);
199
- if (!validationResult.success) {
200
- logger.error('Input validation failed', { error: validationResult.error.message });
201
- return errorResult(`Invalid input: ${validationResult.error.message}`);
202
- }
203
- const { graph, intervention, outcome } = validationResult.data;
204
- logger.debug('Processing causal inference', {
205
- nodes: graph.nodes.length,
206
- edges: graph.edges.length,
207
- intervention,
208
- outcome,
209
- });
210
- // Validate nodes exist
211
- if (!graph.nodes.includes(intervention)) {
212
- return errorResult(`Intervention variable "${intervention}" not found in graph nodes`);
213
- }
214
- if (!graph.nodes.includes(outcome)) {
215
- return errorResult(`Outcome variable "${outcome}" not found in graph nodes`);
216
- }
217
- const maxBackdoorPaths = context?.config?.causal?.maxBackdoorPaths ?? 10;
218
- let identifiability;
219
- let confounders;
220
- let backdoorPaths;
221
- let interventionValid;
222
- // Try to use WASM bridge if available
223
- if (context?.bridge?.initialized) {
224
- try {
225
- logger.debug('Using WASM bridge for causal inference');
226
- const result = await context.bridge.inferCausal(graph, intervention, outcome);
227
- confounders = result.confounders;
228
- backdoorPaths = result.backdoorPaths;
229
- interventionValid = result.interventionValid;
230
- identifiability = assessIdentifiability(confounders, backdoorPaths).identifiability;
231
- }
232
- catch (wasmError) {
233
- logger.warn('WASM bridge failed, falling back to JS implementation', {
234
- error: wasmError instanceof Error ? wasmError.message : String(wasmError),
235
- });
236
- // Use JavaScript implementation
237
- const graphStructure = buildGraphStructure(graph);
238
- interventionValid = validateIntervention(intervention, outcome, graphStructure);
239
- confounders = identifyConfounders(intervention, outcome, graphStructure);
240
- backdoorPaths = findBackdoorPaths(intervention, outcome, graphStructure, maxBackdoorPaths);
241
- identifiability = assessIdentifiability(confounders, backdoorPaths).identifiability;
242
- }
243
- }
244
- else {
245
- // Pure JavaScript fallback
246
- logger.debug('Using JavaScript fallback for causal inference');
247
- const graphStructure = buildGraphStructure(graph);
248
- interventionValid = validateIntervention(intervention, outcome, graphStructure);
249
- confounders = identifyConfounders(intervention, outcome, graphStructure);
250
- backdoorPaths = findBackdoorPaths(intervention, outcome, graphStructure, maxBackdoorPaths);
251
- identifiability = assessIdentifiability(confounders, backdoorPaths).identifiability;
252
- }
253
- const output = {
254
- identifiability,
255
- backdoorPaths: backdoorPaths.map(p => p.join(' -> ')),
256
- details: {
257
- confounders,
258
- interventionValid,
259
- interpretation: getInterpretation(interventionValid, confounders, backdoorPaths, identifiability),
260
- nodeCount: graph.nodes.length,
261
- edgeCount: graph.edges.length,
262
- },
263
- };
264
- const duration = performance.now() - startTime;
265
- logger.info('Causal graph analysis completed', {
266
- identifiability: identifiability.toFixed(4),
267
- confounders: confounders.length,
268
- backdoorPaths: backdoorPaths.length,
269
- interventionValid,
270
- durationMs: duration.toFixed(2),
271
- });
272
- return successResult(output);
273
- }
274
- catch (error) {
275
- const duration = performance.now() - startTime;
276
- logger.error('Causal inference failed', {
277
- error: error instanceof Error ? error.message : String(error),
278
- durationMs: duration.toFixed(2),
279
- });
280
- return errorResult(error instanceof Error ? error : new Error(String(error)));
281
- }
282
- }
283
- /**
284
- * pr_causal_infer MCP Tool Definition
285
- */
286
- export const causalInferTool = {
287
- name: 'pr_causal_infer',
288
- description: 'Analyze causal graph structure for identifiability. Identifies confounders, finds backdoor paths, and assesses whether a causal effect can be identified. Does not estimate effect magnitude (requires observational data).',
289
- category: 'causal',
290
- version: '0.1.3',
291
- tags: ['causal', 'do-calculus', 'confounders', 'backdoor-paths', 'ai-interpretability'],
292
- cacheable: true,
293
- cacheTTL: 60000,
294
- inputSchema: {
295
- type: 'object',
296
- properties: {
297
- graph: {
298
- type: 'object',
299
- properties: {
300
- nodes: {
301
- type: 'array',
302
- items: { type: 'string' },
303
- description: 'List of variable names',
304
- },
305
- edges: {
306
- type: 'array',
307
- items: {
308
- type: 'array',
309
- items: { type: 'string' },
310
- minItems: 2,
311
- maxItems: 2,
312
- },
313
- description: 'Directed edges as [from, to] pairs',
314
- },
315
- },
316
- required: ['nodes', 'edges'],
317
- description: 'Causal graph with nodes and directed edges',
318
- },
319
- intervention: {
320
- type: 'string',
321
- description: 'Treatment/intervention variable',
322
- },
323
- outcome: {
324
- type: 'string',
325
- description: 'Outcome variable to measure effect on',
326
- },
327
- },
328
- required: ['graph', 'intervention', 'outcome'],
329
- },
330
- handler,
331
- };
332
- export default causalInferTool;
333
- //# sourceMappingURL=causal-infer.js.map