@monoes/monomindcli 1.10.29 โ†’ 1.10.30

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 (80) hide show
  1. package/.claude/helpers/auto-memory-hook.mjs +39 -4
  2. package/.claude/helpers/handlers/edit-handler.cjs +145 -0
  3. package/.claude/helpers/handlers/route-handler.cjs +393 -0
  4. package/.claude/helpers/handlers/session-handler.cjs +167 -0
  5. package/.claude/helpers/handlers/session-restore-handler.cjs +343 -0
  6. package/.claude/helpers/handlers/task-handler.cjs +329 -0
  7. package/.claude/helpers/hook-handler.cjs +114 -2273
  8. package/.claude/helpers/intelligence.cjs +21 -2
  9. package/.claude/helpers/learning-service.mjs +166 -8
  10. package/.claude/helpers/memory-palace.cjs +72 -12
  11. package/.claude/helpers/router.cjs +79 -5
  12. package/.claude/helpers/statusline.cjs +193 -399
  13. package/.claude/helpers/utils/micro-agents.cjs +338 -0
  14. package/.claude/helpers/utils/monograph.cjs +349 -0
  15. package/.claude/helpers/utils/telemetry.cjs +144 -0
  16. package/.claude/skills/agent-browser-testing/SKILL.md +3 -2
  17. package/.claude/skills/monomind/browse-agentcore.md +116 -0
  18. package/.claude/skills/monomind/browse-electron.md +189 -0
  19. package/.claude/skills/monomind/browse-qa.md +229 -0
  20. package/.claude/skills/monomind/browse-references/authentication.md +162 -0
  21. package/.claude/skills/monomind/browse-references/trust-boundaries.md +41 -0
  22. package/.claude/skills/monomind/browse-references/video-recording.md +84 -0
  23. package/.claude/skills/monomind/browse-slack.md +189 -0
  24. package/.claude/skills/monomind/browse-vercel.md +240 -0
  25. package/.claude/skills/monomind/browse.md +724 -0
  26. package/dist/src/browser/actions.d.ts +13 -0
  27. package/dist/src/browser/actions.d.ts.map +1 -0
  28. package/dist/src/browser/actions.js +201 -0
  29. package/dist/src/browser/actions.js.map +1 -0
  30. package/dist/src/browser/browser.d.ts +14 -0
  31. package/dist/src/browser/browser.d.ts.map +1 -0
  32. package/dist/src/browser/browser.js +198 -0
  33. package/dist/src/browser/browser.js.map +1 -0
  34. package/dist/src/browser/cdp.d.ts +17 -0
  35. package/dist/src/browser/cdp.d.ts.map +1 -0
  36. package/dist/src/browser/cdp.js +106 -0
  37. package/dist/src/browser/cdp.js.map +1 -0
  38. package/dist/src/browser/index.d.ts +11 -0
  39. package/dist/src/browser/index.d.ts.map +1 -0
  40. package/dist/src/browser/index.js +11 -0
  41. package/dist/src/browser/index.js.map +1 -0
  42. package/dist/src/browser/network.d.ts +11 -0
  43. package/dist/src/browser/network.d.ts.map +1 -0
  44. package/dist/src/browser/network.js +81 -0
  45. package/dist/src/browser/network.js.map +1 -0
  46. package/dist/src/browser/screenshot.d.ts +15 -0
  47. package/dist/src/browser/screenshot.d.ts.map +1 -0
  48. package/dist/src/browser/screenshot.js +36 -0
  49. package/dist/src/browser/screenshot.js.map +1 -0
  50. package/dist/src/browser/session.d.ts +8 -0
  51. package/dist/src/browser/session.d.ts.map +1 -0
  52. package/dist/src/browser/session.js +50 -0
  53. package/dist/src/browser/session.js.map +1 -0
  54. package/dist/src/browser/snapshot.d.ts +12 -0
  55. package/dist/src/browser/snapshot.d.ts.map +1 -0
  56. package/dist/src/browser/snapshot.js +147 -0
  57. package/dist/src/browser/snapshot.js.map +1 -0
  58. package/dist/src/browser/tabs.d.ts +8 -0
  59. package/dist/src/browser/tabs.d.ts.map +1 -0
  60. package/dist/src/browser/tabs.js +25 -0
  61. package/dist/src/browser/tabs.js.map +1 -0
  62. package/dist/src/browser/types.d.ts +109 -0
  63. package/dist/src/browser/types.d.ts.map +1 -0
  64. package/dist/src/browser/types.js +16 -0
  65. package/dist/src/browser/types.js.map +1 -0
  66. package/dist/src/browser/wait.d.ts +4 -0
  67. package/dist/src/browser/wait.d.ts.map +1 -0
  68. package/dist/src/browser/wait.js +122 -0
  69. package/dist/src/browser/wait.js.map +1 -0
  70. package/dist/src/commands/browse.d.ts +8 -0
  71. package/dist/src/commands/browse.d.ts.map +1 -0
  72. package/dist/src/commands/browse.js +573 -0
  73. package/dist/src/commands/browse.js.map +1 -0
  74. package/dist/src/commands/index.d.ts.map +1 -1
  75. package/dist/src/commands/index.js +2 -0
  76. package/dist/src/commands/index.js.map +1 -1
  77. package/dist/src/ui/dashboard-v2.html +1692 -0
  78. package/dist/src/ui/server.mjs +15 -1
  79. package/dist/tsconfig.tsbuildinfo +1 -1
  80. package/package.json +2 -1
@@ -28,11 +28,13 @@ const CWD = process.env.CLAUDE_PROJECT_DIR || process.cwd();
28
28
 
29
29
  // Read monomind version โ€” check global install first, then CWD package.json
