@timmeck/brain 1.0.0 → 1.1.0

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 (198) hide show
  1. package/BRAIN_PLAN.md +3324 -3324
  2. package/LICENSE +21 -21
  3. package/README.md +194 -188
  4. package/dist/brain.js +2 -0
  5. package/dist/brain.js.map +1 -1
  6. package/dist/cli/colors.d.ts +50 -0
  7. package/dist/cli/colors.js +106 -0
  8. package/dist/cli/colors.js.map +1 -0
  9. package/dist/cli/commands/config.d.ts +2 -0
  10. package/dist/cli/commands/config.js +165 -0
  11. package/dist/cli/commands/config.js.map +1 -0
  12. package/dist/cli/commands/dashboard.js +222 -8
  13. package/dist/cli/commands/dashboard.js.map +1 -1
  14. package/dist/cli/commands/export.js +3 -0
  15. package/dist/cli/commands/export.js.map +1 -1
  16. package/dist/cli/commands/import.js +24 -15
  17. package/dist/cli/commands/import.js.map +1 -1
  18. package/dist/cli/commands/insights.js +33 -6
  19. package/dist/cli/commands/insights.js.map +1 -1
  20. package/dist/cli/commands/learn.d.ts +2 -0
  21. package/dist/cli/commands/learn.js +22 -0
  22. package/dist/cli/commands/learn.js.map +1 -0
  23. package/dist/cli/commands/modules.js +25 -6
  24. package/dist/cli/commands/modules.js.map +1 -1
  25. package/dist/cli/commands/network.js +15 -9
  26. package/dist/cli/commands/network.js.map +1 -1
  27. package/dist/cli/commands/query.js +92 -25
  28. package/dist/cli/commands/query.js.map +1 -1
  29. package/dist/cli/commands/start.js +5 -4
  30. package/dist/cli/commands/start.js.map +1 -1
  31. package/dist/cli/commands/status.js +19 -16
  32. package/dist/cli/commands/status.js.map +1 -1
  33. package/dist/cli/commands/stop.js +5 -4
  34. package/dist/cli/commands/stop.js.map +1 -1
  35. package/dist/cli/ipc-helper.js +4 -3
  36. package/dist/cli/ipc-helper.js.map +1 -1
  37. package/dist/db/migrations/001_core_schema.js +115 -115
  38. package/dist/db/migrations/002_learning_schema.js +33 -33
  39. package/dist/db/migrations/003_code_schema.js +48 -48
  40. package/dist/db/migrations/004_synapses_schema.js +52 -52
  41. package/dist/db/migrations/005_fts_indexes.js +73 -73
  42. package/dist/db/migrations/index.js +6 -6
  43. package/dist/db/repositories/antipattern.repository.js +3 -3
  44. package/dist/db/repositories/code-module.repository.d.ts +1 -0
  45. package/dist/db/repositories/code-module.repository.js +8 -0
  46. package/dist/db/repositories/code-module.repository.js.map +1 -1
  47. package/dist/db/repositories/error.repository.js +46 -46
  48. package/dist/db/repositories/insight.repository.js +3 -3
  49. package/dist/db/repositories/notification.repository.js +3 -3
  50. package/dist/db/repositories/project.repository.js +21 -21
  51. package/dist/db/repositories/rule.repository.js +24 -24
  52. package/dist/db/repositories/solution.repository.js +50 -50
  53. package/dist/db/repositories/synapse.repository.js +18 -18
  54. package/dist/db/repositories/terminal.repository.js +24 -24
  55. package/dist/index.js +4 -0
  56. package/dist/index.js.map +1 -1
  57. package/dist/ipc/router.d.ts +2 -0
  58. package/dist/ipc/router.js +7 -1
  59. package/dist/ipc/router.js.map +1 -1
  60. package/dist/services/code.service.d.ts +1 -1
  61. package/dist/services/code.service.js +5 -2
  62. package/dist/services/code.service.js.map +1 -1
  63. package/package.json +5 -4
  64. package/src/brain.ts +3 -0
  65. package/src/cli/colors.ts +116 -0
  66. package/src/cli/commands/config.ts +169 -0
  67. package/src/cli/commands/dashboard.ts +231 -8
  68. package/src/cli/commands/export.ts +4 -0
  69. package/src/cli/commands/import.ts +24 -15
  70. package/src/cli/commands/insights.ts +37 -5
  71. package/src/cli/commands/learn.ts +24 -0
  72. package/src/cli/commands/modules.ts +28 -5
  73. package/src/cli/commands/network.ts +15 -9
  74. package/src/cli/commands/query.ts +103 -26
  75. package/src/cli/commands/start.ts +5 -4
  76. package/src/cli/commands/status.ts +19 -16
  77. package/src/cli/commands/stop.ts +5 -4
  78. package/src/cli/ipc-helper.ts +4 -3
  79. package/src/code/analyzer.ts +77 -77
  80. package/src/code/fingerprint.ts +87 -87
  81. package/src/code/matcher.ts +64 -64
  82. package/src/code/parsers/generic.ts +29 -29
  83. package/src/code/parsers/python.ts +54 -54
  84. package/src/code/parsers/typescript.ts +65 -65
  85. package/src/code/registry.ts +60 -60
  86. package/src/code/scorer.ts +108 -108
  87. package/src/config.ts +111 -111
  88. package/src/db/connection.ts +22 -22
  89. package/src/db/migrations/001_core_schema.ts +120 -120
  90. package/src/db/migrations/002_learning_schema.ts +38 -38
  91. package/src/db/migrations/003_code_schema.ts +53 -53
  92. package/src/db/migrations/004_synapses_schema.ts +57 -57
  93. package/src/db/migrations/005_fts_indexes.ts +78 -78
  94. package/src/db/migrations/006_synapses_phase3.ts +17 -17
  95. package/src/db/migrations/index.ts +64 -64
  96. package/src/db/repositories/antipattern.repository.ts +66 -66
  97. package/src/db/repositories/code-module.repository.ts +9 -0
  98. package/src/db/repositories/error.repository.ts +149 -149
  99. package/src/db/repositories/insight.repository.ts +78 -78
  100. package/src/db/repositories/notification.repository.ts +66 -66
  101. package/src/db/repositories/project.repository.ts +93 -93
  102. package/src/db/repositories/rule.repository.ts +108 -108
  103. package/src/db/repositories/solution.repository.ts +154 -154
  104. package/src/db/repositories/synapse.repository.ts +153 -153
  105. package/src/db/repositories/terminal.repository.ts +101 -101
  106. package/src/hooks/post-tool-use.ts +90 -90
  107. package/src/hooks/post-write.ts +117 -117
  108. package/src/index.ts +4 -0
  109. package/src/ipc/client.ts +118 -118
  110. package/src/ipc/protocol.ts +35 -35
  111. package/src/ipc/router.ts +9 -1
  112. package/src/ipc/server.ts +110 -110
  113. package/src/learning/confidence-scorer.ts +47 -47
  114. package/src/learning/decay.ts +46 -46
  115. package/src/learning/learning-engine.ts +162 -162
  116. package/src/learning/pattern-extractor.ts +90 -90
  117. package/src/learning/rule-generator.ts +74 -74
  118. package/src/matching/error-matcher.ts +115 -115
  119. package/src/matching/fingerprint.ts +29 -29
  120. package/src/matching/similarity.ts +61 -61
  121. package/src/matching/tfidf.ts +74 -74
  122. package/src/matching/tokenizer.ts +41 -41
  123. package/src/mcp/auto-detect.ts +93 -93
  124. package/src/mcp/server.ts +73 -73
  125. package/src/mcp/tools.ts +290 -290
  126. package/src/parsing/error-parser.ts +28 -28
  127. package/src/parsing/parsers/compiler.ts +93 -93
  128. package/src/parsing/parsers/generic.ts +28 -28
  129. package/src/parsing/parsers/go.ts +97 -97
  130. package/src/parsing/parsers/node.ts +69 -69
  131. package/src/parsing/parsers/python.ts +62 -62
  132. package/src/parsing/parsers/rust.ts +50 -50
  133. package/src/parsing/parsers/shell.ts +42 -42
  134. package/src/parsing/types.ts +47 -47
  135. package/src/research/gap-analyzer.ts +135 -135
  136. package/src/research/insight-generator.ts +123 -123
  137. package/src/research/research-engine.ts +116 -116
  138. package/src/research/synergy-detector.ts +126 -126
  139. package/src/research/template-extractor.ts +130 -130
  140. package/src/research/trend-analyzer.ts +127 -127
  141. package/src/services/analytics.service.ts +87 -87
  142. package/src/services/code.service.ts +5 -2
  143. package/src/services/error.service.ts +164 -164
  144. package/src/services/notification.service.ts +41 -41
  145. package/src/services/prevention.service.ts +119 -119
  146. package/src/services/research.service.ts +93 -93
  147. package/src/services/solution.service.ts +116 -116
  148. package/src/services/synapse.service.ts +59 -59
  149. package/src/services/terminal.service.ts +81 -81
  150. package/src/synapses/activation.ts +80 -80
  151. package/src/synapses/decay.ts +38 -38
  152. package/src/synapses/hebbian.ts +69 -69
  153. package/src/synapses/pathfinder.ts +81 -81
  154. package/src/synapses/synapse-manager.ts +109 -109
  155. package/src/types/code.types.ts +52 -52
  156. package/src/types/config.types.ts +79 -79
  157. package/src/types/error.types.ts +67 -67
  158. package/src/types/ipc.types.ts +8 -8
  159. package/src/types/mcp.types.ts +53 -53
  160. package/src/types/research.types.ts +28 -28
  161. package/src/types/solution.types.ts +30 -30
  162. package/src/types/synapse.types.ts +49 -49
  163. package/src/utils/events.ts +45 -45
  164. package/src/utils/hash.ts +5 -5
  165. package/src/utils/logger.ts +48 -48
  166. package/src/utils/paths.ts +19 -19
  167. package/tests/fixtures/code-modules/modules.ts +83 -83
  168. package/tests/fixtures/errors/go.ts +9 -9
  169. package/tests/fixtures/errors/node.ts +24 -24
  170. package/tests/fixtures/errors/python.ts +21 -21
  171. package/tests/fixtures/errors/rust.ts +25 -25
  172. package/tests/fixtures/errors/shell.ts +15 -15
  173. package/tests/fixtures/solutions/solutions.ts +27 -27
  174. package/tests/helpers/setup-db.ts +52 -52
  175. package/tests/integration/code-flow.test.ts +86 -86
  176. package/tests/integration/error-flow.test.ts +83 -83
  177. package/tests/integration/ipc-flow.test.ts +166 -166
  178. package/tests/integration/learning-cycle.test.ts +82 -82
  179. package/tests/integration/synapse-flow.test.ts +117 -117
  180. package/tests/unit/code/analyzer.test.ts +58 -58
  181. package/tests/unit/code/fingerprint.test.ts +51 -51
  182. package/tests/unit/code/scorer.test.ts +55 -55
  183. package/tests/unit/learning/confidence-scorer.test.ts +60 -60
  184. package/tests/unit/learning/decay.test.ts +45 -45
  185. package/tests/unit/learning/pattern-extractor.test.ts +50 -50
  186. package/tests/unit/matching/error-matcher.test.ts +69 -69
  187. package/tests/unit/matching/fingerprint.test.ts +47 -47
  188. package/tests/unit/matching/similarity.test.ts +65 -65
  189. package/tests/unit/matching/tfidf.test.ts +71 -71
  190. package/tests/unit/matching/tokenizer.test.ts +83 -83
  191. package/tests/unit/parsing/parsers.test.ts +113 -113
  192. package/tests/unit/research/gap-analyzer.test.ts +45 -45
  193. package/tests/unit/research/trend-analyzer.test.ts +45 -45
  194. package/tests/unit/synapses/activation.test.ts +80 -80
  195. package/tests/unit/synapses/decay.test.ts +27 -27
  196. package/tests/unit/synapses/hebbian.test.ts +96 -96
  197. package/tests/unit/synapses/pathfinder.test.ts +72 -72
  198. package/tsconfig.json +18 -18
