@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
@@ -1,42 +1,119 @@
1
1
  import { Command } from 'commander';
2
2
  import { withIpc } from '../ipc-helper.js';
3
+ import { c, icons, header, statusBadge, divider } from '../colors.js';
3
4
 
4
5
  export function queryCommand(): Command {
5
6
  return new Command('query')
6
- .description('Search for errors and solutions')
7
- .argument('<search>', 'Error message or description to search for')
8
- .option('-l, --limit <n>', 'Maximum results', '10')
7
+ .description('Search errors, code modules, and insights')
8
+ .argument('<search>', 'Search term')
9
+ .option('-l, --limit <n>', 'Maximum results per category', '10')
10
+ .option('--errors-only', 'Only search errors')
11
+ .option('--modules-only', 'Only search code modules')
12
+ .option('--insights-only', 'Only search insights')
13
+ .option('--page <n>', 'Page number (starting from 1)', '1')
9
14
  .action(async (search: string, opts) => {
10
15
  await withIpc(async (client) => {
11
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
12
- const results: any = await client.request('error.query', {
13
- search,
14
- limit: parseInt(opts.limit, 10),
15
- });
16
-
17
- if (!results?.length) {
18
- console.log('No matching errors found.');
19
- return;
16
+ const limit = parseInt(opts.limit, 10);
17
+ const page = parseInt(opts.page, 10) || 1;
18
+ const offset = (page - 1) * limit;
19
+ const searchAll = !opts.errorsOnly && !opts.modulesOnly && !opts.insightsOnly;
20
+ let totalResults = 0;
21
+
22
+ // --- Errors ---
23
+ if (searchAll || opts.errorsOnly) {
24
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
25
+ const results: any = await client.request('error.query', {
26
+ search,
27
+ limit: limit + offset,
28
+ });
29
+
30
+ const errors = Array.isArray(results) ? results.slice(offset, offset + limit) : [];
31
+ if (errors.length > 0) {
32
+ totalResults += errors.length;
33
+ console.log(header(`Errors matching "${search}"`, icons.error));
34
+
35
+ for (const err of errors) {
36
+ const badge = statusBadge(err.resolved ? 'resolved' : 'open');
37
+ const typeTag = c.purple(err.errorType ?? 'unknown');
38
+ console.log(` ${c.dim(`#${err.id}`)} ${badge} ${typeTag}`);
39
+ console.log(` ${c.dim((err.message ?? '').slice(0, 120))}`);
40
+
41
+ // Get solutions for this error
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ const solutions: any = await client.request('solution.query', { error_id: err.id });
44
+ if (solutions?.length > 0) {
45
+ console.log(` ${c.green(`${icons.check} ${solutions.length} solution(s)`)}`);
46
+ for (const sol of solutions.slice(0, 3)) {
47
+ console.log(` ${c.dim(icons.corner)} ${c.dim((sol.description ?? '').slice(0, 100))}`);
48
+ }
49
+ }
50
+ console.log();
51
+ }
52
+ }
20
53
  }
21
54
 
22
- console.log(`Found ${results.length} errors:\n`);
23
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
24
- for (const err of results as any[]) {
25
- const status = err.resolved ? 'RESOLVED' : 'OPEN';
26
- console.log(` #${err.id} [${status}] ${err.errorType ?? 'unknown'}`);
27
- console.log(` ${(err.message ?? '').slice(0, 120)}`);
55
+ // --- Code Modules ---
56
+ if (searchAll || opts.modulesOnly) {
57
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
+ const modules: any = await client.request('code.find', {
59
+ query: search,
60
+ limit: limit + offset,
61
+ });
62
+
63
+ const mods = Array.isArray(modules) ? modules.slice(offset, offset + limit) : [];
64
+ if (mods.length > 0) {
65
+ totalResults += mods.length;
66
+ console.log(header(`Modules matching "${search}"`, icons.module));
28
67
 
29
- // Get solutions for this error
68
+ for (const mod of mods) {
69
+ const score = mod.reusability_score ?? mod.reusabilityScore ?? 0;
70
+ const scoreColor = score >= 0.7 ? c.green : score >= 0.4 ? c.orange : c.red;
71
+ console.log(` ${c.dim(`#${mod.id}`)} ${c.cyan(`[${mod.language}]`)} ${c.value(mod.name)}`);
72
+ console.log(` ${c.label('File:')} ${c.dim(mod.file_path ?? mod.filePath)} ${c.label('Score:')} ${scoreColor(typeof score === 'number' ? score.toFixed(2) : score)}`);
73
+ if (mod.description) {
74
+ console.log(` ${c.dim(mod.description.slice(0, 120))}`);
75
+ }
76
+ console.log();
77
+ }
78
+ }
79
+ }
80
+
81
+ // --- Insights ---
82
+ if (searchAll || opts.insightsOnly) {
30
83
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
- const solutions: any = await client.request('solution.query', { error_id: err.id });
32
- if (solutions?.length > 0) {
33
- console.log(` Solutions: ${solutions.length}`);
34
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
35
- for (const sol of solutions.slice(0, 3) as any[]) {
36
- console.log(` - #${sol.id}: ${(sol.description ?? '').slice(0, 100)}`);
84
+ const insights: any = await client.request('research.insights', {
85
+ activeOnly: true,
86
+ limit: 100,
87
+ });
88
+
89
+ // Client-side search since insights API may not support text search
90
+ const allInsights = Array.isArray(insights) ? insights : [];
91
+ const searchLower = search.toLowerCase();
92
+ const matched = allInsights.filter((i: { title?: string; description?: string }) =>
93
+ (i.title ?? '').toLowerCase().includes(searchLower) ||
94
+ (i.description ?? '').toLowerCase().includes(searchLower)
95
+ ).slice(offset, offset + limit);
96
+
97
+ if (matched.length > 0) {
98
+ totalResults += matched.length;
99
+ console.log(header(`Insights matching "${search}"`, icons.insight));
100
+
101
+ for (const ins of matched) {
102
+ const typeTag = c.cyan(`[${ins.type}]`);
103
+ console.log(` ${typeTag} ${c.value(ins.title)}`);
104
+ if (ins.description) {
105
+ console.log(` ${c.dim(ins.description.slice(0, 150))}`);
106
+ }
107
+ console.log();
37
108
  }
38
109
  }
39
- console.log();
110
+ }
111
+
112
+ if (totalResults === 0) {
113
+ console.log(`\n${icons.search} ${c.dim(`No results found for "${search}".`)}`);
114
+ } else {
115
+ console.log(` ${c.dim(`Page ${page} — showing ${totalResults} result(s). Use --page ${page + 1} for more.`)}`);
116
+ console.log(divider());
40
117
  }
41
118
  });
42
119
  });
@@ -3,6 +3,7 @@ import { spawn } from 'node:child_process';
3
3
  import fs from 'node:fs';
4
4
  import path from 'node:path';
5
5
  import { getDataDir } from '../../utils/paths.js';
6
+ import { c, icons } from '../colors.js';
6
7
 
7
8
  export function startCommand(): Command {
8
9
  return new Command('start')
@@ -17,7 +18,7 @@ export function startCommand(): Command {
17
18
  const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
18
19
  try {
19
20
  process.kill(pid, 0); // Check if process exists
20
- console.log(`Brain daemon is already running (PID: ${pid})`);
21
+ console.log(`${icons.brain} Brain daemon is ${c.green('already running')} ${c.dim(`(PID: ${pid})`)}`);
21
22
  return;
22
23
  } catch {
23
24
  // PID file stale, remove it
@@ -45,14 +46,14 @@ export function startCommand(): Command {
45
46
  });
46
47
  child.unref();
47
48
 
48
- console.log(`Brain daemon starting (PID: ${child.pid})`);
49
+ console.log(`${icons.brain} ${c.info('Brain daemon starting')} ${c.dim(`(PID: ${child.pid})`)}`);
49
50
 
50
51
  // Wait briefly for PID file to appear
51
52
  setTimeout(() => {
52
53
  if (fs.existsSync(pidPath)) {
53
- console.log('Brain daemon started successfully.');
54
+ console.log(`${icons.ok} ${c.success('Brain daemon started successfully.')}`);
54
55
  } else {
55
- console.log('Brain daemon may still be starting. Check: brain status');
56
+ console.log(`${icons.clock} ${c.warn('Brain daemon may still be starting.')} Check: ${c.cyan('brain status')}`);
56
57
  }
57
58
  }, 1000);
58
59
  });
@@ -3,6 +3,7 @@ import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { getDataDir } from '../../utils/paths.js';
5
5
  import { withIpc } from '../ipc-helper.js';
6
+ import { c, icons, header, keyValue, divider } from '../colors.js';
6
7
 
7
8
  export function statusCommand(): Command {
8
9
  return new Command('status')
@@ -11,7 +12,7 @@ export function statusCommand(): Command {
11
12
  const pidPath = path.join(getDataDir(), 'brain.pid');
12
13
 
13
14
  if (!fs.existsSync(pidPath)) {
14
- console.log('Brain Daemon: NOT RUNNING');
15
+ console.log(`${icons.brain} Brain Daemon: ${c.red.bold('NOT RUNNING')}`);
15
16
  return;
16
17
  }
17
18
 
@@ -23,11 +24,12 @@ export function statusCommand(): Command {
23
24
  } catch { /* not running */ }
24
25
 
25
26
  if (!running) {
26
- console.log('Brain Daemon: NOT RUNNING (stale PID file)');
27
+ console.log(`${icons.brain} Brain Daemon: ${c.red.bold('NOT RUNNING')} ${c.dim('(stale PID file)')}`);
27
28
  return;
28
29
  }
29
30
 
30
- console.log(`Brain Daemon: RUNNING (PID ${pid})`);
31
+ console.log(header('Brain Status', icons.brain));
32
+ console.log(` ${c.green(`${icons.dot} RUNNING`)} ${c.dim(`(PID ${pid})`)}`);
31
33
 
32
34
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
33
35
  await withIpc(async (client) => {
@@ -43,27 +45,28 @@ export function statusCommand(): Command {
43
45
  dbSize = `${(stat.size / 1024 / 1024).toFixed(1)} MB`;
44
46
  } catch { /* ignore */ }
45
47
 
46
- console.log(`Database: ${dbPath} (${dbSize})`);
48
+ console.log(keyValue('Database', `${dbPath} (${dbSize})`));
47
49
  console.log();
48
50
 
49
- console.log('Error Brain:');
50
- console.log(` Errors: ${summary.errors?.total ?? 0} total, ${summary.errors?.unresolved ?? 0} unresolved, ${summary.errors?.last7d ?? 0} last 7d`);
51
- console.log(` Solutions: ${summary.solutions?.total ?? 0}`);
52
- console.log(` Rules: ${summary.rules?.active ?? 0} active`);
53
- console.log(` Anti-Patterns: ${summary.antipatterns?.total ?? 0}`);
51
+ console.log(` ${icons.error} ${c.purple.bold('Error Brain')}`);
52
+ console.log(` ${c.label('Errors:')} ${c.value(summary.errors?.total ?? 0)} total, ${c.red(summary.errors?.unresolved ?? 0)} unresolved, ${c.dim(`${summary.errors?.last7d ?? 0} last 7d`)}`);
53
+ console.log(` ${c.label('Solutions:')} ${c.value(summary.solutions?.total ?? 0)}`);
54
+ console.log(` ${c.label('Rules:')} ${c.green(summary.rules?.active ?? 0)} active`);
55
+ console.log(` ${c.label('Anti-Pat.:')} ${c.value(summary.antipatterns?.total ?? 0)}`);
54
56
  console.log();
55
57
 
56
- console.log('Code Brain:');
57
- console.log(` Modules: ${summary.modules?.total ?? 0} registered`);
58
+ console.log(` ${icons.module} ${c.blue.bold('Code Brain')}`);
59
+ console.log(` ${c.label('Modules:')} ${c.value(summary.modules?.total ?? 0)} registered`);
58
60
  console.log();
59
61
 
60
- console.log('Synapse Network:');
61
- console.log(` Synapses: ${network.totalSynapses ?? 0}`);
62
- console.log(` Avg weight: ${(network.avgWeight ?? 0).toFixed(2)}`);
62
+ console.log(` ${icons.synapse} ${c.cyan.bold('Synapse Network')}`);
63
+ console.log(` ${c.label('Synapses:')} ${c.value(network.totalSynapses ?? 0)}`);
64
+ console.log(` ${c.label('Avg weight:')} ${c.value((network.avgWeight ?? 0).toFixed(2))}`);
63
65
  console.log();
64
66
 
65
- console.log('Research Brain:');
66
- console.log(` Insights: ${summary.insights?.active ?? 0} active`);
67
+ console.log(` ${icons.insight} ${c.orange.bold('Research Brain')}`);
68
+ console.log(` ${c.label('Insights:')} ${c.value(summary.insights?.active ?? 0)} active`);
69
+ console.log(`\n${divider()}`);
67
70
  });
68
71
  });
69
72
  }
@@ -2,6 +2,7 @@ import { Command } from 'commander';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { getDataDir } from '../../utils/paths.js';
5
+ import { c, icons } from '../colors.js';
5
6
 
6
7
  export function stopCommand(): Command {
7
8
  return new Command('stop')
@@ -10,7 +11,7 @@ export function stopCommand(): Command {
10
11
  const pidPath = path.join(getDataDir(), 'brain.pid');
11
12
 
12
13
  if (!fs.existsSync(pidPath)) {
13
- console.log('Brain daemon is not running (no PID file found).');
14
+ console.log(`${icons.brain} ${c.dim('Brain daemon is not running (no PID file found).')}`);
14
15
  return;
15
16
  }
16
17
 
@@ -18,12 +19,12 @@ export function stopCommand(): Command {
18
19
 
19
20
  try {
20
21
  process.kill(pid, 'SIGTERM');
21
- console.log(`Brain daemon stopped (PID: ${pid})`);
22
+ console.log(`${icons.brain} ${c.success('Brain daemon stopped')} ${c.dim(`(PID: ${pid})`)}`);
22
23
  } catch (err) {
23
24
  if ((err as NodeJS.ErrnoException).code === 'ESRCH') {
24
- console.log('Brain daemon was not running (stale PID file removed).');
25
+ console.log(`${icons.brain} ${c.dim('Brain daemon was not running (stale PID file removed).')}`);
25
26
  } else {
26
- console.error(`Failed to stop daemon: ${err}`);
27
+ console.error(`${icons.error} ${c.error(`Failed to stop daemon: ${err}`)}`);
27
28
  }
28
29
  }
29
30
 
@@ -1,5 +1,6 @@
1
1
  import { IpcClient } from '../ipc/client.js';
2
2
  import { getPipeName } from '../utils/paths.js';
3
+ import { c, icons } from './colors.js';
3
4
 
4
5
  export async function withIpc<T>(fn: (client: IpcClient) => Promise<T>): Promise<T> {
5
6
  const client = new IpcClient(getPipeName(), 5000);
@@ -8,11 +9,11 @@ export async function withIpc<T>(fn: (client: IpcClient) => Promise<T>): Promise
8
9
  return await fn(client);
9
10
  } catch (err) {
10
11
  if (err instanceof Error && err.message.includes('ENOENT')) {
11
- console.error('Brain daemon is not running. Start it with: brain start');
12
+ console.error(`${icons.error} ${c.error('Brain daemon is not running.')} Start it with: ${c.cyan('brain start')}`);
12
13
  } else if (err instanceof Error && err.message.includes('ECONNREFUSED')) {
13
- console.error('Brain daemon is not responding. Try: brain stop && brain start');
14
+ console.error(`${icons.error} ${c.error('Brain daemon is not responding.')} Try: ${c.cyan('brain stop && brain start')}`);
14
15
  } else {
15
- console.error(`Error: ${err instanceof Error ? err.message : err}`);
16
+ console.error(`${icons.error} ${c.error(err instanceof Error ? err.message : String(err))}`);
16
17
  }
17
18
  process.exit(1);
18
19
  } finally {
@@ -1,77 +1,77 @@
1
- import type { ExportInfo } from '../types/code.types.js';
2
- import * as tsParser from './parsers/typescript.js';
3
- import * as pyParser from './parsers/python.js';
4
- import * as genericParser from './parsers/generic.js';
5
-
6
- export interface AnalysisResult {
7
- exports: ExportInfo[];
8
- externalDeps: string[];
9
- internalDeps: string[];
10
- isPure: boolean;
11
- hasTypeAnnotations: boolean;
12
- linesOfCode: number;
13
- }
14
-
15
- const SIDE_EFFECT_PATTERNS = [
16
- 'fs.', 'process.exit', 'process.env', 'console.', 'fetch(',
17
- 'XMLHttpRequest', 'document.', 'window.',
18
- 'global.', 'require(',
19
- ];
20
-
21
- function getParser(language: string) {
22
- switch (language) {
23
- case 'typescript':
24
- case 'javascript':
25
- return tsParser;
26
- case 'python':
27
- return pyParser;
28
- default:
29
- return genericParser;
30
- }
31
- }
32
-
33
- export function analyzeCode(source: string, language: string): AnalysisResult {
34
- const parser = getParser(language);
35
- const exports = parser.extractExports(source);
36
- const { external, internal } = parser.extractImports(source);
37
- const isPure = checkPurity(source);
38
- const typed = parser.hasTypeAnnotations(source);
39
- const linesOfCode = source.split('\n').filter(l => l.trim().length > 0).length;
40
-
41
- return {
42
- exports,
43
- externalDeps: external,
44
- internalDeps: internal,
45
- isPure,
46
- hasTypeAnnotations: typed,
47
- linesOfCode,
48
- };
49
- }
50
-
51
- export function checkPurity(source: string): boolean {
52
- return !SIDE_EFFECT_PATTERNS.some(p => source.includes(p));
53
- }
54
-
55
- export function measureCohesion(exports: ExportInfo[]): number {
56
- if (exports.length <= 1) return 1.0;
57
-
58
- const names = exports.map(e =>
59
- e.name
60
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
61
- .replace(/([a-z\d])([A-Z])/g, '$1 $2')
62
- .toLowerCase()
63
- .split(/\s+/)
64
- );
65
-
66
- const vocab = new Set<string>();
67
- names.forEach(tokens => tokens.forEach(t => vocab.add(t)));
68
-
69
- let sharedTokens = 0;
70
- for (const token of vocab) {
71
- const count = names.filter(n => n.includes(token)).length;
72
- if (count > 1) sharedTokens += count;
73
- }
74
-
75
- const maxPossible = names.length * vocab.size;
76
- return maxPossible === 0 ? 0 : sharedTokens / maxPossible;
77
- }
1
+ import type { ExportInfo } from '../types/code.types.js';
2
+ import * as tsParser from './parsers/typescript.js';
3
+ import * as pyParser from './parsers/python.js';
4
+ import * as genericParser from './parsers/generic.js';
5
+
6
+ export interface AnalysisResult {
7
+ exports: ExportInfo[];
8
+ externalDeps: string[];
9
+ internalDeps: string[];
10
+ isPure: boolean;
11
+ hasTypeAnnotations: boolean;
12
+ linesOfCode: number;
13
+ }
14
+
15
+ const SIDE_EFFECT_PATTERNS = [
16
+ 'fs.', 'process.exit', 'process.env', 'console.', 'fetch(',
17
+ 'XMLHttpRequest', 'document.', 'window.',
18
+ 'global.', 'require(',
19
+ ];
20
+
21
+ function getParser(language: string) {
22
+ switch (language) {
23
+ case 'typescript':
24
+ case 'javascript':
25
+ return tsParser;
26
+ case 'python':
27
+ return pyParser;
28
+ default:
29
+ return genericParser;
30
+ }
31
+ }
32
+
33
+ export function analyzeCode(source: string, language: string): AnalysisResult {
34
+ const parser = getParser(language);
35
+ const exports = parser.extractExports(source);
36
+ const { external, internal } = parser.extractImports(source);
37
+ const isPure = checkPurity(source);
38
+ const typed = parser.hasTypeAnnotations(source);
39
+ const linesOfCode = source.split('\n').filter(l => l.trim().length > 0).length;
40
+
41
+ return {
42
+ exports,
43
+ externalDeps: external,
44
+ internalDeps: internal,
45
+ isPure,
46
+ hasTypeAnnotations: typed,
47
+ linesOfCode,
48
+ };
49
+ }
50
+
51
+ export function checkPurity(source: string): boolean {
52
+ return !SIDE_EFFECT_PATTERNS.some(p => source.includes(p));
53
+ }
54
+
55
+ export function measureCohesion(exports: ExportInfo[]): number {
56
+ if (exports.length <= 1) return 1.0;
57
+
58
+ const names = exports.map(e =>
59
+ e.name
60
+ .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
61
+ .replace(/([a-z\d])([A-Z])/g, '$1 $2')
62
+ .toLowerCase()
63
+ .split(/\s+/)
64
+ );
65
+
66
+ const vocab = new Set<string>();
67
+ names.forEach(tokens => tokens.forEach(t => vocab.add(t)));
68
+
69
+ let sharedTokens = 0;
70
+ for (const token of vocab) {
71
+ const count = names.filter(n => n.includes(token)).length;
72
+ if (count > 1) sharedTokens += count;
73
+ }
74
+
75
+ const maxPossible = names.length * vocab.size;
76
+ return maxPossible === 0 ? 0 : sharedTokens / maxPossible;
77
+ }
@@ -1,87 +1,87 @@
1
- import { sha256 } from '../utils/hash.js';
2
-
3
- export function fingerprintCode(source: string, language: string): string {
4
- let normalized = stripComments(source, language);
5
- normalized = normalized.replace(/\s+/g, ' ').trim();
6
- normalized = normalizeIdentifiers(normalized, language);
7
- normalized = normalized.replace(/'[^']*'/g, "'<STR>'");
8
- normalized = normalized.replace(/"[^"]*"/g, '"<STR>"');
9
- normalized = normalized.replace(/`[^`]*`/g, '`<STR>`');
10
- normalized = normalized.replace(/\b\d+\b/g, '<NUM>');
11
- return sha256(normalized);
12
- }
13
-
14
- export function stripComments(source: string, language: string): string {
15
- switch (language) {
16
- case 'typescript':
17
- case 'javascript':
18
- case 'java':
19
- case 'go':
20
- case 'rust':
21
- case 'c':
22
- case 'cpp':
23
- return source
24
- .replace(/\/\/.*$/gm, '')
25
- .replace(/\/\*[\s\S]*?\*\//g, '');
26
- case 'python':
27
- return source
28
- .replace(/#.*$/gm, '')
29
- .replace(/"""[\s\S]*?"""/g, '')
30
- .replace(/'''[\s\S]*?'''/g, '');
31
- default:
32
- return source
33
- .replace(/\/\/.*$/gm, '')
34
- .replace(/#.*$/gm, '');
35
- }
36
- }
37
-
38
- function normalizeIdentifiers(source: string, language: string): string {
39
- const importNames = extractImportNames(source, language);
40
- const keywords = getLanguageKeywords(language);
41
- const preserve = new Set([...importNames, ...keywords]);
42
-
43
- return source.replace(/\b[a-zA-Z_]\w*\b/g, (match) => {
44
- if (preserve.has(match)) return match;
45
- if (match[0] === match[0]!.toUpperCase() && match[0] !== match[0]!.toLowerCase()) return '<CLASS>';
46
- return '<VAR>';
47
- });
48
- }
49
-
50
- function extractImportNames(source: string, language: string): string[] {
51
- const names: string[] = [];
52
-
53
- if (language === 'typescript' || language === 'javascript') {
54
- const re = /import\s+(?:\{([^}]+)\}|\*\s+as\s+(\w+)|(\w+))/g;
55
- let m: RegExpExecArray | null;
56
- while ((m = re.exec(source)) !== null) {
57
- if (m[1]) names.push(...m[1].split(',').map(s => s.trim().split(/\s+as\s+/).pop()!));
58
- if (m[2]) names.push(m[2]);
59
- if (m[3]) names.push(m[3]);
60
- }
61
- } else if (language === 'python') {
62
- const re = /(?:from\s+\S+\s+)?import\s+(.+)/g;
63
- let m: RegExpExecArray | null;
64
- while ((m = re.exec(source)) !== null) {
65
- names.push(...m[1]!.split(',').map(s => {
66
- const parts = s.trim().split(/\s+as\s+/);
67
- return parts[parts.length - 1]!;
68
- }));
69
- }
70
- }
71
-
72
- return names.filter(Boolean);
73
- }
74
-
75
- function getLanguageKeywords(language: string): string[] {
76
- const common = ['if', 'else', 'for', 'while', 'return', 'break', 'continue', 'switch', 'case', 'default', 'try', 'catch', 'throw', 'new', 'delete', 'true', 'false', 'null', 'undefined', 'void'];
77
-
78
- const langKeywords: Record<string, string[]> = {
79
- typescript: [...common, 'const', 'let', 'var', 'function', 'class', 'interface', 'type', 'enum', 'import', 'export', 'from', 'async', 'await', 'extends', 'implements', 'readonly', 'private', 'public', 'protected', 'static', 'abstract', 'as', 'is', 'in', 'of', 'typeof', 'keyof', 'infer', 'never', 'unknown', 'any', 'string', 'number', 'boolean', 'symbol', 'object'],
80
- javascript: [...common, 'const', 'let', 'var', 'function', 'class', 'import', 'export', 'from', 'async', 'await', 'extends', 'typeof', 'instanceof', 'in', 'of', 'this', 'super', 'yield'],
81
- python: ['def', 'class', 'import', 'from', 'if', 'elif', 'else', 'for', 'while', 'return', 'yield', 'break', 'continue', 'try', 'except', 'finally', 'raise', 'with', 'as', 'pass', 'lambda', 'True', 'False', 'None', 'and', 'or', 'not', 'in', 'is', 'global', 'nonlocal', 'assert', 'async', 'await', 'self'],
82
- rust: [...common, 'fn', 'let', 'mut', 'pub', 'struct', 'enum', 'impl', 'trait', 'use', 'mod', 'crate', 'self', 'super', 'match', 'loop', 'move', 'ref', 'where', 'async', 'await', 'dyn', 'Box', 'Vec', 'String', 'Option', 'Result', 'Some', 'None', 'Ok', 'Err'],
83
- go: [...common, 'func', 'package', 'import', 'type', 'struct', 'interface', 'map', 'chan', 'go', 'defer', 'select', 'range', 'var', 'const', 'nil', 'make', 'len', 'append', 'cap', 'copy', 'close'],
84
- };
85
-
86
- return langKeywords[language] ?? common;
87
- }
1
+ import { sha256 } from '../utils/hash.js';
2
+
3
+ export function fingerprintCode(source: string, language: string): string {
4
+ let normalized = stripComments(source, language);
5
+ normalized = normalized.replace(/\s+/g, ' ').trim();
6
+ normalized = normalizeIdentifiers(normalized, language);
7
+ normalized = normalized.replace(/'[^']*'/g, "'<STR>'");
8
+ normalized = normalized.replace(/"[^"]*"/g, '"<STR>"');
9
+ normalized = normalized.replace(/`[^`]*`/g, '`<STR>`');
10
+ normalized = normalized.replace(/\b\d+\b/g, '<NUM>');
11
+ return sha256(normalized);
12
+ }
13
+
14
+ export function stripComments(source: string, language: string): string {
15
+ switch (language) {
16
+ case 'typescript':
17
+ case 'javascript':
18
+ case 'java':
19
+ case 'go':
20
+ case 'rust':
21
+ case 'c':
22
+ case 'cpp':
23
+ return source
24
+ .replace(/\/\/.*$/gm, '')
25
+ .replace(/\/\*[\s\S]*?\*\//g, '');
26
+ case 'python':
27
+ return source
28
+ .replace(/#.*$/gm, '')
29
+ .replace(/"""[\s\S]*?"""/g, '')
30
+ .replace(/'''[\s\S]*?'''/g, '');
31
+ default:
32
+ return source
33
+ .replace(/\/\/.*$/gm, '')
34
+ .replace(/#.*$/gm, '');
35
+ }
36
+ }
37
+
38
+ function normalizeIdentifiers(source: string, language: string): string {
39
+ const importNames = extractImportNames(source, language);
40
+ const keywords = getLanguageKeywords(language);
41
+ const preserve = new Set([...importNames, ...keywords]);
42
+
43
+ return source.replace(/\b[a-zA-Z_]\w*\b/g, (match) => {
44
+ if (preserve.has(match)) return match;
45
+ if (match[0] === match[0]!.toUpperCase() && match[0] !== match[0]!.toLowerCase()) return '<CLASS>';
46
+ return '<VAR>';
47
+ });
48
+ }
49
+
50
+ function extractImportNames(source: string, language: string): string[] {
51
+ const names: string[] = [];
52
+
53
+ if (language === 'typescript' || language === 'javascript') {
54
+ const re = /import\s+(?:\{([^}]+)\}|\*\s+as\s+(\w+)|(\w+))/g;
55
+ let m: RegExpExecArray | null;
56
+ while ((m = re.exec(source)) !== null) {
57
+ if (m[1]) names.push(...m[1].split(',').map(s => s.trim().split(/\s+as\s+/).pop()!));
58
+ if (m[2]) names.push(m[2]);
59
+ if (m[3]) names.push(m[3]);
60
+ }
61
+ } else if (language === 'python') {
62
+ const re = /(?:from\s+\S+\s+)?import\s+(.+)/g;
63
+ let m: RegExpExecArray | null;
64
+ while ((m = re.exec(source)) !== null) {
65
+ names.push(...m[1]!.split(',').map(s => {
66
+ const parts = s.trim().split(/\s+as\s+/);
67
+ return parts[parts.length - 1]!;
68
+ }));
69
+ }
70
+ }
71
+
72
+ return names.filter(Boolean);
73
+ }
74
+
75
+ function getLanguageKeywords(language: string): string[] {
76
+ const common = ['if', 'else', 'for', 'while', 'return', 'break', 'continue', 'switch', 'case', 'default', 'try', 'catch', 'throw', 'new', 'delete', 'true', 'false', 'null', 'undefined', 'void'];
77
+
78
+ const langKeywords: Record<string, string[]> = {
79
+ typescript: [...common, 'const', 'let', 'var', 'function', 'class', 'interface', 'type', 'enum', 'import', 'export', 'from', 'async', 'await', 'extends', 'implements', 'readonly', 'private', 'public', 'protected', 'static', 'abstract', 'as', 'is', 'in', 'of', 'typeof', 'keyof', 'infer', 'never', 'unknown', 'any', 'string', 'number', 'boolean', 'symbol', 'object'],
80
+ javascript: [...common, 'const', 'let', 'var', 'function', 'class', 'import', 'export', 'from', 'async', 'await', 'extends', 'typeof', 'instanceof', 'in', 'of', 'this', 'super', 'yield'],
81
+ python: ['def', 'class', 'import', 'from', 'if', 'elif', 'else', 'for', 'while', 'return', 'yield', 'break', 'continue', 'try', 'except', 'finally', 'raise', 'with', 'as', 'pass', 'lambda', 'True', 'False', 'None', 'and', 'or', 'not', 'in', 'is', 'global', 'nonlocal', 'assert', 'async', 'await', 'self'],
82
+ rust: [...common, 'fn', 'let', 'mut', 'pub', 'struct', 'enum', 'impl', 'trait', 'use', 'mod', 'crate', 'self', 'super', 'match', 'loop', 'move', 'ref', 'where', 'async', 'await', 'dyn', 'Box', 'Vec', 'String', 'Option', 'Result', 'Some', 'None', 'Ok', 'Err'],
83
+ go: [...common, 'func', 'package', 'import', 'type', 'struct', 'interface', 'map', 'chan', 'go', 'defer', 'select', 'range', 'var', 'const', 'nil', 'make', 'len', 'append', 'cap', 'copy', 'close'],
84
+ };
85
+
86
+ return langKeywords[language] ?? common;
87
+ }