30
30
  function getVersion() {
31
- // 1. Walk up from script location to find monomind package.json
31
+ // 1. Monomind global install: script lives at <install>/packages/@monomind/cli/dist/src/init/
32
+ // or user project: .claude/helpers/statusline.cjs
33
+ // Walk up to find a monomind package.json (has "name":"monomind" or "@monomind/cli")
32
34
  const scriptDir = path.dirname(__filename);
33
35
  const walkCandidates = [
34
- path.join(scriptDir, '..', '..', 'package.json'),
35
- path.join(scriptDir, '..', '..', '..', 'package.json'),
36
+ path.join(scriptDir, '..', '..', 'package.json'), // dist/src -> @monomind/cli
37
+ path.join(scriptDir, '..', '..', '..', 'package.json'), // -> monomind umbrella
36
38
  path.join(scriptDir, '..', '..', '..', '..', 'package.json'),
37
39
  ];
38
40
  for (const p of walkCandidates) {
@@ -45,6 +47,7 @@ function getVersion() {
45
47
  }
46
48
  // 2. Fallback: npm global prefix
47
49
  try {
50
+ const { execSync } = require('child_process');
48
51
  const prefix = execSync('npm config get prefix', { encoding: 'utf-8', timeout: 2000 }).trim();
49
52
  const pkg = JSON.parse(fs.readFileSync(path.join(prefix, 'lib', 'node_modules', 'monomind', 'package.json'), 'utf-8'));
50
53
  if (pkg.version) return `v${pkg.version}`;
@@ -104,6 +107,16 @@ function safeStat(filePath) {
104
107
  return null;
105
108
  }
106
109
 
110
+ // Shared settings cache โ€” read once, used by multiple functions
111
+ let _settingsCache = undefined;
112
+ function getSettings() {
113
+ if (_settingsCache !== undefined) return _settingsCache;
114
+ _settingsCache = readJSON(path.join(CWD, '.claude', 'settings.json'))
115
+ || readJSON(path.join(CWD, '.claude', 'settings.local.json'))
116
+ || null;
117
+ return _settingsCache;
118
+ }
119
+
107
120
  // Project identifier โ€” github owner/repo from git remote, else folder name
108
121
  function getProjectName() {
109
122
  try {
@@ -116,16 +129,6 @@ function getProjectName() {
116
129
  return path.basename(CWD);
117
130
  }
118
131
 
119
- // Shared settings cache โ€” read once, used by multiple functions
120
- let _settingsCache = undefined;
121
- function getSettings() {
122
- if (_settingsCache !== undefined) return _settingsCache;
123
- _settingsCache = readJSON(path.join(CWD, '.claude', 'settings.json'))
124
- || readJSON(path.join(CWD, '.claude', 'settings.local.json'))
125
- || null;
126
- return _settingsCache;
127
- }
128
-
129
132
  // โ”€โ”€โ”€ Data Collection (all pure-Node.js or single-exec) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
130
133
 
131
134
  // Get all git info in ONE shell call
@@ -385,7 +388,6 @@ function getSwarmStatus() {
385
388
  activeAgents: liveCount,
386
389
  maxAgents: CONFIG.maxAgents,
387
390
  coordinationActive: true,
388
- lastActive: liveCount,
389
391
  };
390
392
  }
391
393
  } catch { /* fall through */ }
@@ -398,12 +400,10 @@ function getSwarmStatus() {
398
400
  const updatedAt = swarmState.updatedAt || swarmState.startedAt;
399
401
  const age = updatedAt ? now - new Date(updatedAt).getTime() : Infinity;
400
402
  if (age < staleThresholdMs) {
401
- const sc = swarmState.agents?.length || swarmState.agentCount || 0;
402
403
  return {
403
- activeAgents: sc,
404
+ activeAgents: swarmState.agents?.length || swarmState.agentCount || 0,
404
405
  maxAgents: swarmState.maxAgents || CONFIG.maxAgents,
405
406
  coordinationActive: true,
406
- lastActive: sc,
407
407
  };
408
408
  }
409
409
  }
@@ -418,23 +418,11 @@ function getSwarmStatus() {
418
418
  activeAgents: activityData.swarm.agent_count || 0,
419
419
  maxAgents: CONFIG.maxAgents,
420
420
  coordinationActive: activityData.swarm.coordination_active || activityData.swarm.active || false,
421
- lastActive: activityData.swarm.lastActive || 0,
422
421
  };
423
422
  }
424
- // Only show lastActive if data is recent enough (< 1 hour) and plausible (โ‰ค maxAgents)
425
- const displayTtlMs = 60 * 60 * 1000;
426
- const lastActive = (age < displayTtlMs)
427
- ? Math.min(activityData.swarm.lastActive || 0, CONFIG.maxAgents)
428
- : 0;
429
- return {
430
- activeAgents: 0,
431
- maxAgents: CONFIG.maxAgents,
432
- coordinationActive: false,
433
- lastActive,
434
- };
435
423
  }
436
424
 
437
- return { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false, lastActive: 0 };
425
+ return { activeAgents: 0, maxAgents: CONFIG.maxAgents, coordinationActive: false };
438
426
  }
439
427
 
440
428
  // System metrics (uses process.memoryUsage() โ€” no shell spawn)
@@ -565,7 +553,6 @@ function getActiveAgent() {
565
553
  category: data.category || null,
566
554
  confidence: data.confidence || 0,
567
555
  activated: data.activated || false, // true = manually loaded extras agent
568
- extrasMatches: data.extrasMatches || [],
569
556
  };
570
557
  } catch { return null; }
571
558
  }