@@ -2,6 +2,7 @@ import { Command } from 'commander';
2
2
  import { withIpc } from '../ipc-helper.js';
3
3
  import { writeFileSync } from 'fs';
4
4
  import { resolve } from 'path';
5
+ import { c, icons } from '../colors.js';
5
6
 
6
7
  export function dashboardCommand(): Command {
7
8
  return new Command('dashboard')
@@ -10,13 +11,15 @@ export function dashboardCommand(): Command {
10
11
  .option('--no-open', 'Generate without opening in browser')
11
12
  .action(async (opts) => {
12
13
  await withIpc(async (client) => {
13
- console.log('Fetching data from Brain...');
14
+ console.log(`${icons.chart} ${c.info('Fetching data from Brain...')}`);
14
15
 
15
16
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
17
  const summary: any = await client.request('analytics.summary', {});
17
18
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
18
19
  const network: any = await client.request('synapse.stats', {});
19
20
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
21
+ const networkOverview: any = await client.request('analytics.network', { limit: 50 });
22
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
20
23
  const insights: any = await client.request('research.insights', {
21
24
  activeOnly: true,
22
25
  limit: 500,
@@ -44,6 +47,9 @@ export function dashboardCommand(): Command {
44
47
  const warnings = insightList.filter((i: InsightItem) => i.type === 'warning');
45
48
  const synergies = insightList.filter((i: InsightItem) => i.type === 'synergy' || i.type === 'optimization');
46
49
 
50
+ // Build synapse graph data
51
+ const synapseEdges = Array.isArray(networkOverview?.strongestSynapses) ? networkOverview.strongestSynapses : [];
52
+
47
53
  const data = {
48
54
  stats: {
49
55
  modules: summary.modules?.total ?? 0,
@@ -55,6 +61,7 @@ export function dashboardCommand(): Command {
55
61
  },
56
62
  langStats,
57
63
  insights: { templates, suggestions, trends, gaps, warnings, synergies },
64
+ synapseEdges,
58
65
  };
59
66
 
60
67
  const html = generateHtml(data);
@@ -63,10 +70,8 @@ export function dashboardCommand(): Command {
63
70
  : resolve(import.meta.dirname, '../../../dashboard.html');
64
71
 
65
72
  writeFileSync(outPath, html, 'utf-8');
66
- console.log(`Dashboard written to ${outPath}`);
67
- console.log(` Modules: ${data.stats.modules}`);
68
- console.log(` Synapses: ${data.stats.synapses}`);
69
- console.log(` Insights: ${data.stats.insights}`);
73
+ console.log(`${icons.ok} ${c.success('Dashboard written to')} ${c.dim(outPath)}`);
74
+ console.log(` ${c.label('Modules:')} ${c.value(data.stats.modules)} ${c.label('Synapses:')} ${c.value(data.stats.synapses)} ${c.label('Insights:')} ${c.value(data.stats.insights)}`);
70
75
 
71
76
  if (opts.open !== false) {
72
77
  const { exec } = await import('child_process');
@@ -83,6 +88,13 @@ interface InsightItem {
83
88
  priority?: string;
84
89
  }
85
90
 
91
+ interface SynapseEdge {
92
+ source: string;
93
+ target: string;
94
+ type: string;
95
+ weight: number;
96
+ }
97
+
86
98
  interface DashboardData {
87
99
  stats: {
88
100
  modules: number;
@@ -101,6 +113,7 @@ interface DashboardData {
101
113
  warnings: InsightItem[];
102
114
  synergies: InsightItem[];
103
115
  };
116
+ synapseEdges: SynapseEdge[];
104
117
  }
105
118
 
106
119
  function esc(s: string): string {
@@ -108,7 +121,7 @@ function esc(s: string): string {
108
121
  }
109
122
 
110
123
  function generateHtml(data: DashboardData): string {
111
- const { stats, langStats, insights } = data;
124
+ const { stats, langStats, insights, synapseEdges } = data;
112
125
 
113
126
  // Build language chart bars
114
127
  const sortedLangs = Object.entries(langStats).sort((a, b) => b[1] - a[1]);
@@ -276,6 +289,14 @@ function generateHtml(data: DashboardData): string {
276
289
  .prio-low{background:rgba(139,143,176,.1);color:var(--text2);border:1px solid rgba(139,143,176,.2)}
277
290
  .empty{color:var(--text3);font-style:italic;padding:24px}
278
291
 
292
+ /* Graph */
293
+ .graph-container{position:relative;background:var(--glass);border:1px solid var(--glass-border);border-radius:var(--radius);overflow:hidden;backdrop-filter:blur(20px)}
294
+ #synapse-graph{width:100%;height:500px;display:block;cursor:grab}
295
+ #synapse-graph:active{cursor:grabbing}
296
+ .graph-legend{display:flex;gap:16px;flex-wrap:wrap;padding:12px 20px;border-top:1px solid var(--glass-border);font-size:.8rem;color:var(--text2)}
297
+ .legend-dot{display:inline-block;width:10px;height:10px;border-radius:50%;margin-right:6px;vertical-align:middle}
298
+ .graph-tooltip{position:absolute;display:none;background:var(--bg2);border:1px solid var(--glass-border);border-radius:8px;padding:8px 14px;font-size:.8rem;color:var(--text);pointer-events:none;z-index:10;backdrop-filter:blur(20px);box-shadow:0 8px 30px rgba(0,0,0,.3)}
299
+
279
300
  /* Footer */
280
301
  footer{text-align:center;padding:40px 0;border-top:1px solid var(--glass-border)}
281
302
  footer p{color:var(--text3);font-size:.8rem}
@@ -310,7 +331,8 @@ function generateHtml(data: DashboardData): string {
310
331
  <nav class="reveal reveal-delay-1">
311
332
  <a href="#stats">Stats</a>
312
333
  <a href="#languages">Languages</a>
313
- <a href="#research" class="research">&#128300; Research</a>
334
+ <a href="#network">&#128300; Network</a>
335
+ <a href="#research" class="research">&#128161; Research</a>
314
336
  </nav>
315
337
 
316
338
  <section id="stats" class="reveal reveal-delay-2">
@@ -330,7 +352,22 @@ function generateHtml(data: DashboardData): string {
330
352
  <div class="lang-chart">${langBars}</div>
331
353
  </section>
332
354
 
333
- <section id="research" class="reveal reveal-delay-4">
355
+ <section id="network" class="reveal reveal-delay-4">
356
+ <div class="section-title"><div class="icon" style="background:rgba(71,229,255,.1)">&#128300;</div> Synapse Network</div>
357
+ <div class="graph-container">
358
+ <canvas id="synapse-graph"></canvas>
359
+ <div class="graph-legend">
360
+ <span><span class="legend-dot" style="background:var(--blue)"></span> error</span>
361
+ <span><span class="legend-dot" style="background:var(--green)"></span> solution</span>
362
+ <span><span class="legend-dot" style="background:var(--purple)"></span> code_module</span>
363
+ <span><span class="legend-dot" style="background:var(--orange)"></span> project</span>
364
+ <span><span class="legend-dot" style="background:var(--cyan)"></span> other</span>
365
+ </div>
366
+ <div id="graph-tooltip" class="graph-tooltip"></div>
367
+ </div>
368
+ </section>
369
+
370
+ <section id="research" class="reveal reveal-delay-5">
334
371
  <div class="section-title"><div class="icon" style="background:rgba(71,229,255,.1)">&#128300;</div> Research Insights</div>
335
372
  <div class="tab-bar">
336
373
  <button class="tab-btn active" data-tab="templates">&#127912; Templates <span class="count">${insights.templates.length}</span></button>
@@ -489,6 +526,192 @@ setTimeout(() => {
489
526
  el.style.width = el.dataset.target + '%';
490
527
  });
491
528
  }, 500);
529
+
530
+ // --- Synapse Force-Directed Graph ---
531
+ (function(){
532
+ const edges = ${JSON.stringify(synapseEdges.map((e: SynapseEdge) => ({ s: e.source, t: e.target, type: e.type, w: e.weight })))};
533
+ const canvas = document.getElementById('synapse-graph');
534
+ if (!canvas || !edges.length) return;
535
+ const ctx = canvas.getContext('2d');
536
+ const container = canvas.parentElement;
537
+ let W, H, dpr;
538
+
539
+ const NODE_COLORS = {
540
+ error: '#ff5577', solution: '#3dffa0', code_module: '#b47aff',
541
+ project: '#ffb347', rule: '#5b9cff', antipattern: '#ff5577'
542
+ };
543
+ const DEFAULT_COLOR = '#47e5ff';
544
+
545
+ // Build graph nodes & edges
546
+ const nodeMap = new Map();
547
+ const graphEdges = [];
548
+ for (const e of edges) {
549
+ if (!nodeMap.has(e.s)) nodeMap.set(e.s, { id: e.s, type: e.s.split(':')[0], x: 0, y: 0, vx: 0, vy: 0, connections: 0 });
550
+ if (!nodeMap.has(e.t)) nodeMap.set(e.t, { id: e.t, type: e.t.split(':')[0], x: 0, y: 0, vx: 0, vy: 0, connections: 0 });
551
+ nodeMap.get(e.s).connections++;
552
+ nodeMap.get(e.t).connections++;
553
+ graphEdges.push({ source: nodeMap.get(e.s), target: nodeMap.get(e.t), type: e.type, weight: e.w });
554
+ }
555
+ const nodes = [...nodeMap.values()];
556
+
557
+ function resize() {
558
+ dpr = window.devicePixelRatio || 1;
559
+ W = container.clientWidth;
560
+ H = 500;
561
+ canvas.width = W * dpr;
562
+ canvas.height = H * dpr;
563
+ canvas.style.width = W + 'px';
564
+ canvas.style.height = H + 'px';
565
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
566
+ }
567
+ resize();
568
+ window.addEventListener('resize', resize);
569
+
570
+ // Random initial positions
571
+ for (const n of nodes) {
572
+ n.x = W * 0.2 + Math.random() * W * 0.6;
573
+ n.y = H * 0.2 + Math.random() * H * 0.6;
574
+ }
575
+
576
+ // Force simulation
577
+ const REPULSION = 3000;
578
+ const ATTRACTION = 0.008;
579
+ const DAMPING = 0.85;
580
+ const CENTER_GRAVITY = 0.002;
581
+ let hovered = null;
582
+ let dragging = null;
583
+ let dragOff = {x:0,y:0};
584
+
585
+ function simulate() {
586
+ // Repulsion
587
+ for (let i = 0; i < nodes.length; i++) {
588
+ for (let j = i + 1; j < nodes.length; j++) {
589
+ let dx = nodes[i].x - nodes[j].x;
590
+ let dy = nodes[i].y - nodes[j].y;
591
+ let dist = Math.sqrt(dx*dx + dy*dy) || 1;
592
+ let force = REPULSION / (dist * dist);
593
+ let fx = (dx / dist) * force;
594
+ let fy = (dy / dist) * force;
595
+ nodes[i].vx += fx; nodes[i].vy += fy;
596
+ nodes[j].vx -= fx; nodes[j].vy -= fy;
597
+ }
598
+ }
599
+ // Attraction along edges
600
+ for (const e of graphEdges) {
601
+ let dx = e.target.x - e.source.x;
602
+ let dy = e.target.y - e.source.y;
603
+ let dist = Math.sqrt(dx*dx + dy*dy) || 1;
604
+ let force = (dist - 100) * ATTRACTION * e.weight;
605
+ let fx = (dx / dist) * force;
606
+ let fy = (dy / dist) * force;
607
+ e.source.vx += fx; e.source.vy += fy;
608
+ e.target.vx -= fx; e.target.vy -= fy;
609
+ }
610
+ // Center gravity
611
+ for (const n of nodes) {
612
+ n.vx += (W/2 - n.x) * CENTER_GRAVITY;
613
+ n.vy += (H/2 - n.y) * CENTER_GRAVITY;
614
+ }
615
+ // Apply & damp
616
+ for (const n of nodes) {
617
+ if (n === dragging) continue;
618
+ n.vx *= DAMPING; n.vy *= DAMPING;
619
+ n.x += n.vx; n.y += n.vy;
620
+ n.x = Math.max(20, Math.min(W - 20, n.x));
621
+ n.y = Math.max(20, Math.min(H - 20, n.y));
622
+ }
623
+ }
624
+
625
+ function getNodeRadius(n) { return Math.min(16, 5 + n.connections * 1.5); }
626
+
627
+ function draw() {
628
+ ctx.clearRect(0, 0, W, H);
629
+ // Edges
630
+ for (const e of graphEdges) {
631
+ const alpha = 0.15 + e.weight * 0.5;
632
+ ctx.strokeStyle = 'rgba(91,156,255,' + Math.min(0.8, alpha) + ')';
633
+ ctx.lineWidth = 0.5 + e.weight * 2;
634
+ ctx.beginPath();
635
+ ctx.moveTo(e.source.x, e.source.y);
636
+ ctx.lineTo(e.target.x, e.target.y);
637
+ ctx.stroke();
638
+ }
639
+ // Nodes
640
+ for (const n of nodes) {
641
+ const r = getNodeRadius(n);
642
+ const color = NODE_COLORS[n.type] || DEFAULT_COLOR;
643
+ const isHover = n === hovered || n === dragging;
644
+ // Glow
645
+ if (isHover) {
646
+ ctx.shadowColor = color;
647
+ ctx.shadowBlur = 20;
648
+ }
649
+ ctx.fillStyle = color;
650
+ ctx.globalAlpha = isHover ? 1 : 0.8;
651
+ ctx.beginPath();
652
+ ctx.arc(n.x, n.y, r, 0, Math.PI * 2);
653
+ ctx.fill();
654
+ ctx.globalAlpha = 1;
655
+ ctx.shadowBlur = 0;
656
+ // Label for hovered or large nodes
657
+ if (isHover || n.connections >= 4) {
658
+ ctx.fillStyle = '#e8eaf6';
659
+ ctx.font = (isHover ? 'bold ' : '') + '11px Inter, system-ui, sans-serif';
660
+ ctx.textAlign = 'center';
661
+ ctx.fillText(n.id, n.x, n.y - r - 6);
662
+ }
663
+ }
664
+ simulate();
665
+ requestAnimationFrame(draw);
666
+ }
667
+ draw();
668
+
669
+ // Interaction
670
+ const tooltip = document.getElementById('graph-tooltip');
671
+ function getNodeAt(mx, my) {
672
+ for (let i = nodes.length - 1; i >= 0; i--) {
673
+ const n = nodes[i], r = getNodeRadius(n);
674
+ if (Math.hypot(mx - n.x, my - n.y) <= r + 4) return n;
675
+ }
676
+ return null;
677
+ }
678
+ function getPos(e) {
679
+ const rect = canvas.getBoundingClientRect();
680
+ return { x: e.clientX - rect.left, y: e.clientY - rect.top };
681
+ }
682
+ canvas.addEventListener('mousemove', function(e) {
683
+ const p = getPos(e);
684
+ if (dragging) {
685
+ dragging.x = p.x + dragOff.x;
686
+ dragging.y = p.y + dragOff.y;
687
+ dragging.vx = 0; dragging.vy = 0;
688
+ return;
689
+ }
690
+ const n = getNodeAt(p.x, p.y);
691
+ hovered = n;
692
+ canvas.style.cursor = n ? 'pointer' : 'grab';
693
+ if (n) {
694
+ const conns = graphEdges.filter(e => e.source === n || e.target === n);
695
+ tooltip.innerHTML = '<strong>' + n.id + '</strong><br>' + conns.length + ' connections';
696
+ tooltip.style.display = 'block';
697
+ tooltip.style.left = (p.x + 15) + 'px';
698
+ tooltip.style.top = (p.y - 10) + 'px';
699
+ } else {
700
+ tooltip.style.display = 'none';
701
+ }
702
+ });
703
+ canvas.addEventListener('mousedown', function(e) {
704
+ const p = getPos(e);
705
+ const n = getNodeAt(p.x, p.y);
706
+ if (n) {
707
+ dragging = n;
708
+ dragOff = { x: n.x - p.x, y: n.y - p.y };
709
+ canvas.style.cursor = 'grabbing';
710
+ }
711
+ });
712
+ canvas.addEventListener('mouseup', function() { dragging = null; });
713
+ canvas.addEventListener('mouseleave', function() { dragging = null; hovered = null; tooltip.style.display = 'none'; });
714
+ })();
492
715
  </script>
493
716
  </body>
494
717
  </html>`;
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons } from '../colors.js';
3
4
 
4
5
  export function exportCommand(): Command {
5
6
  return new Command('export')
@@ -7,6 +8,8 @@ export function exportCommand(): Command {
7
8
  .option('--format <fmt>', 'Output format: json (default)', 'json')
8
9
  .action(async () => {
9
10
  await withIpc(async (client) => {
11
+ process.stderr.write(`${icons.gear} ${c.info('Exporting Brain data...')}\n`);
12
+
10
13
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
14
  const summary: any = await client.request('analytics.summary', {});
12
15
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -22,6 +25,7 @@ export function exportCommand(): Command {
22
25
  };
23
26
 
24
27
  console.log(JSON.stringify(data, null, 2));
28
+ process.stderr.write(`${icons.ok} ${c.success('Export complete.')}\n`);
25
29
  });
26
30
  });
27
31
  }
@@ -2,10 +2,13 @@ import { Command } from 'commander';
2
2
  import { withIpc } from '../ipc-helper.js';
3
3
  import { readdirSync, readFileSync, statSync } from 'fs';
4
4
  import { resolve, basename, relative, extname } from 'path';
5
+ import { c, icons, header, divider, progressBar } from '../colors.js';
5
6
 
6
7
  const DEFAULT_EXTENSIONS = new Set([
7
8
  '.ts', '.tsx', '.js', '.jsx', '.py', '.rs', '.go',
8
9
  '.java', '.c', '.cpp', '.h', '.hpp', '.rb', '.sh',
10
+ '.html', '.css', '.scss', '.json', '.yaml', '.yml', '.toml',
11
+ '.md', '.sql', '.php', '.svelte', '.vue', '.astro',
9
12
  ]);
10
13
 
11
14
  const EXCLUDE_DIRS = new Set([
@@ -22,6 +25,10 @@ const LANG_MAP: Record<string, string> = {
22
25
  py: 'python', rs: 'rust', go: 'go', java: 'java',
23
26
  c: 'c', cpp: 'cpp', h: 'c', hpp: 'cpp',
24
27
  rb: 'ruby', sh: 'shell', bash: 'shell',
28
+ html: 'html', css: 'css', scss: 'scss',
29
+ json: 'json', yaml: 'yaml', yml: 'yaml', toml: 'toml',
30
+ md: 'markdown', sql: 'sql', php: 'php',
31
+ svelte: 'svelte', vue: 'vue', astro: 'astro',
25
32
  };
26
33
 
27
34
  function detectLanguage(filePath: string): string {
@@ -75,7 +82,7 @@ export function importCommand(): Command {
75
82
  .description('Import source files from a project directory into Brain')
76
83
  .argument('<directory>', 'Project directory to scan')
77
84
  .option('-p, --project <name>', 'Project name (default: directory basename)')
78
- .option('-e, --extensions <list>', 'Comma-separated extensions (default: ts,tsx,js,jsx,py,rs,go,java,c,cpp,h,hpp,rb,sh)')
85
+ .option('-e, --extensions <list>', 'Comma-separated extensions (default: ts,tsx,js,jsx,py,rs,go,java,c,cpp,h,hpp,rb,sh,html,css,scss,json,yaml,yml,toml,md,sql,php,svelte,vue,astro)')
79
86
  .option('--dry-run', 'List files that would be imported without importing')
80
87
  .option('--max-size <kb>', 'Skip files larger than N KB', '100')
81
88
  .action(async (directory: string, opts) => {
@@ -107,23 +114,23 @@ export function importCommand(): Command {
107
114
  process.exit(1);
108
115
  }
109
116
 
110
- console.log(`Scanning ${dir} ...`);
117
+ console.log(`${icons.search} ${c.info('Scanning')} ${c.value(dir)} ...`);
111
118
  const files = findSourceFiles(dir, extensions, maxSizeBytes);
112
119
 
113
120
  if (files.length === 0) {
114
- console.log('No source files found.');
121
+ console.log(`${c.dim('No source files found.')}`);
115
122
  return;
116
123
  }
117
124
 
118
- console.log(`Found ${files.length} source files.\n`);
125
+ console.log(`${icons.ok} Found ${c.value(files.length)} source files.\n`);
119
126
 
120
127
  if (opts.dryRun) {
121
128
  for (const f of files) {
122
129
  const rel = relative(dir, f);
123
130
  const lang = detectLanguage(f);
124
- console.log(` [${lang}] ${rel}`);
131
+ console.log(` ${c.cyan(`[${lang}]`)} ${c.dim(rel)}`);
125
132
  }
126
- console.log(`\n${files.length} files would be imported as project "${projectName}".`);
133
+ console.log(`\n${c.value(files.length)} files would be imported as project ${c.cyan(`"${projectName}"`)}.`);
127
134
  return;
128
135
  }
129
136
 
@@ -146,7 +153,7 @@ export function importCommand(): Command {
146
153
  source = readFileSync(filePath, 'utf-8');
147
154
  } catch {
148
155
  failedCount++;
149
- process.stdout.write(` [${i + 1}/${files.length}] ${rel} — read error\n`);
156
+ process.stdout.write(` ${c.dim(`[${i + 1}/${files.length}]`)} ${c.dim(rel)} ${c.red('— read error')}\n`);
150
157
  continue;
151
158
  }
152
159
 
@@ -164,27 +171,29 @@ export function importCommand(): Command {
164
171
  });
165
172
 
166
173
  const score = result.reusabilityScore ?? 0;
167
- const status = result.isNew ? 'new' : 'existing';
174
+ const scoreColor = score >= 0.7 ? c.green : score >= 0.4 ? c.orange : c.red;
175
+ const statusTag = result.isNew ? c.green('new') : c.dim('existing');
168
176
  totalScore += score;
169
177
  imported++;
170
178
 
171
179
  if (result.isNew) newCount++;
172
180
  else existingCount++;
173
181
 
174
- process.stdout.write(` [${i + 1}/${files.length}] ${rel} score: ${score.toFixed(2)} (${status})\n`);
182
+ process.stdout.write(` ${c.dim(`[${i + 1}/${files.length}]`)} ${c.dim(rel)} ${c.dim(icons.arrow)} ${scoreColor(score.toFixed(2))} (${statusTag})\n`);
175
183
  } catch (err) {
176
184
  failedCount++;
177
185
  const msg = err instanceof Error ? err.message : String(err);
178
- process.stdout.write(` [${i + 1}/${files.length}] ${rel} failed: ${msg.slice(0, 80)}\n`);
186
+ process.stdout.write(` ${c.dim(`[${i + 1}/${files.length}]`)} ${c.dim(rel)} ${c.red(`— ${msg.slice(0, 80)}`)}\n`);
179
187
  }
180
188
  }
181
189
 
182
190
  const avgScore = imported > 0 ? (totalScore / imported).toFixed(2) : '0';
183
- console.log(`\n--- Import Summary ---`);
184
- console.log(` Project: ${projectName}`);
185
- console.log(` Imported: ${imported} (${newCount} new, ${existingCount} existing)`);
186
- if (failedCount > 0) console.log(` Failed: ${failedCount}`);
187
- console.log(` Avg reusability score: ${avgScore}`);
191
+ console.log(header('Import Summary', icons.module));
192
+ console.log(` ${c.label('Project:')} ${c.cyan(projectName)}`);
193
+ console.log(` ${c.label('Imported:')} ${c.value(imported)} (${c.green(`${newCount} new`)}, ${c.dim(`${existingCount} existing`)})`);
194
+ if (failedCount > 0) console.log(` ${c.label('Failed:')} ${c.red(failedCount)}`);
195
+ console.log(` ${c.label('Avg score:')} ${c.value(avgScore)} ${progressBar(parseFloat(avgScore), 1)}`);
196
+ console.log(divider());
188
197
  });
189
198
  });
190
199
  }
@@ -1,5 +1,18 @@
1
1
  import { Command } from 'commander';
2
2
  import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header, priorityBadge, divider } from '../colors.js';
4
+
5
+ const TYPE_ICONS: Record<string, string> = {
6
+ trend: '📈',
7
+ pattern: '🔄',
8
+ gap: '⚠',
9
+ synergy: '⚡',
10
+ optimization: '🎯',
11
+ template_candidate: '🎨',
12
+ project_suggestion: '💡',
13
+ warning: '🚨',
14
+ suggestion: '💡',
15
+ };
3
16
 
4
17
  export function insightsCommand(): Command {
5
18
  return new Command('insights')
@@ -16,18 +29,37 @@ export function insightsCommand(): Command {
16
29
  });
17
30
 
18
31
  if (!insights?.length) {
19
- console.log('No active insights.');
32
+ console.log(`${icons.insight} ${c.dim('No active insights.')}`);
20
33
  return;
21
34
  }
22
35
 
23
- console.log(`${insights.length} insights:\n`);
36
+ console.log(header(`${insights.length} Insights`, icons.insight));
37
+
38
+ // Group by type
39
+ const byType: Record<string, number> = {};
24
40
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
41
  for (const ins of insights as any[]) {
26
- const priority = ins.priority >= 8 ? 'HIGH' : ins.priority >= 5 ? 'MEDIUM' : 'LOW';
27
- console.log(` [${ins.type}] [${priority}] ${ins.title}`);
28
- if (ins.description) console.log(` ${ins.description.slice(0, 150)}`);
42
+ byType[ins.type] = (byType[ins.type] || 0) + 1;
43
+ }
44
+ const typeSummary = Object.entries(byType)
45
+ .sort((a, b) => b[1] - a[1])
46
+ .map(([t, count]) => `${TYPE_ICONS[t] ?? '•'} ${c.cyan(t)} ${c.dim(`(${count})`)}`)
47
+ .join(' ');
48
+ console.log(` ${typeSummary}\n`);
49
+
50
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
51
+ for (const ins of insights as any[]) {
52
+ const typeIcon = TYPE_ICONS[ins.type] ?? '•';
53
+ const pBadge = priorityBadge(ins.priority ?? 0);
54
+ const typeTag = c.cyan(`[${ins.type}]`);
55
+
56
+ console.log(` ${typeIcon} ${typeTag} ${pBadge} ${c.value(ins.title)}`);
57
+ if (ins.description) {
58
+ console.log(` ${c.dim(ins.description.slice(0, 150))}`);
59
+ }
29
60
  console.log();
30
61
  }
62
+ console.log(divider());
31
63
  });
32
64
  });
33
65
  }
@@ -0,0 +1,24 @@
1
+ import { Command } from 'commander';
2
+ import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header, keyValue, divider } from '../colors.js';
4
+
5
+ export function learnCommand(): Command {
6
+ return new Command('learn')
7
+ .description('Trigger a learning cycle manually (pattern extraction + rule generation)')
8
+ .action(async () => {
9
+ await withIpc(async (client) => {
10
+ console.log(`${icons.brain} ${c.info('Running learning cycle...')}`);
11
+
12
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
+ const result: any = await client.request('learning.run', {});
14
+
15
+ console.log(header('Learning Cycle Complete', icons.bolt));
16
+ console.log(keyValue('New patterns', result.newPatterns ?? 0));
17
+ console.log(keyValue('Updated rules', result.updatedRules ?? 0));
18
+ console.log(keyValue('Pruned rules', result.prunedRules ?? 0));
19
+ console.log(keyValue('New anti-patterns', result.newAntipatterns ?? 0));
20
+ console.log(keyValue('Duration', `${result.duration ?? 0}ms`));
21
+ console.log(`\n${divider()}`);
22
+ });
23
+ });
24
+ }
@@ -1,30 +1,53 @@
1
1
  import { Command } from 'commander';
2
2
  import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header, divider } from '../colors.js';
3
4
 
4
5
  export function modulesCommand(): Command {
5
6
  return new Command('modules')
6
7
  .description('List registered code modules')
7
8
  .option('--language <lang>', 'Filter by language')
9
+ .option('-l, --limit <n>', 'Maximum results')
8
10
  .action(async (opts) => {
9
11
  await withIpc(async (client) => {
10
12
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
13
  const modules: any = await client.request('code.modules', {
12
14
  language: opts.language,
15
+ limit: opts.limit ? parseInt(opts.limit, 10) : undefined,
13
16
  });
14
17
 
15
18
  if (!modules?.length) {
16
- console.log('No code modules registered.');
19
+ console.log(`${icons.module} ${c.dim('No code modules registered.')}`);
17
20
  return;
18
21
  }
19
22
 
20
- console.log(`${modules.length} code modules:\n`);
23
+ console.log(header(`${modules.length} Code Modules`, icons.module));
24
+
25
+ // Group by language
26
+ const byLang: Record<string, number> = {};
27
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
28
+ for (const mod of modules as any[]) {
29
+ byLang[mod.language] = (byLang[mod.language] || 0) + 1;
30
+ }
31
+ const langSummary = Object.entries(byLang)
32
+ .sort((a, b) => b[1] - a[1])
33
+ .map(([lang, count]) => `${c.cyan(lang)} ${c.dim(`(${count})`)}`)
34
+ .join(' ');
35
+ console.log(` ${langSummary}\n`);
36
+
21
37
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
38
  for (const mod of modules as any[]) {
23
- console.log(` #${mod.id} [${mod.language}] ${mod.name}`);
24
- if (mod.description) console.log(` ${mod.description.slice(0, 120)}`);
25
- console.log(` File: ${mod.filePath} Reusability: ${mod.reusabilityScore ?? '?'}`);
39
+ const score = mod.reusabilityScore ?? mod.reusability_score ?? 0;
40
+ const scoreColor = score >= 0.7 ? c.green : score >= 0.4 ? c.orange : c.red;
41
+ const langTag = c.cyan(`[${mod.language}]`);
42
+
43
+ console.log(` ${c.dim(`#${mod.id}`)} ${langTag} ${c.value(mod.name)}`);
44
+ if (mod.description) {
45
+ console.log(` ${c.dim(mod.description.slice(0, 120))}`);
46
+ }
47
+ console.log(` ${c.label('File:')} ${c.dim(mod.filePath ?? mod.file_path)} ${c.label('Score:')} ${scoreColor(typeof score === 'number' ? score.toFixed(2) : score)}`);
26
48
  console.log();
27
49
  }
50
+ console.log(divider());
28
51
  });
29
52
  });
30
53
  }
@@ -1,5 +1,6 @@
1
1
  import { Command } from 'commander';
2
2
  import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header, keyValue, divider } from '../colors.js';
3
4
 
4
5
  export function networkCommand(): Command {
5
6
  return new Command('network')
@@ -13,7 +14,7 @@ export function networkCommand(): Command {
13
14
  const nodeId = parseInt(nodeIdStr, 10);
14
15
 
15
16
  if (!nodeType || isNaN(nodeId)) {
16
- console.error('Invalid node format. Use: --node error:42');
17
+ console.error(c.error('Invalid node format. Use: --node error:42'));
17
18
  return;
18
19
  }
19
20
 
@@ -25,14 +26,16 @@ export function networkCommand(): Command {
25
26
  });
26
27
 
27
28
  if (!related?.length) {
28
- console.log(`No connections found for ${nodeType}:${nodeId}`);
29
+ console.log(`${c.dim('No connections found for')} ${c.cyan(`${nodeType}:${nodeId}`)}`);
29
30
  return;
30
31
  }
31
32
 
32
- console.log(`Connections from ${nodeType}:${nodeId}:\n`);
33
+ console.log(header(`Connections from ${nodeType}:${nodeId}`, icons.synapse));
33
34
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
35
  for (const r of related as any[]) {
35
- console.log(` → ${r.nodeType}:${r.nodeId} (weight: ${(r.activation ?? r.weight ?? 0).toFixed(3)})`);
36
+ const weight = (r.activation ?? r.weight ?? 0);
37
+ const weightColor = weight >= 0.7 ? c.green : weight >= 0.3 ? c.orange : c.dim;
38
+ console.log(` ${c.cyan(icons.arrow)} ${c.value(`${r.nodeType}:${r.nodeId}`)} ${c.label('weight:')} ${weightColor(weight.toFixed(3))}`);
36
39
  }
37
40
  } else {
38
41
  // Show general network stats
@@ -43,19 +46,22 @@ export function networkCommand(): Command {
43
46
  limit: parseInt(opts.limit, 10),
44
47
  });
45
48
 
46
- console.log('Synapse Network Overview:\n');
47
- console.log(` Total synapses: ${stats.totalSynapses ?? 0}`);
48
- console.log(` Average weight: ${(stats.avgWeight ?? 0).toFixed(3)}`);
49
+ console.log(header('Synapse Network', icons.synapse));
50
+ console.log(keyValue('Total synapses', stats.totalSynapses ?? 0));
51
+ console.log(keyValue('Average weight', (stats.avgWeight ?? 0).toFixed(3)));
49
52
  console.log();
50
53
 
51
54
  if (overview?.strongestSynapses?.length) {
52
- console.log('Strongest connections:');
55
+ console.log(` ${c.purple.bold('Strongest connections:')}`);
53
56
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
54
57
  for (const s of overview.strongestSynapses as any[]) {
55
- console.log(` ${s.source} ${s.target} [${s.type}] weight: ${(s.weight ?? 0).toFixed(3)}`);
58
+ const weight = (s.weight ?? 0);
59
+ const weightColor = weight >= 0.7 ? c.green : weight >= 0.3 ? c.orange : c.dim;
60
+ console.log(` ${c.dim(s.source)} ${c.cyan(icons.arrow)} ${c.dim(s.target)} ${c.label(`[${s.type}]`)} ${weightColor(weight.toFixed(3))}`);
56
61
  }
57
62
  }
58
63
  }
64
+ console.log(`\n${divider()}`);
59
65
  });
60
66
  });
61
67
  }