@timmeck/brain 1.9.0 → 2.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 (253) hide show
  1. package/README.md +33 -0
  2. package/brain.log +3876 -0
  3. package/{src/cli/commands/dashboard.ts → dashboard.html} +694 -807
  4. package/dist/api/server.d.ts +4 -18
  5. package/dist/api/server.js +4 -173
  6. package/dist/api/server.js.map +1 -1
  7. package/dist/brain.d.ts +2 -0
  8. package/dist/brain.js +15 -4
  9. package/dist/brain.js.map +1 -1
  10. package/dist/cli/colors.d.ts +4 -25
  11. package/dist/cli/colors.js +3 -89
  12. package/dist/cli/colors.js.map +1 -1
  13. package/dist/cli/commands/dashboard.js +21 -2
  14. package/dist/cli/commands/dashboard.js.map +1 -1
  15. package/dist/cli/commands/peers.d.ts +2 -0
  16. package/dist/cli/commands/peers.js +38 -0
  17. package/dist/cli/commands/peers.js.map +1 -0
  18. package/dist/cli/commands/status.js +0 -1
  19. package/dist/cli/commands/status.js.map +1 -1
  20. package/dist/config.js +2 -29
  21. package/dist/config.js.map +1 -1
  22. package/dist/db/connection.d.ts +1 -2
  23. package/dist/db/connection.js +1 -18
  24. package/dist/db/connection.js.map +1 -1
  25. package/dist/index.js +3 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/ipc/__tests__/protocol.test.d.ts +1 -0
  28. package/dist/ipc/__tests__/protocol.test.js +117 -0
  29. package/dist/ipc/__tests__/protocol.test.js.map +1 -0
  30. package/dist/ipc/client.d.ts +1 -16
  31. package/dist/ipc/client.js +1 -100
  32. package/dist/ipc/client.js.map +1 -1
  33. package/dist/ipc/protocol.d.ts +1 -8
  34. package/dist/ipc/protocol.js +1 -28
  35. package/dist/ipc/protocol.js.map +1 -1
  36. package/dist/ipc/router.d.ts +2 -0
  37. package/dist/ipc/router.js +30 -0
  38. package/dist/ipc/router.js.map +1 -1
  39. package/dist/ipc/server.d.ts +1 -22
  40. package/dist/ipc/server.js +1 -163
  41. package/dist/ipc/server.js.map +1 -1
  42. package/dist/learning/confidence-scorer.d.ts +2 -5
  43. package/dist/learning/confidence-scorer.js +4 -19
  44. package/dist/learning/confidence-scorer.js.map +1 -1
  45. package/dist/learning/decay.js +2 -3
  46. package/dist/learning/decay.js.map +1 -1
  47. package/dist/learning/learning-engine.d.ts +2 -5
  48. package/dist/learning/learning-engine.js +3 -15
  49. package/dist/learning/learning-engine.js.map +1 -1
  50. package/dist/mcp/http-server.d.ts +1 -7
  51. package/dist/mcp/http-server.js +6 -117
  52. package/dist/mcp/http-server.js.map +1 -1
  53. package/dist/mcp/server.js +5 -61
  54. package/dist/mcp/server.js.map +1 -1
  55. package/dist/mcp/tools.js +36 -0
  56. package/dist/mcp/tools.js.map +1 -1
  57. package/dist/parsing/parsers/compiler.js +1 -1
  58. package/dist/parsing/parsers/compiler.js.map +1 -1
  59. package/dist/research/research-engine.d.ts +2 -6
  60. package/dist/research/research-engine.js +3 -23
  61. package/dist/research/research-engine.js.map +1 -1
  62. package/dist/services/synapse.service.d.ts +3 -3
  63. package/dist/signals/__tests__/fingerprint.test.d.ts +1 -0
  64. package/dist/signals/__tests__/fingerprint.test.js +118 -0
  65. package/dist/signals/__tests__/fingerprint.test.js.map +1 -0
  66. package/dist/synapses/activation.d.ts +3 -13
  67. package/dist/synapses/activation.js +2 -49
  68. package/dist/synapses/activation.js.map +1 -1
  69. package/dist/synapses/decay.d.ts +2 -11
  70. package/dist/synapses/decay.js +2 -26
  71. package/dist/synapses/decay.js.map +1 -1
  72. package/dist/synapses/hebbian.d.ts +2 -13
  73. package/dist/synapses/hebbian.js +2 -35
  74. package/dist/synapses/hebbian.js.map +1 -1
  75. package/dist/synapses/pathfinder.d.ts +2 -14
  76. package/dist/synapses/pathfinder.js +2 -49
  77. package/dist/synapses/pathfinder.js.map +1 -1
  78. package/dist/synapses/synapse-manager.d.ts +7 -23
  79. package/dist/synapses/synapse-manager.js +6 -63
  80. package/dist/synapses/synapse-manager.js.map +1 -1
  81. package/dist/types/ipc.types.d.ts +1 -11
  82. package/dist/utils/__tests__/hash.test.d.ts +1 -0
  83. package/dist/utils/__tests__/hash.test.js +32 -0
  84. package/dist/utils/__tests__/hash.test.js.map +1 -0
  85. package/dist/utils/__tests__/paths.test.d.ts +1 -0
  86. package/dist/utils/__tests__/paths.test.js +75 -0
  87. package/dist/utils/__tests__/paths.test.js.map +1 -0
  88. package/dist/utils/events.d.ts +4 -8
  89. package/dist/utils/events.js +2 -14
  90. package/dist/utils/events.js.map +1 -1
  91. package/dist/utils/hash.d.ts +1 -1
  92. package/dist/utils/hash.js +1 -4
  93. package/dist/utils/hash.js.map +1 -1
  94. package/dist/utils/logger.d.ts +3 -2
  95. package/dist/utils/logger.js +8 -35
  96. package/dist/utils/logger.js.map +1 -1
  97. package/dist/utils/paths.d.ts +2 -1
  98. package/dist/utils/paths.js +4 -13
  99. package/dist/utils/paths.js.map +1 -1
  100. package/eslint.config.js +14 -0
  101. package/package.json +56 -49
  102. package/BRAIN_PLAN.md +0 -3324
  103. package/reddit_post.md +0 -45
  104. package/src/api/server.ts +0 -395
  105. package/src/brain.ts +0 -313
  106. package/src/cli/colors.ts +0 -116
  107. package/src/cli/commands/config.ts +0 -169
  108. package/src/cli/commands/doctor.ts +0 -124
  109. package/src/cli/commands/explain.ts +0 -83
  110. package/src/cli/commands/export.ts +0 -31
  111. package/src/cli/commands/import.ts +0 -199
  112. package/src/cli/commands/insights.ts +0 -65
  113. package/src/cli/commands/learn.ts +0 -24
  114. package/src/cli/commands/modules.ts +0 -53
  115. package/src/cli/commands/network.ts +0 -67
  116. package/src/cli/commands/projects.ts +0 -42
  117. package/src/cli/commands/query.ts +0 -120
  118. package/src/cli/commands/start.ts +0 -105
  119. package/src/cli/commands/status.ts +0 -75
  120. package/src/cli/commands/stop.ts +0 -34
  121. package/src/cli/ipc-helper.ts +0 -22
  122. package/src/cli/update-check.ts +0 -63
  123. package/src/code/analyzer.ts +0 -117
  124. package/src/code/fingerprint.ts +0 -87
  125. package/src/code/matcher.ts +0 -129
  126. package/src/code/parsers/generic.ts +0 -29
  127. package/src/code/parsers/python.ts +0 -54
  128. package/src/code/parsers/typescript.ts +0 -65
  129. package/src/code/registry.ts +0 -60
  130. package/src/code/scorer.ts +0 -120
  131. package/src/config.ts +0 -135
  132. package/src/dashboard/server.ts +0 -142
  133. package/src/db/connection.ts +0 -22
  134. package/src/db/migrations/001_core_schema.ts +0 -120
  135. package/src/db/migrations/002_learning_schema.ts +0 -38
  136. package/src/db/migrations/003_code_schema.ts +0 -53
  137. package/src/db/migrations/004_synapses_schema.ts +0 -57
  138. package/src/db/migrations/005_fts_indexes.ts +0 -78
  139. package/src/db/migrations/006_synapses_phase3.ts +0 -17
  140. package/src/db/migrations/007_feedback.ts +0 -13
  141. package/src/db/migrations/008_git_integration.ts +0 -38
  142. package/src/db/migrations/009_embeddings.ts +0 -8
  143. package/src/db/migrations/index.ts +0 -70
  144. package/src/db/repositories/antipattern.repository.ts +0 -66
  145. package/src/db/repositories/code-module.repository.ts +0 -142
  146. package/src/db/repositories/error.repository.ts +0 -189
  147. package/src/db/repositories/insight.repository.ts +0 -99
  148. package/src/db/repositories/notification.repository.ts +0 -66
  149. package/src/db/repositories/project.repository.ts +0 -93
  150. package/src/db/repositories/rule.repository.ts +0 -108
  151. package/src/db/repositories/solution.repository.ts +0 -154
  152. package/src/db/repositories/synapse.repository.ts +0 -163
  153. package/src/db/repositories/terminal.repository.ts +0 -101
  154. package/src/embeddings/engine.ts +0 -238
  155. package/src/hooks/post-tool-use.ts +0 -92
  156. package/src/hooks/post-write.ts +0 -129
  157. package/src/index.ts +0 -63
  158. package/src/ipc/client.ts +0 -118
  159. package/src/ipc/protocol.ts +0 -35
  160. package/src/ipc/router.ts +0 -133
  161. package/src/ipc/server.ts +0 -176
  162. package/src/learning/confidence-scorer.ts +0 -80
  163. package/src/learning/decay.ts +0 -46
  164. package/src/learning/learning-engine.ts +0 -170
  165. package/src/learning/pattern-extractor.ts +0 -90
  166. package/src/learning/rule-generator.ts +0 -74
  167. package/src/main.rs:10:5 +0 -0
  168. package/src/matching/error-matcher.ts +0 -166
  169. package/src/matching/fingerprint.ts +0 -34
  170. package/src/matching/similarity.ts +0 -61
  171. package/src/matching/tfidf.ts +0 -74
  172. package/src/matching/tokenizer.ts +0 -41
  173. package/src/mcp/auto-detect.ts +0 -93
  174. package/src/mcp/http-server.ts +0 -140
  175. package/src/mcp/server.ts +0 -73
  176. package/src/mcp/tools.ts +0 -328
  177. package/src/parsing/error-parser.ts +0 -28
  178. package/src/parsing/parsers/compiler.ts +0 -93
  179. package/src/parsing/parsers/generic.ts +0 -28
  180. package/src/parsing/parsers/go.ts +0 -97
  181. package/src/parsing/parsers/node.ts +0 -69
  182. package/src/parsing/parsers/python.ts +0 -62
  183. package/src/parsing/parsers/rust.ts +0 -50
  184. package/src/parsing/parsers/shell.ts +0 -42
  185. package/src/parsing/types.ts +0 -47
  186. package/src/research/gap-analyzer.ts +0 -135
  187. package/src/research/insight-generator.ts +0 -123
  188. package/src/research/research-engine.ts +0 -116
  189. package/src/research/synergy-detector.ts +0 -126
  190. package/src/research/template-extractor.ts +0 -130
  191. package/src/research/trend-analyzer.ts +0 -127
  192. package/src/services/analytics.service.ts +0 -226
  193. package/src/services/code.service.ts +0 -271
  194. package/src/services/error.service.ts +0 -266
  195. package/src/services/git.service.ts +0 -132
  196. package/src/services/notification.service.ts +0 -41
  197. package/src/services/prevention.service.ts +0 -159
  198. package/src/services/research.service.ts +0 -98
  199. package/src/services/solution.service.ts +0 -174
  200. package/src/services/synapse.service.ts +0 -59
  201. package/src/services/terminal.service.ts +0 -81
  202. package/src/synapses/activation.ts +0 -80
  203. package/src/synapses/decay.ts +0 -38
  204. package/src/synapses/hebbian.ts +0 -69
  205. package/src/synapses/pathfinder.ts +0 -81
  206. package/src/synapses/synapse-manager.ts +0 -113
  207. package/src/types/code.types.ts +0 -52
  208. package/src/types/config.types.ts +0 -103
  209. package/src/types/error.types.ts +0 -67
  210. package/src/types/ipc.types.ts +0 -8
  211. package/src/types/mcp.types.ts +0 -53
  212. package/src/types/research.types.ts +0 -28
  213. package/src/types/solution.types.ts +0 -30
  214. package/src/types/synapse.types.ts +0 -50
  215. package/src/utils/events.ts +0 -45
  216. package/src/utils/hash.ts +0 -5
  217. package/src/utils/logger.ts +0 -48
  218. package/src/utils/paths.ts +0 -19
  219. package/tests/e2e/test_code_intelligence.py +0 -1015
  220. package/tests/e2e/test_error_memory.py +0 -451
  221. package/tests/e2e/test_full_integration.py +0 -534
  222. package/tests/fixtures/code-modules/modules.ts +0 -83
  223. package/tests/fixtures/errors/go.ts +0 -9
  224. package/tests/fixtures/errors/node.ts +0 -24
  225. package/tests/fixtures/errors/python.ts +0 -21
  226. package/tests/fixtures/errors/rust.ts +0 -25
  227. package/tests/fixtures/errors/shell.ts +0 -15
  228. package/tests/fixtures/solutions/solutions.ts +0 -27
  229. package/tests/helpers/setup-db.ts +0 -52
  230. package/tests/integration/code-flow.test.ts +0 -86
  231. package/tests/integration/error-flow.test.ts +0 -83
  232. package/tests/integration/ipc-flow.test.ts +0 -166
  233. package/tests/integration/learning-cycle.test.ts +0 -82
  234. package/tests/integration/synapse-flow.test.ts +0 -117
  235. package/tests/unit/code/analyzer.test.ts +0 -58
  236. package/tests/unit/code/fingerprint.test.ts +0 -51
  237. package/tests/unit/code/scorer.test.ts +0 -55
  238. package/tests/unit/learning/confidence-scorer.test.ts +0 -60
  239. package/tests/unit/learning/decay.test.ts +0 -45
  240. package/tests/unit/learning/pattern-extractor.test.ts +0 -50
  241. package/tests/unit/matching/error-matcher.test.ts +0 -69
  242. package/tests/unit/matching/fingerprint.test.ts +0 -47
  243. package/tests/unit/matching/similarity.test.ts +0 -65
  244. package/tests/unit/matching/tfidf.test.ts +0 -71
  245. package/tests/unit/matching/tokenizer.test.ts +0 -83
  246. package/tests/unit/parsing/parsers.test.ts +0 -113
  247. package/tests/unit/research/gap-analyzer.test.ts +0 -45
  248. package/tests/unit/research/trend-analyzer.test.ts +0 -45
  249. package/tests/unit/synapses/activation.test.ts +0 -80
  250. package/tests/unit/synapses/decay.test.ts +0 -27
  251. package/tests/unit/synapses/hebbian.test.ts +0 -96
  252. package/tests/unit/synapses/pathfinder.test.ts +0 -72
  253. package/tsconfig.json +0 -18