@@ -577,56 +564,26 @@ function getAgentDBStats() {
577
564
  let namespaces = 0;
578
565
  let hasHnsw = false;
579
566
 
580
- // Count all memory entries across sources (sum, not max)
581
- // 0. Palace drawers
582
- const drawersPath = path.join(CWD, '.monomind', 'palace', 'drawers.jsonl');
583
- const drawersStat = safeStat(drawersPath);
584
- if (drawersStat) {
585
- dbSizeKB += drawersStat.size / 1024;
586
- try {
587
- vectorCount += fs.readFileSync(drawersPath, 'utf-8').split('\n').filter(Boolean).length;
588
- } catch { /* ignore */ }
589
- }
590
-
591
- // 1. Palace closets
592
- const closetsPath = path.join(CWD, '.monomind', 'palace', 'closets.jsonl');
593
- const closetsStat = safeStat(closetsPath);
594
- if (closetsStat) {
595
- dbSizeKB += closetsStat.size / 1024;
596
- try {
597
- vectorCount += fs.readFileSync(closetsPath, 'utf-8').split('\n').filter(Boolean).length;
598
- } catch { /* ignore */ }
599
- }
600
-
601
- // 2. Knowledge chunks
602
- const chunksPath = path.join(CWD, '.monomind', 'knowledge', 'chunks.jsonl');
603
- const chunksStat = safeStat(chunksPath);
604
- if (chunksStat) {
605
- dbSizeKB += chunksStat.size / 1024;
606
- try {
607
- vectorCount += fs.readFileSync(chunksPath, 'utf-8').split('\n').filter(Boolean).length;
608
- } catch { /* ignore */ }
609
- }
610
-
611
- // 3. Auto-memory store (intelligence layer)
567
+ // 1. Count real entries from auto-memory-store.json
612
568
  const storePath = path.join(CWD, '.monomind', 'data', 'auto-memory-store.json');
613
569
  const storeStat = safeStat(storePath);
614
570
  if (storeStat) {
615
571
  dbSizeKB += storeStat.size / 1024;
616
572
  try {
617
573
  const store = JSON.parse(fs.readFileSync(storePath, 'utf-8'));
618
- vectorCount += Array.isArray(store) ? store.length : (store?.entries?.length || 0);
619
- } catch { /* ignore */ }
574
+ if (Array.isArray(store)) vectorCount += store.length;
575
+ else if (store?.entries) vectorCount += store.entries.length;
576
+ } catch { /* fall back to size estimate */ }
620
577
  }
621
578
 
622
- // 4. Ranked context
579
+ // 2. Count entries from ranked-context.json
623
580
  const rankedPath = path.join(CWD, '.monomind', 'data', 'ranked-context.json');
624
581
  try {
625
582
  const ranked = readJSON(rankedPath);
626
- if (ranked?.entries?.length) vectorCount += ranked.entries.length;
583
+ if (ranked?.entries?.length > vectorCount) vectorCount = ranked.entries.length;
627
584
  } catch { /* ignore */ }
628
585
 
629
- // 5. DB file sizes
586
+ // 3. Add DB file sizes
630
587
  const dbFiles = [
631
588
  path.join(CWD, 'data', 'memory.db'),
632
589
  path.join(CWD, '.monomind', 'memory.db'),
@@ -634,17 +591,31 @@ function getAgentDBStats() {
634
591
  ];
635
592
  for (const f of dbFiles) {
636
593
  const stat = safeStat(f);
637
- if (stat) { dbSizeKB += stat.size / 1024; namespaces++; }
594
+ if (stat) {
595
+ dbSizeKB += stat.size / 1024;
596
+ namespaces++;
597
+ }
638
598
  }
639
599
 
640
- // 6. HNSW index
600
+ // 4. Check for graph data
601
+ const graphPath = path.join(CWD, 'data', 'memory.graph');
602
+ const graphStat = safeStat(graphPath);
603
+ if (graphStat) dbSizeKB += graphStat.size / 1024;
604
+
605
+ // 5. HNSW index
641
606
  const hnswPaths = [
642
607
  path.join(CWD, '.swarm', 'hnsw.index'),
643
608
  path.join(CWD, '.monomind', 'hnsw.index'),
644
609
  ];
645
610
  for (const p of hnswPaths) {
646
- if (safeStat(p)) { hasHnsw = true; break; }
611
+ const stat = safeStat(p);
612
+ if (stat) {
613
+ hasHnsw = true;
614
+ break;
615
+ }
647
616
  }
617
+
618
+ // HNSW is available if memory package is present
648
619
  if (!hasHnsw) {
649
620
  const memPkgPaths = [
650
621
  path.join(CWD, 'packages', '@monomind', 'memory', 'dist'),
@@ -658,234 +629,6 @@ function getAgentDBStats() {
658
629
  return { vectorCount, dbSizeKB: Math.floor(dbSizeKB), namespaces, hasHnsw };
659
630
  }
660
631
 
661
- // Graphify knowledge graph stats
662
- // Sources, in priority order:
663
- // 1. .monomind/graph/stats.json โ€” explicit cached stats
664
- // 2. .monomind/monograph.db โ€” live SQLite (read counts via sqlite3)
665
- // 3. .monomind/graph/graph.json โ€” legacy JSON dump
666
- function getGraphifyStats() {
667
- const statsPath = path.join(CWD, '.monomind', 'graph', 'stats.json');
668
- const dbPath = path.join(CWD, '.monomind', 'monograph.db');
669
- const graphPath = path.join(CWD, '.monomind', 'graph', 'graph.json');
670
-
671
- try {
672
- const s = readJSON(statsPath);
673
- if (s && s.nodes !== undefined) return { nodes: s.nodes, edges: s.edges || 0, exists: true };
674
- } catch { /* ignore */ }
675
-
676
- // Live monograph.db โ€” single SQLite call, strict 1s timeout
677
- try {
678
- if (fs.existsSync(dbPath)) {
679
- const out = safeExec(`sqlite3 "${dbPath}" "SELECT (SELECT COUNT(*) FROM nodes), (SELECT COUNT(*) FROM edges);"`, 1000);
680
- if (out) {
681
- const [n, e] = out.split('|').map(v => parseInt(v, 10) || 0);
682
- if (n > 0) return { nodes: n, edges: e, exists: true };
683
- }
684
- }
685
- } catch { /* ignore */ }
686
-
687
- try {
688
- const stat = safeStat(graphPath);
689
- if (stat && stat.size < 10 * 1024 * 1024) {
690
- const g = JSON.parse(fs.readFileSync(graphPath, 'utf-8'));
691
- const nodes = Array.isArray(g.nodes) ? g.nodes.length : 0;
692
- const edges = (Array.isArray(g.edges) ? g.edges : (Array.isArray(g.links) ? g.links : [])).length;
693
- return { nodes, edges, exists: true };
694
- }
695
- } catch { /* ignore */ }
696
- return { nodes: 0, edges: 0, exists: false };
697
- }
698
-
699
- // Hook latency โ€” sum of mean times for hooks that fire on every prompt.
700
- function getHookLatency() {
701
- const p = path.join(CWD, '.monomind', 'metrics', 'hook-latency.json');
702
- try {
703
- if (!fs.existsSync(p)) return null;
704
- const d = JSON.parse(fs.readFileSync(p, 'utf-8'));
705
- // Hooks that run per-prompt: route, pre-search, post-read, post-edit
706
- const perPrompt = ['route'];
707
- let totalMs = 0;
708
- let count = 0;
709
- for (const h of perPrompt) {
710
- if (d[h] && d[h].mean) { totalMs += d[h].mean; count++; }
711
- }
712
- if (count === 0) return null;
713
- // Find slowest hook
714
- let slowest = null;
715
- for (const k of Object.keys(d)) {
716
- if (k === 'lastUpdated' || !d[k] || typeof d[k] !== 'object') continue;
717
- if (!slowest || d[k].mean > slowest.mean) slowest = { name: k, mean: d[k].mean };
718
- }
719
- return { perPromptMs: totalMs, slowest };
720
- } catch { return null; }
721
- }
722
-
723
- // Graph usage telemetry โ€” counts ALL graph wins (MCP calls + silent assists)
724
- // vs greps that got no graph help. "graph %" reflects how often the graph
725
- // actually touched the LLM's flow, not just how often the LLM invoked MCP.
726
- function getGraphUsage() {
727
- const usagePath = path.join(CWD, '.monomind', 'metrics', 'graph-usage.json');
728
- try {
729
- if (!fs.existsSync(usagePath)) return null;
730
- const d = JSON.parse(fs.readFileSync(usagePath, 'utf-8'));
731
- const graphWins =
732
- (d.monograph_call || 0) // LLM-initiated MCP monograph tool call
733
- + (d.preresolve_hit || 0) // route hook injected pre-resolved files
734
- + (d.graph_assist_search || 0) // pre-search / pre-bash injected hits
735
- + (d.graph_assist_neighbors || 0); // post-read neighbor footer
736
- const searches = (d.grep_call || 0) + (d.glob_call || 0)
737
- + (d.bash_grep_call || 0) + (d.bash_find_call || 0);
738
- const total = graphWins + searches;
739
- if (total === 0) return null;
740
- return {
741
- graphWins,
742
- searches,
743
- pct: Math.round((graphWins / total) * 100),
744
- dollarsSaved: d.dollars_saved || 0,
745
- };
746
- } catch { return null; }
747
- }
748
-
749
- // Graph freshness โ€” compare graph build time against most recent commit
750
- // Returns: { commitsBehind, stale } where stale = >5 commits or never built
751
- function getGraphFreshness() {
752
- const lockPath = path.join(CWD, '.monomind', 'graph', '.rebuild-lock');
753
- const dbPath = path.join(CWD, '.monomind', 'monograph.db');
754
-
755
- let buildMs = 0;
756
- try {
757
- const lockStat = safeStat(lockPath);
758
- const dbStat = safeStat(dbPath);
759
- buildMs = Math.max(lockStat?.mtimeMs || 0, dbStat?.mtimeMs || 0);
760
- } catch { /* ignore */ }
761
- if (!buildMs) return { commitsBehind: -1, stale: true, fresh: false };
762
-
763
- // Count commits since the graph was last built
764
- const buildIso = new Date(buildMs).toISOString();
765
- const out = safeExec(`git rev-list --count --since='${buildIso}' HEAD 2>/dev/null`, 1500);
766
- const commitsBehind = parseInt(out, 10) || 0;
767
- return {
768
- commitsBehind,
769
- stale: commitsBehind > 5,
770
- fresh: commitsBehind === 0,
771
- };
772
- }
773
-
774
- // Active loops โ€” scan .monomind/loops/*.json
775
- // Filters: skip *-hil*.json, skip stale (>6h since lastRunAt)
776
- function getLoopStatus() {
777
- const loopsDir = path.join(CWD, '.monomind', 'loops');
778
- if (!fs.existsSync(loopsDir)) return { count: 0, loops: [] };
779
- const STALE_MS = 6 * 60 * 60 * 1000;
780
- const now = Date.now();
781
- let loops = [];
782
- try {
783
- const files = fs.readdirSync(loopsDir).filter(f =>
784
- f.endsWith('.json') && !f.includes('-hil') && !f.endsWith('.stop'));
785
- for (const f of files) {
786
- const d = readJSON(path.join(loopsDir, f));
787
- if (!d || !d.command) continue;
788
- const last = d.lastRunAt || d.nextRunAt || d.startedAt || 0;
789
- if (last && (now - last) > STALE_MS) continue;
790
- loops.push({
791
- cmd: d.command.replace(/^\//,''),
792
- type: d.type || 'repeat',
793
- rep: d.currentRep || 0,
794
- max: d.maxReps || 0,
795
- status: d.status || 'running',
796
- });
797
- }
798
- } catch { /* ignore */ }
799
- return { count: loops.length, loops };
800
- }
801
-
802
- // HIL pending โ€” count <id>-hil.md files with no human response yet
803
- function getHILPending() {
804
- const loopsDir = path.join(CWD, '.monomind', 'loops');
805
- if (!fs.existsSync(loopsDir)) return { pending: 0 };
806
- let pending = 0;
807
- try {
808
- const files = fs.readdirSync(loopsDir).filter(f => f.endsWith('-hil.md'));
809
- for (const f of files) {
810
- try {
811
- const txt = fs.readFileSync(path.join(loopsDir, f), 'utf-8');
812
- // A response is a line starting with "> " followed by non-whitespace
813
- const answered = /^[ \t]*>[ \t]+\S/m.test(txt);
814
- if (!answered) pending++;
815
- } catch { /* ignore */ }
816
- }
817
- } catch { /* ignore */ }
818
- return { pending };
819
- }
820
-
821
- // Memory Palace stats โ€” drawers.jsonl + kg.json (the real persistent memory)
822
- function getMemoryPalaceStats() {
823
- const palaceDir = path.join(CWD, '.monomind', 'palace');
824
- let drawers = 0, triples = 0, palaceSizeKB = 0;
825
-
826
- try {
827
- const drawersPath = path.join(palaceDir, 'drawers.jsonl');
828
- if (fs.existsSync(drawersPath)) {
829
- const stat = safeStat(drawersPath);
830
- if (stat) palaceSizeKB += stat.size / 1024;
831
- drawers = fs.readFileSync(drawersPath, 'utf-8').split('\n').filter(Boolean).length;
832
- }
833
- } catch { /* ignore */ }
834
-
835
- try {
836
- const kgPath = path.join(palaceDir, 'kg.json');
837
- const kg = readJSON(kgPath);
838
- if (kg?.triples) {
839
- triples = kg.triples.length;
840
- const kgStat = safeStat(kgPath);
841
- if (kgStat) palaceSizeKB += kgStat.size / 1024;
842
- }
843
- } catch { /* ignore */ }
844
-
845
- return { drawers, triples, palaceSizeKB: Math.floor(palaceSizeKB) };
846
- }
847
-
848
- // Auto-memory file stats โ€” reads ~/.claude/projects/<slug>/memory/*.md
849
- function getAutoMemoryStats() {
850
- const homeDir = os.homedir();
851
- const slug = path.resolve(CWD).replace(/\//g, '-');
852
- const memDir = path.join(homeDir, '.claude', 'projects', slug, 'memory');
853
- let files = [];
854
- try {
855
- files = fs.readdirSync(memDir).filter(f => f.endsWith('.md') && f !== 'MEMORY.md');
856
- } catch { /* dir may not exist */ }
857
-
858
- const byType = {};
859
- for (const fname of files) {
860
- let type = 'project';
861
- try {
862
- const raw = fs.readFileSync(path.join(memDir, fname), 'utf-8').replace(/\r\n/g, '\n');
863
- const m = raw.match(/^---\n[\s\S]*?type:\s*(\S+)/);
864
- if (m) type = m[1].trim();
865
- } catch { /* ignore */ }
866
- byType[type] = (byType[type] || 0) + 1;
867
- }
868
- return { count: files.length, byType };
869
- }
870
-
871
- // Token summary โ€” reads cache written by session-restore hook.
872
- // Valid for the entire UTC day it was written; expires at midnight UTC.
873
- function getTokenStats() {
874
- const cachePath = path.join(CWD, '.monomind', 'metrics', 'token-summary.json');
875
- const data = readJSON(cachePath);
876
- if (!data) return null;
877
- // Reject only if cache is from a different UTC day (midnight boundary)
878
- const cachedDay = (data.cachedAt || '').slice(0, 10); // "2026-04-15"
879
- const todayUTC = new Date().toISOString().slice(0, 10);
880
- if (cachedDay && cachedDay !== todayUTC) return null;
881
- return {
882
- todayCost: data.todayCost || 0,
883
- todayCalls: data.todayCalls || 0,
884
- monthCost: data.monthCost || 0,
885
- monthCalls: data.monthCalls || 0,
886
- };
887
- }
888
-
889
632
  // Test stats (count files only โ€” NO reading file contents)
890
633
  function getTestStats() {
891
634
  let testFiles = 0;
@@ -1062,6 +805,143 @@ function getTriggerStats() {
1062
805
  } catch { return { triggers: 0, agents: 0 }; }
1063
806
  }
1064
807
 
808
+ // Hook latency โ€” surface slow per-prompt hooks in the statusline.
809
+ function getHookLatency() {
810
+ const p = path.join(CWD, '.monomind', 'metrics', 'hook-latency.json');
811
+ try {
812
+ if (!fs.existsSync(p)) return null;
813
+ const d = JSON.parse(fs.readFileSync(p, 'utf-8'));
814
+ const perPrompt = ['route'];
815
+ let totalMs = 0; let count = 0;
816
+ for (const h of perPrompt) {
817
+ if (d[h] && d[h].mean) { totalMs += d[h].mean; count++; }
818
+ }
819
+ if (count === 0) return null;
820
+ let slowest = null;
821
+ for (const k of Object.keys(d)) {
822
+ if (k === 'lastUpdated' || !d[k] || typeof d[k] !== 'object') continue;
823
+ if (!slowest || d[k].mean > slowest.mean) slowest = { name: k, mean: d[k].mean };
824
+ }
825
+ return { perPromptMs: totalMs, slowest: slowest };
826
+ } catch { return null; }
827
+ }
828
+
829
+ // Graph usage telemetry โ€” counts ALL graph wins (MCP calls + silent assists)
830
+ // vs greps that got no graph help.
831
+ function getGraphUsage() {
832
+ const usagePath = path.join(CWD, '.monomind', 'metrics', 'graph-usage.json');
833
+ try {
834
+ if (!fs.existsSync(usagePath)) return null;
835
+ const d = JSON.parse(fs.readFileSync(usagePath, 'utf-8'));
836
+ const graphWins = (d.monograph_call || 0) + (d.preresolve_hit || 0)
837
+ + (d.graph_assist_search || 0) + (d.graph_assist_neighbors || 0);
838
+ const searches = (d.grep_call || 0) + (d.glob_call || 0)
839
+ + (d.bash_grep_call || 0) + (d.bash_find_call || 0);
840
+ const total = graphWins + searches;
841
+ if (total === 0) return null;
842
+ return { graphWins: graphWins, searches: searches, pct: Math.round((graphWins / total) * 100), dollarsSaved: d.dollars_saved || 0 };
843
+ } catch { return null; }
844
+ }
845
+
846
+ // Graph freshness โ€” compare last build time vs commits since
847
+ function getGraphFreshness() {
848
+ const lockPath = path.join(CWD, '.monomind', 'graph', '.rebuild-lock');
849
+ const dbPath = path.join(CWD, '.monomind', 'monograph.db');
850
+ let buildMs = 0;
851
+ try {
852
+ const lockStat = safeStat(lockPath);
853
+ const dbStat = safeStat(dbPath);
854
+ buildMs = Math.max(lockStat?.mtimeMs || 0, dbStat?.mtimeMs || 0);
855
+ } catch { /* ignore */ }
856
+ if (!buildMs) return { commitsBehind: -1, stale: true, fresh: false };
857
+ const buildIso = new Date(buildMs).toISOString();
858
+ const out = safeExec(`git rev-list --count --since='${buildIso}' HEAD 2>/dev/null`, 1500);
859
+ const commitsBehind = parseInt(out, 10) || 0;
860
+ return { commitsBehind, stale: commitsBehind > 5, fresh: commitsBehind === 0 };
861
+ }
862
+
863
+ // Active loops โ€” scan .monomind/loops/*.json, skip stale (>6h)
864
+ function getLoopStatus() {
865
+ const loopsDir = path.join(CWD, '.monomind', 'loops');
866
+ if (!fs.existsSync(loopsDir)) return { count: 0, loops: [] };
867
+ const STALE_MS = 6 * 60 * 60 * 1000;
868
+ const now = Date.now();
869
+ const loops = [];
870
+ try {
871
+ const files = fs.readdirSync(loopsDir).filter(f =>
872
+ f.endsWith('.json') && !f.includes('-hil') && !f.endsWith('.stop'));
873
+ for (const f of files) {
874
+ const d = readJSON(path.join(loopsDir, f));
875
+ if (!d || !d.command) continue;
876
+ const last = d.lastRunAt || d.nextRunAt || d.startedAt || 0;
877
+ if (last && (now - last) > STALE_MS) continue;
878
+ loops.push({
879
+ cmd: String(d.command).replace(/^\//,''),
880
+ type: d.type || 'repeat',
881
+ rep: d.currentRep || 0,
882
+ max: d.maxReps || 0,
883
+ status: d.status || 'running',
884
+ });
885
+ }
886
+ } catch { /* ignore */ }
887
+ return { count: loops.length, loops };
888
+ }
889
+
890
+ // HIL pending โ€” count <id>-hil.md files with no human response yet
891
+ function getHILPending() {
892
+ const loopsDir = path.join(CWD, '.monomind', 'loops');
893
+ if (!fs.existsSync(loopsDir)) return { pending: 0 };
894
+ let pending = 0;
895
+ try {
896
+ const files = fs.readdirSync(loopsDir).filter(f => f.endsWith('-hil.md'));
897
+ for (const f of files) {
898
+ try {
899
+ const txt = fs.readFileSync(path.join(loopsDir, f), 'utf-8');
900
+ const answered = /^[ \t]*>[ \t]+\S/m.test(txt);
901
+ if (!answered) pending++;
902
+ } catch { /* ignore */ }
903
+ }
904
+ } catch { /* ignore */ }
905
+ return { pending };
906
+ }
907
+
908
+ // Monograph knowledge graph stats
909
+ // Sources, in priority order:
910
+ // 1. .monomind/graph/stats.json โ€” explicit cached stats
911
+ // 2. .monomind/monograph.db โ€” live SQLite (read counts via sqlite3)
912
+ // 3. .monomind/graph/graph.json โ€” legacy JSON dump
913
+ function getGraphifyStats() {
914
+ const statsPath = path.join(CWD, '.monomind', 'graph', 'stats.json');
915
+ const dbPath = path.join(CWD, '.monomind', 'monograph.db');
916
+ const graphPath = path.join(CWD, '.monomind', 'graph', 'graph.json');
917
+
918
+ try {
919
+ const s = readJSON(statsPath);
920
+ if (s && s.nodes !== undefined) return { nodes: s.nodes, edges: s.edges || 0, exists: true };
921
+ } catch { /* ignore */ }
922
+
923
+ try {
924
+ if (fs.existsSync(dbPath)) {
925
+ const out = safeExec(`sqlite3 "${dbPath}" "SELECT (SELECT COUNT(*) FROM nodes), (SELECT COUNT(*) FROM edges);"`, 1000);
926
+ if (out) {
927
+ const [n, e] = out.split('|').map(v => parseInt(v, 10) || 0);
928
+ if (n > 0) return { nodes: n, edges: e, exists: true };
929
+ }
930
+ }
931
+ } catch { /* ignore */ }
932
+
933
+ try {
934
+ const stat = safeStat(graphPath);
935
+ if (stat && stat.size < 10 * 1024 * 1024) {
936
+ const g = JSON.parse(fs.readFileSync(graphPath, 'utf-8'));
937
+ const nodes = Array.isArray(g.nodes) ? g.nodes.length : 0;
938
+ const edges = (Array.isArray(g.edges) ? g.edges : (Array.isArray(g.links) ? g.links : [])).length;
939
+ return { nodes, edges, exists: true };
940
+ }
941
+ } catch { /* ignore */ }
942
+ return { nodes: 0, edges: 0, exists: false };
943
+ }
944
+
1065
945
  function getSIBudget() {
1066
946
  const SI_LIMIT = 1500;
1067
947
  const siPath = path.join(CWD, '.agents', 'shared_instructions.md');
@@ -1072,22 +952,6 @@ function getSIBudget() {
1072
952
  } catch { return null; }
1073
953
  }
1074
954
 
1075
- // Control server status (Neural Control Room web UI)
1076
- function getControlStatus() {
1077
- const statusPath = path.join(CWD, '.monomind', 'control.json');
1078
- try {
1079
- if (!fs.existsSync(statusPath)) return { running: false, port: null };
1080
- const data = JSON.parse(fs.readFileSync(statusPath, 'utf-8'));
1081
- if (!data || !data.pid) return { running: false, port: null };
1082
- try {
1083
- process.kill(data.pid, 0);
1084
- return { running: true, port: data.port || 4242, url: data.url };
1085
- } catch {
1086
- return { running: false, port: null };
1087
- }
1088
- } catch { return { running: false, port: null }; }
1089
- }
1090
-
1091
955
  // โ”€โ”€ Single-line statusline (compact) โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1092
956
  function generateStatusline() {
1093
957
  const git = getGitInfo();
@@ -1096,15 +960,11 @@ function generateStatusline() {
1096
960
  const hooks = getHooksStatus();
1097
961
  const knowledge = getKnowledgeStats();
1098
962
  const triggers = getTriggerStats();
1099
- const palace = getMemoryPalaceStats();
1100
- const tokens = getTokenStats();
1101
963
  const parts = [];
1102
964
 
1103
- // Brand + project + swarm dot
965
+ // Brand + swarm dot
1104
966
  const swarmDot = swarm.coordinationActive ? `${x.green}โ—${x.reset}` : `${x.slate}โ—‹${x.reset}`;
1105
- const projName = getProjectName();
1106
- const cwdBase = path.basename(CWD);
1107
- parts.push(`${x.bold}${x.purple}โ–Š MonoMind${x.reset} ${x.teal}${projName}${x.reset} ${swarmDot} ${x.dim}โ—Ž ${cwdBase}${x.reset}`);
967
+ parts.push(`${x.bold}${x.purple}โ–Š Monomind${x.reset} ${swarmDot}`);
1108
968
 
1109
969
  // Git branch + changes (compact)
1110
970
  if (git.gitBranch) {
@@ -1122,58 +982,28 @@ function generateStatusline() {
1122
982
  // Active agent
1123
983
  const activeAgent = getActiveAgent();
1124
984
  if (activeAgent) {
1125
- const isExtras = activeAgent.slug === 'extras' || activeAgent.name === 'Extras';
1126
- if (isExtras && activeAgent.extrasMatches && activeAgent.extrasMatches.length > 0) {
1127
- // Show first specialist name in compact mode
1128
- const first = activeAgent.extrasMatches[0];
1129
- parts.push(`${x.sky}๐Ÿ‘ค ${x.bold}${first.name}${x.reset}`);
1130
- } else if (!isExtras) {
1131
- const col = activeAgent.activated ? x.green : x.sky;
1132
- const icon = activeAgent.activated ? 'โ—' : '';
1133
- parts.push(icon
1134
- ? `${col}${icon} ${x.bold}${activeAgent.name}${x.reset}`
1135
- : `${col}๐Ÿ‘ค ${x.bold}${activeAgent.name}${x.reset}`);
1136
- }
1137
- // else: suppress extras with no matches
985
+ const col = activeAgent.activated ? x.green : x.sky;
986
+ const icon = activeAgent.activated ? 'โ—' : 'โ†’';
987
+ parts.push(`${col}${icon} ${x.bold}${activeAgent.name}${x.reset}`);
1138
988
  }
1139
989
 
1140
990
  // Intelligence
1141
991
  const ic = pctColor(system.intelligencePct);
1142
992
  parts.push(`${ic}๐Ÿ’ก ${system.intelligencePct}%${x.reset}`);
1143
993
 
1144
- // Auto-memory files
1145
- const autoMemCompact = getAutoMemoryStats();
1146
- if (autoMemCompact.count > 0) {
1147
- const typeOrder = ['handoff', 'user', 'feedback', 'project', 'reference'];
1148
- const typeColors = { user: x.sky, feedback: x.gold, project: x.teal, reference: x.violet, handoff: x.coral };
1149
- const typeParts = typeOrder
1150
- .filter(t => autoMemCompact.byType[t] > 0)
1151
- .map(t => `${typeColors[t] || x.slate}${autoMemCompact.byType[t]}${t.slice(0,1)}${x.reset}`);
1152
- const typeSuffix = typeParts.length > 0 ? ` ${typeParts.join(' ')}` : '';
1153
- parts.push(`${x.purple}๐Ÿง  ${autoMemCompact.count}m${x.reset}${typeSuffix}`);
1154
- }
1155
-
1156
- // Knowledge chunks โ€” show when populated
994
+ // Knowledge chunks (Task 28) โ€” show when populated
1157
995
  if (knowledge.chunks > 0) {
1158
996
  parts.push(`${x.teal}๐Ÿ“š ${knowledge.chunks}k${x.reset}`);
1159
997
  }
1160
998
 
1161
- // Graphify code graph
1162
- const gfCompact = getGraphifyStats();
1163
- if (gfCompact.exists) {
1164
- parts.push(`${x.sky}๐Ÿ”— ${gfCompact.nodes}n ${gfCompact.edges}e${x.reset}`);
1165
- }
1166
-
1167
- // Triggers โ€” show when populated
999
+ // Triggers (Task 32) โ€” show when populated
1168
1000
  if (triggers.triggers > 0) {
1169
1001
  parts.push(`${x.mint}๐ŸŽฏ ${triggers.triggers}t${x.reset}`);
1170
1002
  }
1171
1003
 
1172
- // Swarm agents โ€” show active count, or dim recent count when idle
1004
+ // Swarm agents (only when active)
1173
1005
  if (swarm.activeAgents > 0) {
1174
1006
  parts.push(`${x.gold}๐Ÿ ${swarm.activeAgents}/${swarm.maxAgents}${x.reset}`);
1175
- } else if ((swarm.lastActive || 0) > 0) {
1176
- parts.push(`${x.slate}๐Ÿ ${swarm.lastActive}โœ“${x.reset}`);
1177
1007
  }
1178
1008
 
1179
1009
  // Hooks
@@ -1181,18 +1011,6 @@ function generateStatusline() {
1181
1011
  parts.push(`${x.mint}โšก ${hooks.enabled}h${x.reset}`);
1182
1012
  }
1183
1013
 
1184
- // Token cost today (from cache)
1185
- if (tokens && tokens.todayCost > 0) {
1186
- const todayFmt = tokens.todayCost >= 100 ? `$${tokens.todayCost.toFixed(2)}` : tokens.todayCost >= 1 ? `$${tokens.todayCost.toFixed(3)}` : `$${tokens.todayCost.toFixed(4)}`;
1187
- parts.push(`${x.gold}๐Ÿ’ฐ ${todayFmt}${x.reset}`);
1188
- }
1189
-
1190
- // Control server (Neural Control Room)
1191
- const ctrl = getControlStatus();
1192
- if (ctrl.running) {
1193
- parts.push(`${x.teal}๐ŸŽ›๏ธ :${ctrl.port}${x.reset}`);
1194
- }
1195
-
1196
1014
  return parts.join(` ${DIV} `);
1197
1015
  }
1198
1016
 
@@ -1204,7 +1022,7 @@ function generateDashboard() {
1204
1022
  const security = getSecurityStatus();
1205
1023
  const swarm = getSwarmStatus();
1206
1024
  const system = getSystemMetrics();
1207
- // adrs removed โ€” internal dev metric
1025
+ const adrs = getADRStatus();
1208
1026
  const hooks = getHooksStatus();
1209
1027
  const agentdb = getAgentDBStats();
1210
1028
  const tests = getTestStats();
@@ -1213,9 +1031,6 @@ function generateDashboard() {
1213
1031
  const knowledge = getKnowledgeStats();
1214
1032
  const triggers = getTriggerStats();
1215
1033
  const si = getSIBudget();
1216
- const palace = getMemoryPalaceStats();
1217
- const autoMem = getAutoMemoryStats();
1218
- const tokens = getTokenStats();
1219
1034
  const sec = secBadge(security.status);
1220
1035
  const activeAgent = getActiveAgent();
1221
1036
  const lines = [];
@@ -1224,7 +1039,7 @@ function generateDashboard() {
1224
1039
  const swarmDot = swarm.coordinationActive ? `${x.green}โ— LIVE${x.reset}` : `${x.slate}โ—‹ IDLE${x.reset}`;
1225
1040
  const projName = getProjectName();
1226
1041
  const cwdName = path.basename(CWD);
1227
- let hdr = `${x.bold}${x.purple}โ–Š MonoMind${x.reset} ${x.dim}${VERSION}${x.reset} ${swarmDot} ${x.teal}${x.bold}${projName}${x.reset} ${DIV} ${x.dim}โ—Ž ${cwdName}${x.reset} ${DIV} ${x.violet}โฌก ${git.name}${x.reset}`;
1042
+ let hdr = `${x.bold}${x.purple}โ–Š Monomind ${VERSION}${x.reset} ${swarmDot} ${x.teal}${x.bold}${projName}${x.reset} ${DIV} ${x.dim}โ—Ž ${cwdName}${x.reset} ${DIV} ${x.violet}โฌก ${git.name}${x.reset}`;
1228
1043
 
1229
1044
  if (git.gitBranch) {
1230
1045
  hdr += ` ${DIV} ${x.sky}โއ ${x.bold}${git.gitBranch}${x.reset}`;
@@ -1238,35 +1053,16 @@ function generateDashboard() {
1238
1053
  hdr += ` ${DIV} ๐Ÿค– ${x.violet}${x.bold}${modelName}${x.reset}`;
1239
1054
  if (session.duration) hdr += ` ${x.dim}โฑ ${session.duration}${x.reset}`;
1240
1055
 
1241
- // Control server (Neural Control Room)
1242
- const ctrl = getControlStatus();
1243
- if (ctrl.running) {
1244
- hdr += ` ${DIV} ${x.teal}${x.bold}๐ŸŽ›๏ธ CTRL${x.reset}${x.teal} :${ctrl.port}${x.reset}`;
1245
- }
1246
-
1247
- if (tokens) {
1248
- const todayFmt = tokens.todayCost >= 100 ? `$${tokens.todayCost.toFixed(2)}` : tokens.todayCost >= 1 ? `$${tokens.todayCost.toFixed(3)}` : `$${tokens.todayCost.toFixed(4)}`;
1249
- hdr += ` ${DIV} ${x.gold}๐Ÿ’ฐ ${x.bold}${todayFmt}${x.reset}${x.slate} today ยท ${tokens.todayCalls} calls${x.reset}`;
1250
- }
1251
-
1252
1056
  lines.push(hdr);
1253
1057
  lines.push(SEP);
1254
1058
 
1255
1059
  // โ”€โ”€ Row 1: Active agent + Loop status โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
1256
1060
  let agentStr;
1257
1061
  if (activeAgent) {
1258
- const isExtras = activeAgent.slug === 'extras' || activeAgent.name === 'Extras';
1259
- if (isExtras && activeAgent.extrasMatches && activeAgent.extrasMatches.length > 0) {
1260
- const specialists = activeAgent.extrasMatches.slice(0, 3).map(s => s.name).join(', ');
1261
- agentStr = `${x.sky}๐Ÿ‘ค ${x.bold}${specialists}${x.reset}`;
1262
- } else if (isExtras) {
1263
- agentStr = `${x.slate}๐Ÿ‘ค no agent routed${x.reset}`;
1264
- } else {
1265
- const col = activeAgent.activated ? x.green : x.sky;
1266
- const mark = activeAgent.activated ? `${col}${x.bold}โ— ACTIVE${x.reset} ` : '';
1267
- const conf = activeAgent.activated ? '' : ` ${x.slate}${(activeAgent.confidence * 100).toFixed(0)}%${x.reset}`;
1268
- agentStr = `${mark}${col}๐Ÿ‘ค ${x.bold}${activeAgent.name}${x.reset}${conf}`;
1269
- }
1062
+ const col = activeAgent.activated ? x.green : x.sky;
1063
+ const mark = activeAgent.activated ? `${col}${x.bold}โ— ACTIVE${x.reset} ` : '';
1064
+ const conf = activeAgent.activated ? '' : ` ${x.slate}${(activeAgent.confidence * 100).toFixed(0)}%${x.reset}`;
1065
+ agentStr = `${mark}${col}๐Ÿ‘ค ${x.bold}${activeAgent.name}${x.reset}${conf}`;
1270
1066
  } else {
1271
1067
  agentStr = `${x.slate}๐Ÿ‘ค no agent routed${x.reset}`;
1272
1068
  }
@@ -1350,8 +1146,6 @@ function generateJSON() {
1350
1146
  adrs: getADRStatus(),
1351
1147
  hooks: getHooksStatus(),
1352
1148
  agentdb: getAgentDBStats(),
1353
- palace: getMemoryPalaceStats(),
1354
- tokens: getTokenStats(),
1355
1149
  tests: getTestStats(),
1356
1150
  git: { modified: git.modified, untracked: git.untracked, staged: git.staged, ahead: git.ahead, behind: git.behind },
1357
1151
  lastUpdated: new Date().toISOString(),