@@ -1,42 +0,0 @@
1
- import { Command } from 'commander';
2
- import { withIpc } from '../ipc-helper.js';
3
- import { c, icons, header, divider, table } from '../colors.js';
4
-
5
- export function projectsCommand(): Command {
6
- return new Command('projects')
7
- .description('List all imported projects with stats')
8
- .action(async () => {
9
- await withIpc(async (client) => {
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- const projects = await client.request('project.list', {}) as any[];
12
-
13
- console.log(header('Projects', icons.module));
14
-
15
- if (projects.length === 0) {
16
- console.log(`\n ${c.dim('No projects imported yet.')} Use ${c.cyan('brain import <dir>')} to get started.`);
17
- console.log(`\n${divider()}`);
18
- return;
19
- }
20
-
21
- console.log();
22
-
23
- const rows: string[][] = [
24
- [c.dim(' #'), c.dim('Name'), c.dim('Language'), c.dim('Modules'), c.dim('Path')],
25
- ];
26
-
27
- for (const p of projects) {
28
- rows.push([
29
- c.dimmer(` ${p.id}`),
30
- c.value(p.name),
31
- p.language ? c.cyan(p.language) : c.dim('—'),
32
- c.green(String(p.moduleCount)),
33
- p.path ? c.dim(p.path) : c.dim('—'),
34
- ]);
35
- }
36
-
37
- console.log(table(rows, [5, 24, 14, 9, 40]));
38
- console.log(`\n ${c.label('Total:')} ${c.value(String(projects.length))} projects, ${c.green(String(projects.reduce((s: number, p: any) => s + p.moduleCount, 0)))} modules`);
39
- console.log(`\n${divider()}`);
40
- });
41
- });
42
- }
@@ -1,120 +0,0 @@
1
- import { Command } from 'commander';
2
- import { withIpc } from '../ipc-helper.js';
3
- import { c, icons, header, statusBadge, divider } from '../colors.js';
4
-
5
- export function queryCommand(): Command {
6
- return new Command('query')
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')
14
- .action(async (search: string, opts) => {
15
- await withIpc(async (client) => {
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
- }
53
- }
54
-
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));
67
-
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) {
83
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
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();
108
- }
109
- }
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());
117
- }
118
- });
119
- });
120
- }
@@ -1,105 +0,0 @@
1
- import { Command } from 'commander';
2
- import { spawn, type ChildProcess } from 'node:child_process';
3
- import fs from 'node:fs';
4
- import path from 'node:path';
5
- import { getDataDir } from '../../utils/paths.js';
6
- import { c, icons } from '../colors.js';
7
- import { checkForUpdate } from '../update-check.js';
8
-
9
- const MAX_RESTARTS = 5;
10
- const RESTART_WINDOW_MS = 5 * 60 * 1000; // 5 minutes
11
- const BASE_BACKOFF_MS = 1000;
12
-
13
- function spawnDaemon(entryPoint: string, args: string[]): ChildProcess {
14
- const child = spawn(process.execPath, [entryPoint, ...args], {
15
- detached: true,
16
- stdio: 'ignore',
17
- });
18
- child.unref();
19
- return child;
20
- }
21
-
22
- function startWatchdog(entryPoint: string, args: string[], pidPath: string): void {
23
- const restartTimes: number[] = [];
24
-
25
- function launch(): void {
26
- const child = spawnDaemon(entryPoint, args);
27
- console.log(`${icons.brain} ${c.info('Brain daemon starting')} ${c.dim(`(PID: ${child.pid})`)}`);
28
-
29
- child.on('exit', (code) => {
30
- // Normal shutdown (code 0 or SIGTERM) — don't restart
31
- if (code === 0 || code === null) return;
32
-
33
- const now = Date.now();
34
- restartTimes.push(now);
35
-
36
- // Only count restarts within the window
37
- const recentRestarts = restartTimes.filter((t) => now - t < RESTART_WINDOW_MS);
38
- restartTimes.length = 0;
39
- restartTimes.push(...recentRestarts);
40
-
41
- if (recentRestarts.length > MAX_RESTARTS) {
42
- console.error(`${icons.error} ${c.error(`Brain daemon crashed ${MAX_RESTARTS} times in 5 minutes — giving up.`)}`);
43
- // Clean up stale PID file
44
- try { fs.unlinkSync(pidPath); } catch { /* ignore */ }
45
- return;
46
- }
47
-
48
- const backoff = BASE_BACKOFF_MS * Math.pow(2, recentRestarts.length - 1);
49
- console.log(`${icons.warn} ${c.warn(`Brain daemon exited (code ${code}) — restarting in ${backoff / 1000}s...`)}`);
50
-
51
- setTimeout(launch, backoff);
52
- });
53
- }
54
-
55
- launch();
56
-
57
- // Wait briefly for PID file to appear
58
- setTimeout(async () => {
59
- if (fs.existsSync(pidPath)) {
60
- console.log(`${icons.ok} ${c.success('Brain daemon started successfully.')} ${c.dim('(watchdog active)')}`);
61
- } else {
62
- console.log(`${icons.clock} ${c.warn('Brain daemon may still be starting.')} Check: ${c.cyan('brain status')}`);
63
- }
64
- await checkForUpdate();
65
- }, 1000);
66
- }
67
-
68
- export function startCommand(): Command {
69
- return new Command('start')
70
- .description('Start the Brain daemon')
71
- .option('-f, --foreground', 'Run in foreground (no detach)')
72
- .option('-c, --config <path>', 'Config file path')
73
- .action((opts) => {
74
- const pidPath = path.join(getDataDir(), 'brain.pid');
75
-
76
- // Check if already running
77
- if (fs.existsSync(pidPath)) {
78
- const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
79
- try {
80
- process.kill(pid, 0); // Check if process exists
81
- console.log(`${icons.brain} Brain daemon is ${c.green('already running')} ${c.dim(`(PID: ${pid})`)}`);
82
- return;
83
- } catch {
84
- // PID file stale, remove it
85
- fs.unlinkSync(pidPath);
86
- }
87
- }
88
-
89
- if (opts.foreground) {
90
- // Run in foreground — import dynamically to avoid loading everything at CLI parse time
91
- import('../../brain.js').then(({ BrainCore }) => {
92
- const core = new BrainCore();
93
- core.start(opts.config);
94
- });
95
- return;
96
- }
97
-
98
- // Spawn detached daemon with watchdog
99
- const args = ['daemon'];
100
- if (opts.config) args.push('-c', opts.config);
101
- const entryPoint = path.resolve(import.meta.dirname, '../../index.js');
102
-
103
- startWatchdog(entryPoint, args, pidPath);
104
- });
105
- }
@@ -1,75 +0,0 @@
1
- import { Command } from 'commander';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { getDataDir } from '../../utils/paths.js';
5
- import { withIpc } from '../ipc-helper.js';
6
- import { c, icons, header, keyValue, divider } from '../colors.js';
7
- import { checkForUpdate, getCurrentVersion } from '../update-check.js';
8
-
9
- export function statusCommand(): Command {
10
- return new Command('status')
11
- .description('Show Brain daemon status')
12
- .action(async () => {
13
- const pidPath = path.join(getDataDir(), 'brain.pid');
14
-
15
- if (!fs.existsSync(pidPath)) {
16
- console.log(`${icons.brain} Brain Daemon: ${c.red.bold('NOT RUNNING')}`);
17
- return;
18
- }
19
-
20
- const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
21
- let running = false;
22
- try {
23
- process.kill(pid, 0);
24
- running = true;
25
- } catch { /* not running */ }
26
-
27
- if (!running) {
28
- console.log(`${icons.brain} Brain Daemon: ${c.red.bold('NOT RUNNING')} ${c.dim('(stale PID file)')}`);
29
- return;
30
- }
31
-
32
- console.log(header(`Brain Status v${getCurrentVersion()}`, icons.brain));
33
- console.log(` ${c.green(`${icons.dot} RUNNING`)} ${c.dim(`(PID ${pid})`)}`);
34
-
35
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
36
- await withIpc(async (client) => {
37
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
38
- const summary: any = await client.request('analytics.summary', {});
39
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
40
- const network: any = await client.request('synapse.stats', {});
41
-
42
- const dbPath = path.join(getDataDir(), 'brain.db');
43
- let dbSize = '?';
44
- try {
45
- const stat = fs.statSync(dbPath);
46
- dbSize = `${(stat.size / 1024 / 1024).toFixed(1)} MB`;
47
- } catch { /* ignore */ }
48
-
49
- console.log(keyValue('Database', `${dbPath} (${dbSize})`));
50
- console.log();
51
-
52
- console.log(` ${icons.error} ${c.purple.bold('Error Brain')}`);
53
- 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`)}`);
54
- console.log(` ${c.label('Solutions:')} ${c.value(summary.solutions?.total ?? 0)}`);
55
- console.log(` ${c.label('Rules:')} ${c.green(summary.rules?.active ?? 0)} active`);
56
- console.log(` ${c.label('Anti-Pat.:')} ${c.value(summary.antipatterns?.total ?? 0)}`);
57
- console.log();
58
-
59
- console.log(` ${icons.module} ${c.blue.bold('Code Brain')}`);
60
- console.log(` ${c.label('Modules:')} ${c.value(summary.modules?.total ?? 0)} registered`);
61
- console.log();
62
-
63
- console.log(` ${icons.synapse} ${c.cyan.bold('Synapse Network')}`);
64
- console.log(` ${c.label('Synapses:')} ${c.value(network.totalSynapses ?? 0)}`);
65
- console.log(` ${c.label('Avg weight:')} ${c.value((network.avgWeight ?? 0).toFixed(2))}`);
66
- console.log();
67
-
68
- console.log(` ${icons.insight} ${c.orange.bold('Research Brain')}`);
69
- console.log(` ${c.label('Insights:')} ${c.value(summary.insights?.active ?? 0)} active`);
70
-
71
- await checkForUpdate();
72
- console.log(`\n${divider()}`);
73
- });
74
- });
75
- }
@@ -1,34 +0,0 @@
1
- import { Command } from 'commander';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { getDataDir } from '../../utils/paths.js';
5
- import { c, icons } from '../colors.js';
6
-
7
- export function stopCommand(): Command {
8
- return new Command('stop')
9
- .description('Stop the Brain daemon')
10
- .action(() => {
11
- const pidPath = path.join(getDataDir(), 'brain.pid');
12
-
13
- if (!fs.existsSync(pidPath)) {
14
- console.log(`${icons.brain} ${c.dim('Brain daemon is not running (no PID file found).')}`);
15
- return;
16
- }
17
-
18
- const pid = parseInt(fs.readFileSync(pidPath, 'utf8').trim(), 10);
19
-
20
- try {
21
- process.kill(pid, 'SIGTERM');
22
- console.log(`${icons.brain} ${c.success('Brain daemon stopped')} ${c.dim(`(PID: ${pid})`)}`);
23
- } catch (err) {
24
- if ((err as NodeJS.ErrnoException).code === 'ESRCH') {
25
- console.log(`${icons.brain} ${c.dim('Brain daemon was not running (stale PID file removed).')}`);
26
- } else {
27
- console.error(`${icons.error} ${c.error(`Failed to stop daemon: ${err}`)}`);
28
- }
29
- }
30
-
31
- // Clean up PID file
32
- try { fs.unlinkSync(pidPath); } catch { /* ignore */ }
33
- });
34
- }
@@ -1,22 +0,0 @@
1
- import { IpcClient } from '../ipc/client.js';
2
- import { getPipeName } from '../utils/paths.js';
3
- import { c, icons } from './colors.js';
4
-
5
- export async function withIpc<T>(fn: (client: IpcClient) => Promise<T>): Promise<T> {
6
- const client = new IpcClient(getPipeName(), 5000);
7
- try {
8
- await client.connect();
9
- return await fn(client);
10
- } catch (err) {
11
- if (err instanceof Error && err.message.includes('ENOENT')) {
12
- console.error(`${icons.error} ${c.error('Brain daemon is not running.')} Start it with: ${c.cyan('brain start')}`);
13
- } else if (err instanceof Error && err.message.includes('ECONNREFUSED')) {
14
- console.error(`${icons.error} ${c.error('Brain daemon is not responding.')} Try: ${c.cyan('brain stop && brain start')}`);
15
- } else {
16
- console.error(`${icons.error} ${c.error(err instanceof Error ? err.message : String(err))}`);
17
- }
18
- process.exit(1);
19
- } finally {
20
- client.disconnect();
21
- }
22
- }
@@ -1,63 +0,0 @@
1
- import https from 'node:https';
2
- import { c, icons } from './colors.js';
3
-
4
- // Read current version from package.json at build time
5
- import { createRequire } from 'node:module';
6
- const require = createRequire(import.meta.url);
7
- const pkg = require('../../package.json');
8
- const CURRENT_VERSION: string = pkg.version;
9
-
10
- export function getCurrentVersion(): string {
11
- return CURRENT_VERSION;
12
- }
13
-
14
- function fetchLatestVersion(): Promise<string | null> {
15
- return new Promise((resolve) => {
16
- const timeout = setTimeout(() => resolve(null), 3000);
17
-
18
- const req = https.get(
19
- 'https://registry.npmjs.org/@timmeck/brain/latest',
20
- { headers: { Accept: 'application/json' } },
21
- (res) => {
22
- let data = '';
23
- res.on('data', (chunk) => { data += chunk; });
24
- res.on('end', () => {
25
- clearTimeout(timeout);
26
- try {
27
- const json = JSON.parse(data);
28
- resolve(json.version ?? null);
29
- } catch {
30
- resolve(null);
31
- }
32
- });
33
- },
34
- );
35
- req.on('error', () => {
36
- clearTimeout(timeout);
37
- resolve(null);
38
- });
39
- });
40
- }
41
-
42
- function isNewer(latest: string, current: string): boolean {
43
- const l = latest.split('.').map(Number);
44
- const c = current.split('.').map(Number);
45
- for (let i = 0; i < 3; i++) {
46
- if ((l[i] ?? 0) > (c[i] ?? 0)) return true;
47
- if ((l[i] ?? 0) < (c[i] ?? 0)) return false;
48
- }
49
- return false;
50
- }
51
-
52
- export async function checkForUpdate(): Promise<void> {
53
- try {
54
- const latest = await fetchLatestVersion();
55
- if (latest && isNewer(latest, CURRENT_VERSION)) {
56
- console.log();
57
- console.log(` ${icons.star} ${c.orange.bold(`Update available: v${CURRENT_VERSION} → v${latest}`)}`);
58
- console.log(` Run: ${c.cyan('npm update -g @timmeck/brain')}`);
59
- }
60
- } catch {
61
- // silently ignore — update check is best-effort
62
- }
63
- }
@@ -1,117 +0,0 @@
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
- complexity: number;
14
- }
15
-
16
- const SIDE_EFFECT_PATTERNS = [
17
- 'fs.', 'process.exit', 'process.env', 'console.', 'fetch(',
18
- 'XMLHttpRequest', 'document.', 'window.',
19
- 'global.', 'require(',
20
- ];
21
-
22
- function getParser(language: string) {
23
- switch (language) {
24
- case 'typescript':
25
- case 'javascript':
26
- return tsParser;
27
- case 'python':
28
- return pyParser;
29
- default:
30
- return genericParser;
31
- }
32
- }
33
-
34
- export function analyzeCode(source: string, language: string): AnalysisResult {
35
- const parser = getParser(language);
36
- const exports = parser.extractExports(source);
37
- const { external, internal } = parser.extractImports(source);
38
- const isPure = checkPurity(source);
39
- const typed = parser.hasTypeAnnotations(source);
40
- const linesOfCode = source.split('\n').filter(l => l.trim().length > 0).length;
41
- const complexity = computeCyclomaticComplexity(source, language);
42
-
43
- return {
44
- exports,
45
- externalDeps: external,
46
- internalDeps: internal,
47
- isPure,
48
- hasTypeAnnotations: typed,
49
- linesOfCode,
50
- complexity,
51
- };
52
- }
53
-
54
- /**
55
- * Computes cyclomatic complexity: counts decision points in the code.
56
- * CC = 1 + number of decision points (if, else if, for, while, case, catch, &&, ||, ?:)
57
- */
58
- export function computeCyclomaticComplexity(source: string, language: string): number {
59
- // Remove comments and strings to avoid false positives
60
- const cleaned = source
61
- .replace(/\/\/.*$/gm, '') // single-line comments
62
- .replace(/\/\*[\s\S]*?\*\//g, '') // multi-line comments
63
- .replace(/#.*$/gm, '') // Python comments
64
- .replace(/(["'`])(?:(?!\1|\\).|\\.)*\1/g, '""'); // strings
65
-
66
- let complexity = 1; // Base complexity
67
-
68
- // Language-agnostic decision point patterns
69
- const patterns = [
70
- /\bif\b/g,
71
- /\belse\s+if\b/g,
72
- /\belif\b/g,
73
- /\bfor\b/g,
74
- /\bwhile\b/g,
75
- /\bcase\b/g,
76
- /\bcatch\b/g,
77
- /\bexcept\b/g,
78
- /&&/g,
79
- /\|\|/g,
80
- /\?\s*[^:]/g, // ternary operator (not type annotations)
81
- ];
82
-
83
- for (const pattern of patterns) {
84
- const matches = cleaned.match(pattern);
85
- if (matches) complexity += matches.length;
86
- }
87
-
88
- return complexity;
89
- }
90
-
91
- export function checkPurity(source: string): boolean {
92
- return !SIDE_EFFECT_PATTERNS.some(p => source.includes(p));
93
- }
94
-
95
- export function measureCohesion(exports: ExportInfo[]): number {
96
- if (exports.length <= 1) return 1.0;
97
-
98
- const names = exports.map(e =>
99
- e.name
100
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
101
- .replace(/([a-z\d])([A-Z])/g, '$1 $2')
102
- .toLowerCase()
103
- .split(/\s+/)
104
- );
105
-
106
- const vocab = new Set<string>();
107
- names.forEach(tokens => tokens.forEach(t => vocab.add(t)));
108
-
109
- let sharedTokens = 0;
110
- for (const token of vocab) {
111
- const count = names.filter(n => n.includes(token)).length;
112
- if (count > 1) sharedTokens += count;
113
- }
114
-
115
- const maxPossible = names.length * vocab.size;
116
- return maxPossible === 0 ? 0 : sharedTokens / maxPossible;
117
- }
@@ -1,87 +0,0 @@
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
- }