@timmeck/brain 1.8.5 → 2.0.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 (221) hide show
  1. package/.dockerignore +11 -0
  2. package/Dockerfile +21 -0
  3. package/README.md +19 -0
  4. package/assets/brain-avatar-256.png +0 -0
  5. package/assets/brain-avatar-512.png +0 -0
  6. package/assets/brain-avatar.png +0 -0
  7. package/brain.log +1164 -0
  8. package/{src/cli/commands/dashboard.ts → dashboard.html} +688 -807
  9. package/dist/api/server.d.ts +4 -18
  10. package/dist/api/server.js +4 -173
  11. package/dist/api/server.js.map +1 -1
  12. package/dist/brain.d.ts +6 -0
  13. package/dist/brain.js +56 -4
  14. package/dist/brain.js.map +1 -1
  15. package/dist/cli/colors.d.ts +4 -25
  16. package/dist/cli/colors.js +3 -89
  17. package/dist/cli/colors.js.map +1 -1
  18. package/dist/cli/commands/peers.d.ts +2 -0
  19. package/dist/cli/commands/peers.js +38 -0
  20. package/dist/cli/commands/peers.js.map +1 -0
  21. package/dist/cli/commands/start.js +54 -17
  22. package/dist/cli/commands/start.js.map +1 -1
  23. package/dist/db/connection.d.ts +1 -2
  24. package/dist/db/connection.js +1 -18
  25. package/dist/db/connection.js.map +1 -1
  26. package/dist/index.js +3 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/ipc/__tests__/protocol.test.d.ts +1 -0
  29. package/dist/ipc/__tests__/protocol.test.js +117 -0
  30. package/dist/ipc/__tests__/protocol.test.js.map +1 -0
  31. package/dist/ipc/client.d.ts +1 -16
  32. package/dist/ipc/client.js +1 -100
  33. package/dist/ipc/client.js.map +1 -1
  34. package/dist/ipc/protocol.d.ts +1 -8
  35. package/dist/ipc/protocol.js +1 -28
  36. package/dist/ipc/protocol.js.map +1 -1
  37. package/dist/ipc/router.js +8 -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/mcp/http-server.d.ts +1 -7
  43. package/dist/mcp/http-server.js +6 -117
  44. package/dist/mcp/http-server.js.map +1 -1
  45. package/dist/mcp/server.js +5 -61
  46. package/dist/mcp/server.js.map +1 -1
  47. package/dist/signals/__tests__/fingerprint.test.d.ts +1 -0
  48. package/dist/signals/__tests__/fingerprint.test.js +118 -0
  49. package/dist/signals/__tests__/fingerprint.test.js.map +1 -0
  50. package/dist/types/ipc.types.d.ts +1 -11
  51. package/dist/utils/__tests__/hash.test.d.ts +1 -0
  52. package/dist/utils/__tests__/hash.test.js +32 -0
  53. package/dist/utils/__tests__/hash.test.js.map +1 -0
  54. package/dist/utils/__tests__/paths.test.d.ts +1 -0
  55. package/dist/utils/__tests__/paths.test.js +75 -0
  56. package/dist/utils/__tests__/paths.test.js.map +1 -0
  57. package/dist/utils/events.d.ts +4 -8
  58. package/dist/utils/events.js +2 -14
  59. package/dist/utils/events.js.map +1 -1
  60. package/dist/utils/hash.d.ts +1 -1
  61. package/dist/utils/hash.js +1 -4
  62. package/dist/utils/hash.js.map +1 -1
  63. package/dist/utils/logger.d.ts +3 -2
  64. package/dist/utils/logger.js +8 -35
  65. package/dist/utils/logger.js.map +1 -1
  66. package/dist/utils/paths.d.ts +2 -1
  67. package/dist/utils/paths.js +4 -13
  68. package/dist/utils/paths.js.map +1 -1
  69. package/gen_avatar.py +142 -0
  70. package/package.json +2 -1
  71. package/BRAIN_PLAN.md +0 -3324
  72. package/src/api/server.ts +0 -395
  73. package/src/brain.ts +0 -266
  74. package/src/cli/colors.ts +0 -116
  75. package/src/cli/commands/config.ts +0 -169
  76. package/src/cli/commands/doctor.ts +0 -124
  77. package/src/cli/commands/explain.ts +0 -83
  78. package/src/cli/commands/export.ts +0 -31
  79. package/src/cli/commands/import.ts +0 -199
  80. package/src/cli/commands/insights.ts +0 -65
  81. package/src/cli/commands/learn.ts +0 -24
  82. package/src/cli/commands/modules.ts +0 -53
  83. package/src/cli/commands/network.ts +0 -67
  84. package/src/cli/commands/projects.ts +0 -42
  85. package/src/cli/commands/query.ts +0 -120
  86. package/src/cli/commands/start.ts +0 -62
  87. package/src/cli/commands/status.ts +0 -75
  88. package/src/cli/commands/stop.ts +0 -34
  89. package/src/cli/ipc-helper.ts +0 -22
  90. package/src/cli/update-check.ts +0 -63
  91. package/src/code/analyzer.ts +0 -117
  92. package/src/code/fingerprint.ts +0 -87
  93. package/src/code/matcher.ts +0 -129
  94. package/src/code/parsers/generic.ts +0 -29
  95. package/src/code/parsers/python.ts +0 -54
  96. package/src/code/parsers/typescript.ts +0 -65
  97. package/src/code/registry.ts +0 -60
  98. package/src/code/scorer.ts +0 -120
  99. package/src/config.ts +0 -135
  100. package/src/dashboard/server.ts +0 -142
  101. package/src/db/connection.ts +0 -22
  102. package/src/db/migrations/001_core_schema.ts +0 -120
  103. package/src/db/migrations/002_learning_schema.ts +0 -38
  104. package/src/db/migrations/003_code_schema.ts +0 -53
  105. package/src/db/migrations/004_synapses_schema.ts +0 -57
  106. package/src/db/migrations/005_fts_indexes.ts +0 -78
  107. package/src/db/migrations/006_synapses_phase3.ts +0 -17
  108. package/src/db/migrations/007_feedback.ts +0 -13
  109. package/src/db/migrations/008_git_integration.ts +0 -38
  110. package/src/db/migrations/009_embeddings.ts +0 -8
  111. package/src/db/migrations/index.ts +0 -70
  112. package/src/db/repositories/antipattern.repository.ts +0 -66
  113. package/src/db/repositories/code-module.repository.ts +0 -142
  114. package/src/db/repositories/error.repository.ts +0 -189
  115. package/src/db/repositories/insight.repository.ts +0 -99
  116. package/src/db/repositories/notification.repository.ts +0 -66
  117. package/src/db/repositories/project.repository.ts +0 -93
  118. package/src/db/repositories/rule.repository.ts +0 -108
  119. package/src/db/repositories/solution.repository.ts +0 -154
  120. package/src/db/repositories/synapse.repository.ts +0 -163
  121. package/src/db/repositories/terminal.repository.ts +0 -101
  122. package/src/embeddings/engine.ts +0 -238
  123. package/src/hooks/post-tool-use.ts +0 -92
  124. package/src/hooks/post-write.ts +0 -129
  125. package/src/index.ts +0 -63
  126. package/src/ipc/client.ts +0 -118
  127. package/src/ipc/protocol.ts +0 -35
  128. package/src/ipc/router.ts +0 -133
  129. package/src/ipc/server.ts +0 -176
  130. package/src/learning/confidence-scorer.ts +0 -80
  131. package/src/learning/decay.ts +0 -46
  132. package/src/learning/learning-engine.ts +0 -170
  133. package/src/learning/pattern-extractor.ts +0 -90
  134. package/src/learning/rule-generator.ts +0 -74
  135. package/src/main.rs:10:5 +0 -0
  136. package/src/matching/error-matcher.ts +0 -166
  137. package/src/matching/fingerprint.ts +0 -34
  138. package/src/matching/similarity.ts +0 -61
  139. package/src/matching/tfidf.ts +0 -74
  140. package/src/matching/tokenizer.ts +0 -41
  141. package/src/mcp/auto-detect.ts +0 -93
  142. package/src/mcp/http-server.ts +0 -140
  143. package/src/mcp/server.ts +0 -73
  144. package/src/mcp/tools.ts +0 -328
  145. package/src/parsing/error-parser.ts +0 -28
  146. package/src/parsing/parsers/compiler.ts +0 -93
  147. package/src/parsing/parsers/generic.ts +0 -28
  148. package/src/parsing/parsers/go.ts +0 -97
  149. package/src/parsing/parsers/node.ts +0 -69
  150. package/src/parsing/parsers/python.ts +0 -62
  151. package/src/parsing/parsers/rust.ts +0 -50
  152. package/src/parsing/parsers/shell.ts +0 -42
  153. package/src/parsing/types.ts +0 -47
  154. package/src/research/gap-analyzer.ts +0 -135
  155. package/src/research/insight-generator.ts +0 -123
  156. package/src/research/research-engine.ts +0 -116
  157. package/src/research/synergy-detector.ts +0 -126
  158. package/src/research/template-extractor.ts +0 -130
  159. package/src/research/trend-analyzer.ts +0 -127
  160. package/src/services/analytics.service.ts +0 -226
  161. package/src/services/code.service.ts +0 -271
  162. package/src/services/error.service.ts +0 -266
  163. package/src/services/git.service.ts +0 -132
  164. package/src/services/notification.service.ts +0 -41
  165. package/src/services/prevention.service.ts +0 -159
  166. package/src/services/research.service.ts +0 -98
  167. package/src/services/solution.service.ts +0 -174
  168. package/src/services/synapse.service.ts +0 -59
  169. package/src/services/terminal.service.ts +0 -81
  170. package/src/synapses/activation.ts +0 -80
  171. package/src/synapses/decay.ts +0 -38
  172. package/src/synapses/hebbian.ts +0 -69
  173. package/src/synapses/pathfinder.ts +0 -81
  174. package/src/synapses/synapse-manager.ts +0 -113
  175. package/src/types/code.types.ts +0 -52
  176. package/src/types/config.types.ts +0 -103
  177. package/src/types/error.types.ts +0 -67
  178. package/src/types/ipc.types.ts +0 -8
  179. package/src/types/mcp.types.ts +0 -53
  180. package/src/types/research.types.ts +0 -28
  181. package/src/types/solution.types.ts +0 -30
  182. package/src/types/synapse.types.ts +0 -50
  183. package/src/utils/events.ts +0 -45
  184. package/src/utils/hash.ts +0 -5
  185. package/src/utils/logger.ts +0 -48
  186. package/src/utils/paths.ts +0 -19
  187. package/tests/e2e/test_code_intelligence.py +0 -1015
  188. package/tests/e2e/test_error_memory.py +0 -451
  189. package/tests/e2e/test_full_integration.py +0 -534
  190. package/tests/fixtures/code-modules/modules.ts +0 -83
  191. package/tests/fixtures/errors/go.ts +0 -9
  192. package/tests/fixtures/errors/node.ts +0 -24
  193. package/tests/fixtures/errors/python.ts +0 -21
  194. package/tests/fixtures/errors/rust.ts +0 -25
  195. package/tests/fixtures/errors/shell.ts +0 -15
  196. package/tests/fixtures/solutions/solutions.ts +0 -27
  197. package/tests/helpers/setup-db.ts +0 -52
  198. package/tests/integration/code-flow.test.ts +0 -86
  199. package/tests/integration/error-flow.test.ts +0 -83
  200. package/tests/integration/ipc-flow.test.ts +0 -166
  201. package/tests/integration/learning-cycle.test.ts +0 -82
  202. package/tests/integration/synapse-flow.test.ts +0 -117
  203. package/tests/unit/code/analyzer.test.ts +0 -58
  204. package/tests/unit/code/fingerprint.test.ts +0 -51
  205. package/tests/unit/code/scorer.test.ts +0 -55
  206. package/tests/unit/learning/confidence-scorer.test.ts +0 -60
  207. package/tests/unit/learning/decay.test.ts +0 -45
  208. package/tests/unit/learning/pattern-extractor.test.ts +0 -50
  209. package/tests/unit/matching/error-matcher.test.ts +0 -69
  210. package/tests/unit/matching/fingerprint.test.ts +0 -47
  211. package/tests/unit/matching/similarity.test.ts +0 -65
  212. package/tests/unit/matching/tfidf.test.ts +0 -71
  213. package/tests/unit/matching/tokenizer.test.ts +0 -83
  214. package/tests/unit/parsing/parsers.test.ts +0 -113
  215. package/tests/unit/research/gap-analyzer.test.ts +0 -45
  216. package/tests/unit/research/trend-analyzer.test.ts +0 -45
  217. package/tests/unit/synapses/activation.test.ts +0 -80
  218. package/tests/unit/synapses/decay.test.ts +0 -27
  219. package/tests/unit/synapses/hebbian.test.ts +0 -96
  220. package/tests/unit/synapses/pathfinder.test.ts +0 -72
  221. package/tsconfig.json +0 -18
@@ -1,41 +0,0 @@
1
- const STOPWORDS = new Set([
2
- 'the', 'is', 'are', 'a', 'an', 'and', 'or', 'not', 'in', 'at', 'by', 'for',
3
- 'from', 'of', 'on', 'to', 'with', 'as', 'error', 'exception', 'throw', 'catch',
4
- 'was', 'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did',
5
- 'will', 'would', 'could', 'should', 'may', 'might', 'can', 'it', 'its',
6
- 'this', 'that', 'these', 'those', 'i', 'we', 'you', 'he', 'she', 'they',
7
- ]);
8
-
9
- export function splitCamelCase(text: string): string[] {
10
- return text
11
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
12
- .replace(/([a-z\d])([A-Z])/g, '$1 $2')
13
- .split(/\s+/)
14
- .filter(t => t.length > 0);
15
- }
16
-
17
- export function splitSnakeCase(text: string): string[] {
18
- return text.split(/[_\-]+/).filter(t => t.length > 0);
19
- }
20
-
21
- export function removeStopwords(tokens: string[]): string[] {
22
- return tokens.filter(t => !STOPWORDS.has(t.toLowerCase()));
23
- }
24
-
25
- export function tokenize(text: string): string[] {
26
- const words = text
27
- .replace(/[^\w\s]/g, ' ')
28
- .split(/\s+/)
29
- .filter(t => t.length > 0);
30
-
31
- const tokens: string[] = [];
32
- for (const word of words) {
33
- tokens.push(...splitCamelCase(word));
34
- if (word.includes('_') || word.includes('-')) {
35
- tokens.push(...splitSnakeCase(word));
36
- }
37
- }
38
-
39
- const cleaned = removeStopwords(tokens);
40
- return [...new Set(cleaned.map(t => t.toLowerCase()))];
41
- }
@@ -1,93 +0,0 @@
1
- import { IpcClient } from '../ipc/client.js';
2
- import { getPipeName } from '../utils/paths.js';
3
-
4
- interface HookInput {
5
- tool_name: string;
6
- tool_input: { command?: string; file_path?: string };
7
- tool_output: string;
8
- exit_code?: number;
9
- }
10
-
11
- const ERROR_PATTERNS = [
12
- /Error:/i,
13
- /error\[E\d+\]/, // Rust errors
14
- /Traceback \(most recent call last\)/, // Python
15
- /FATAL|PANIC/i,
16
- /npm ERR!/,
17
- /SyntaxError|TypeError|ReferenceError|RangeError/,
18
- /ENOENT|EACCES|ECONNREFUSED|ETIMEDOUT/,
19
- /ModuleNotFoundError|ImportError/,
20
- /failed to compile/i,
21
- /BUILD FAILED/i,
22
- /Cannot find module/,
23
- /command not found/,
24
- /Permission denied/,
25
- ];
26
-
27
- function isError(input: HookInput): boolean {
28
- if (input.exit_code !== undefined && input.exit_code !== 0) return true;
29
- return ERROR_PATTERNS.some(p => p.test(input.tool_output));
30
- }
31
-
32
- function readStdin(): Promise<string> {
33
- return new Promise((resolve) => {
34
- let data = '';
35
- process.stdin.setEncoding('utf8');
36
- process.stdin.on('data', (chunk) => { data += chunk; });
37
- process.stdin.on('end', () => resolve(data));
38
- });
39
- }
40
-
41
- async function main(): Promise<void> {
42
- const raw = await readStdin();
43
- if (!raw.trim()) return;
44
-
45
- let input: HookInput;
46
- try {
47
- input = JSON.parse(raw);
48
- } catch {
49
- return;
50
- }
51
-
52
- if (!isError(input)) return;
53
-
54
- const client = new IpcClient(getPipeName(), 3000);
55
- try {
56
- await client.connect();
57
-
58
- // Report error to Brain
59
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
- const result: any = await client.request('error.report', {
61
- project: 'auto-detected',
62
- errorOutput: input.tool_output,
63
- command: input.tool_input.command,
64
- autoDetected: true,
65
- });
66
-
67
- // If Brain knows a solution, output as feedback
68
- if (result.matches?.length > 0) {
69
- const best = result.matches[0];
70
- process.stderr.write(`Brain: Similar error found (#${best.errorId}, ${Math.round(best.score * 100)}% match)\n`);
71
- if (best.solutions?.length > 0) {
72
- process.stderr.write(`Brain: Solution available — use brain_query_error to see details\n`);
73
- }
74
- }
75
-
76
- // Anti-pattern check
77
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
- const antipatterns: any = await client.request('prevention.antipatterns', {
79
- errorType: '',
80
- message: input.tool_output,
81
- });
82
- if (antipatterns?.length > 0 && antipatterns[0].matched) {
83
- process.stderr.write(`Brain WARNING: Known anti-pattern detected: ${antipatterns[0].description}\n`);
84
- }
85
-
86
- } catch {
87
- // Hook must never block the workflow — silent failure
88
- } finally {
89
- client.disconnect();
90
- }
91
- }
92
-
93
- main();
@@ -1,140 +0,0 @@
1
- import http from 'node:http';
2
- import { randomUUID } from 'node:crypto';
3
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
- import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
5
- import { getLogger } from '../utils/logger.js';
6
- import type { IpcRouter } from '../ipc/router.js';
7
- import { registerToolsDirect } from './tools.js';
8
-
9
- export class McpHttpServer {
10
- private server: http.Server | null = null;
11
- private transports = new Map<string, SSEServerTransport>();
12
- private logger = getLogger();
13
-
14
- constructor(
15
- private port: number,
16
- private router: IpcRouter,
17
- ) {}
18
-
19
- start(): void {
20
- this.server = http.createServer((req, res) => {
21
- res.setHeader('Access-Control-Allow-Origin', '*');
22
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
23
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
24
-
25
- if (req.method === 'OPTIONS') {
26
- res.writeHead(204);
27
- res.end();
28
- return;
29
- }
30
-
31
- const url = new URL(req.url ?? '/', `http://localhost:${this.port}`);
32
-
33
- if (url.pathname === '/sse' && req.method === 'GET') {
34
- this.handleSSE(res);
35
- return;
36
- }
37
-
38
- if (url.pathname === '/messages' && req.method === 'POST') {
39
- this.handleMessage(req, res, url);
40
- return;
41
- }
42
-
43
- if (url.pathname === '/' && req.method === 'GET') {
44
- res.writeHead(200, { 'Content-Type': 'application/json' });
45
- res.end(JSON.stringify({
46
- name: 'brain',
47
- version: '1.8.0',
48
- protocol: 'MCP',
49
- transport: 'sse',
50
- endpoints: {
51
- sse: '/sse',
52
- messages: '/messages',
53
- },
54
- clients: this.transports.size,
55
- }));
56
- return;
57
- }
58
-
59
- res.writeHead(404, { 'Content-Type': 'text/plain' });
60
- res.end('Not Found');
61
- });
62
-
63
- this.server.listen(this.port, () => {
64
- this.logger.info(`MCP HTTP server (SSE) started on http://localhost:${this.port}`);
65
- });
66
- }
67
-
68
- stop(): void {
69
- for (const transport of this.transports.values()) {
70
- try { transport.close?.(); } catch { /* ignore */ }
71
- }
72
- this.transports.clear();
73
- this.server?.close();
74
- this.server = null;
75
- this.logger.info('MCP HTTP server stopped');
76
- }
77
-
78
- getClientCount(): number {
79
- return this.transports.size;
80
- }
81
-
82
- private async handleSSE(res: http.ServerResponse): Promise<void> {
83
- try {
84
- const transport = new SSEServerTransport('/messages', res);
85
- const sessionId = transport.sessionId ?? randomUUID();
86
- this.transports.set(sessionId, transport);
87
-
88
- const server = new McpServer({
89
- name: 'brain',
90
- version: '1.8.0',
91
- });
92
-
93
- registerToolsDirect(server, this.router);
94
-
95
- res.on('close', () => {
96
- this.transports.delete(sessionId);
97
- this.logger.debug(`MCP SSE client disconnected: ${sessionId}`);
98
- });
99
-
100
- await server.connect(transport);
101
- this.logger.info(`MCP SSE client connected: ${sessionId}`);
102
- } catch (err) {
103
- this.logger.error('MCP SSE connection error:', err);
104
- if (!res.headersSent) {
105
- res.writeHead(500, { 'Content-Type': 'text/plain' });
106
- res.end('Internal Server Error');
107
- }
108
- }
109
- }
110
-
111
- private async handleMessage(
112
- req: http.IncomingMessage,
113
- res: http.ServerResponse,
114
- url: URL,
115
- ): Promise<void> {
116
- try {
117
- const sessionId = url.searchParams.get('sessionId');
118
- if (!sessionId) {
119
- res.writeHead(400, { 'Content-Type': 'text/plain' });
120
- res.end('Missing sessionId parameter');
121
- return;
122
- }
123
-
124
- const transport = this.transports.get(sessionId);
125
- if (!transport) {
126
- res.writeHead(404, { 'Content-Type': 'text/plain' });
127
- res.end('Session not found. Connect to /sse first.');
128
- return;
129
- }
130
-
131
- await transport.handlePostMessage(req, res);
132
- } catch (err) {
133
- this.logger.error('MCP message error:', err);
134
- if (!res.headersSent) {
135
- res.writeHead(500, { 'Content-Type': 'text/plain' });
136
- res.end('Internal Server Error');
137
- }
138
- }
139
- }
140
- }
package/src/mcp/server.ts DELETED
@@ -1,73 +0,0 @@
1
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
2
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
3
- import { spawn } from 'node:child_process';
4
- import path from 'node:path';
5
- import { IpcClient } from '../ipc/client.js';
6
- import { getPipeName } from '../utils/paths.js';
7
- import { registerTools } from './tools.js';
8
-
9
- function spawnDaemon(): void {
10
- const entryPoint = path.resolve(import.meta.dirname, '../index.ts');
11
- const child = spawn(process.execPath, [
12
- '--import', 'tsx',
13
- entryPoint, 'daemon',
14
- ], {
15
- detached: true,
16
- stdio: 'ignore',
17
- cwd: path.resolve(import.meta.dirname, '../..'),
18
- });
19
- child.unref();
20
- process.stderr.write(`Brain: Auto-started daemon (PID: ${child.pid})\n`);
21
- }
22
-
23
- async function connectWithRetry(ipc: IpcClient, retries: number, delayMs: number): Promise<void> {
24
- for (let i = 0; i < retries; i++) {
25
- try {
26
- await ipc.connect();
27
- return;
28
- } catch {
29
- if (i < retries - 1) {
30
- await new Promise(r => setTimeout(r, delayMs));
31
- }
32
- }
33
- }
34
- throw new Error('Could not connect to daemon after retries');
35
- }
36
-
37
- export async function startMcpServer(): Promise<void> {
38
- const server = new McpServer({
39
- name: 'brain',
40
- version: '1.0.0',
41
- });
42
-
43
- const ipc = new IpcClient(getPipeName());
44
-
45
- try {
46
- await ipc.connect();
47
- } catch {
48
- // Daemon not running — auto-start it
49
- process.stderr.write('Brain: Daemon not running, starting automatically...\n');
50
- spawnDaemon();
51
- try {
52
- await connectWithRetry(ipc, 10, 500);
53
- } catch {
54
- process.stderr.write('Brain: Could not connect to daemon after auto-start. Check logs.\n');
55
- process.exit(1);
56
- }
57
- }
58
-
59
- registerTools(server, ipc);
60
-
61
- const transport = new StdioServerTransport();
62
- await server.connect(transport);
63
-
64
- process.on('SIGINT', () => {
65
- ipc.disconnect();
66
- process.exit(0);
67
- });
68
-
69
- process.on('SIGTERM', () => {
70
- ipc.disconnect();
71
- process.exit(0);
72
- });
73
- }
package/src/mcp/tools.ts DELETED
@@ -1,328 +0,0 @@
1
- import { z } from 'zod';
2
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import type { IpcClient } from '../ipc/client.js';
4
- import type { IpcRouter } from '../ipc/router.js';
5
-
6
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
- type AnyResult = any;
8
-
9
- type BrainCall = (method: string, params?: unknown) => Promise<unknown> | unknown;
10
-
11
- function textResult(data: unknown): { content: Array<{ type: 'text'; text: string }> } {
12
- const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
13
- return { content: [{ type: 'text' as const, text }] };
14
- }
15
-
16
- /** Register tools using IPC client (for stdio MCP transport) */
17
- export function registerTools(server: McpServer, ipc: IpcClient): void {
18
- registerToolsWithCaller(server, (method, params) => ipc.request(method, params));
19
- }
20
-
21
- /** Register tools using router directly (for HTTP MCP transport inside daemon) */
22
- export function registerToolsDirect(server: McpServer, router: IpcRouter): void {
23
- registerToolsWithCaller(server, (method, params) => router.handle(method, params));
24
- }
25
-
26
- function registerToolsWithCaller(server: McpServer, call: BrainCall): void {
27
-
28
- // === Error Brain Tools ===
29
-
30
- server.tool(
31
- 'brain_report_error',
32
- 'Report an error that occurred. Brain stores it, matches against known errors, returns solutions if available.',
33
- {
34
- error_output: z.string().describe('The raw error output from the terminal'),
35
- command: z.string().optional().describe('The command that caused the error'),
36
- task_context: z.string().optional().describe('What was the user trying to accomplish'),
37
- working_directory: z.string().optional().describe('Working directory when error occurred'),
38
- project: z.string().optional().describe('Project name'),
39
- },
40
- async (params) => {
41
- const result: AnyResult = await call('error.report', {
42
- project: params.project ?? 'default',
43
- errorOutput: params.error_output,
44
- filePath: params.working_directory,
45
- taskContext: params.task_context,
46
- workingDirectory: params.working_directory,
47
- command: params.command,
48
- });
49
- let response = `Error #${result.errorId} recorded (${result.isNew ? 'new' : 'seen before'}).`;
50
- if (result.matches?.length > 0) {
51
- const best = result.matches[0];
52
- response += `\nSimilar error found (#${best.errorId}, ${Math.round(best.score * 100)}% match).`;
53
- }
54
- if (result.crossProjectMatches?.length > 0) {
55
- const best = result.crossProjectMatches[0];
56
- response += `\nCross-project match found (#${best.errorId}, ${Math.round(best.score * 100)}% match from another project).`;
57
- }
58
- return textResult(response);
59
- },
60
- );
61
-
62
- server.tool(
63
- 'brain_query_error',
64
- 'Search for similar errors and their solutions in the Brain database.',
65
- {
66
- query: z.string().describe('Error message or description to search for'),
67
- project_only: z.boolean().optional().describe('Only search in current project'),
68
- },
69
- async (params) => {
70
- const results: AnyResult = await call('error.query', {
71
- search: params.query,
72
- });
73
- if (!results?.length) return textResult('No matching errors found.');
74
- const lines = results.map((e: AnyResult) =>
75
- `#${e.id} [${e.errorType}] ${e.message?.slice(0, 120)}${e.resolved ? ' (resolved)' : ''}`
76
- );
77
- return textResult(`Found ${results.length} errors:\n${lines.join('\n')}`);
78
- },
79
- );
80
-
81
- server.tool(
82
- 'brain_report_solution',
83
- 'Report a successful solution for an error. Brain will learn from this.',
84
- {
85
- error_id: z.number().describe('The error ID this solution fixes'),
86
- description: z.string().describe('What was done to fix the error'),
87
- commands: z.string().optional().describe('Commands used to fix'),
88
- code_change: z.string().optional().describe('Code changes or diff'),
89
- },
90
- async (params) => {
91
- const solutionId: AnyResult = await call('solution.report', {
92
- errorId: params.error_id,
93
- description: params.description,
94
- commands: params.commands,
95
- codeChange: params.code_change,
96
- });
97
- return textResult(`Solution #${solutionId} recorded for error #${params.error_id}. Brain will use this to help with similar errors in the future.`);
98
- },
99
- );
100
-
101
- server.tool(
102
- 'brain_report_attempt',
103
- 'Report a failed solution attempt. Brain learns what does NOT work.',
104
- {
105
- error_id: z.number().describe('The error ID'),
106
- solution_id: z.number().describe('The solution ID that was attempted'),
107
- description: z.string().optional().describe('What was tried'),
108
- output: z.string().optional().describe('Output of the failed attempt'),
109
- },
110
- async (params) => {
111
- await call('solution.rate', {
112
- errorId: params.error_id,
113
- solutionId: params.solution_id,
114
- success: false,
115
- output: params.output,
116
- });
117
- return textResult(`Failed attempt recorded for error #${params.error_id}. Brain will avoid suggesting this approach for similar errors.`);
118
- },
119
- );
120
-
121
- // === Code Brain Tools ===
122
-
123
- server.tool(
124
- 'brain_find_reusable_code',
125
- 'Search for reusable code modules from other projects. Use when starting new functionality.',
126
- {
127
- purpose: z.string().describe('What the code should do (e.g., "retry with backoff", "JWT authentication")'),
128
- language: z.string().optional().describe('Programming language'),
129
- },
130
- async (params) => {
131
- const results: AnyResult = await call('code.find', {
132
- query: params.purpose,
133
- language: params.language,
134
- });
135
- if (!results?.length) return textResult('No reusable code modules found.');
136
- const lines = results.map((m: AnyResult) =>
137
- `#${m.id} [${m.language}] ${m.name} — ${m.description ?? 'no description'} (reusability: ${m.reusabilityScore ?? '?'})`
138
- );
139
- return textResult(`Found ${results.length} modules:\n${lines.join('\n')}`);
140
- },
141
- );
142
-
143
- server.tool(
144
- 'brain_register_code',
145
- 'Register a code module as reusable. Brain will analyze it and make it available to other projects.',
146
- {
147
- source_code: z.string().describe('The source code'),
148
- file_path: z.string().describe('File path relative to project root'),
149
- project: z.string().optional().describe('Project name'),
150
- name: z.string().optional().describe('Module name (optional - Brain auto-detects)'),
151
- language: z.string().optional().describe('Programming language'),
152
- description: z.string().optional().describe('What this code does'),
153
- },
154
- async (params) => {
155
- const result: AnyResult = await call('code.analyze', {
156
- project: params.project ?? 'default',
157
- name: params.name ?? params.file_path.split('/').pop() ?? 'unknown',
158
- filePath: params.file_path,
159
- language: params.language ?? detectLanguage(params.file_path),
160
- source: params.source_code,
161
- description: params.description,
162
- });
163
- return textResult(`Module #${result.moduleId} registered (${result.isNew ? 'new' : 'updated'}). Reusability score: ${result.reusabilityScore}.`);
164
- },
165
- );
166
-
167
- server.tool(
168
- 'brain_check_code_similarity',
169
- 'Check if similar code already exists in other projects before writing new code.',
170
- {
171
- source_code: z.string().describe('The code to check'),
172
- language: z.string().optional().describe('Programming language'),
173
- file_path: z.string().optional().describe('File path for context'),
174
- },
175
- async (params) => {
176
- const results: AnyResult = await call('code.similarity', {
177
- source: params.source_code,
178
- language: params.language ?? detectLanguage(params.file_path ?? ''),
179
- });
180
- if (!results?.length) return textResult('No similar code found. This appears to be unique.');
181
- const lines = results.map((m: AnyResult) =>
182
- `Module #${m.moduleId}: ${Math.round(m.score * 100)}% match (${m.matchType})`
183
- );
184
- return textResult(`Found ${results.length} similar modules:\n${lines.join('\n')}`);
185
- },
186
- );
187
-
188
- // === Synapse Network Tools ===
189
-
190
- server.tool(
191
- 'brain_explore',
192
- 'Explore what Brain knows about a topic. Uses spreading activation through the synapse network.',
193
- {
194
- node_type: z.string().describe('Type: error, solution, code_module, project'),
195
- node_id: z.number().describe('ID of the node to explore from'),
196
- max_depth: z.number().optional().describe('How many hops to follow (default: 3)'),
197
- },
198
- async (params) => {
199
- const context: AnyResult = await call('synapse.context', {
200
- errorId: params.node_id,
201
- });
202
- const sections: string[] = [];
203
- if (context.solutions?.length) sections.push(`Solutions: ${context.solutions.length} found`);
204
- if (context.relatedErrors?.length) sections.push(`Related errors: ${context.relatedErrors.length}`);
205
- if (context.relevantModules?.length) sections.push(`Relevant modules: ${context.relevantModules.length}`);
206
- if (context.preventionRules?.length) sections.push(`Prevention rules: ${context.preventionRules.length}`);
207
- if (context.insights?.length) sections.push(`Insights: ${context.insights.length}`);
208
- return textResult(sections.length ? sections.join('\n') : 'No connections found for this node.');
209
- },
210
- );
211
-
212
- server.tool(
213
- 'brain_connections',
214
- 'Find how two things are connected in Brain (e.g., how an error relates to a code module).',
215
- {
216
- from_type: z.string().describe('Source type: error, solution, code_module, project'),
217
- from_id: z.number().describe('Source ID'),
218
- to_type: z.string().describe('Target type'),
219
- to_id: z.number().describe('Target ID'),
220
- },
221
- async (params) => {
222
- const path: AnyResult = await call('synapse.path', params);
223
- if (!path) return textResult('No connection found between these nodes.');
224
- return textResult(path);
225
- },
226
- );
227
-
228
- // === Research Brain Tools ===
229
-
230
- server.tool(
231
- 'brain_insights',
232
- 'Get research insights: trends, gaps, synergies, template candidates, and project suggestions.',
233
- {
234
- type: z.string().optional().describe('Filter by type: trend, pattern, gap, synergy, optimization, template_candidate, project_suggestion, warning'),
235
- priority: z.string().optional().describe('Minimum priority: low, medium, high, critical'),
236
- },
237
- async (params) => {
238
- const insights: AnyResult = await call('research.insights', {
239
- type: params.type,
240
- activeOnly: true,
241
- limit: 20,
242
- });
243
- if (!insights?.length) return textResult('No active insights.');
244
- const lines = insights.map((i: AnyResult) =>
245
- `[${i.type}] ${i.title}: ${i.description?.slice(0, 150)}`
246
- );
247
- return textResult(`${insights.length} insights:\n${lines.join('\n')}`);
248
- },
249
- );
250
-
251
- server.tool(
252
- 'brain_rate_insight',
253
- 'Rate an insight as useful or not useful. Helps Brain learn what insights matter.',
254
- {
255
- insight_id: z.number().describe('The insight ID to rate'),
256
- rating: z.number().describe('Rating: 1 (useful), 0 (neutral), -1 (not useful)'),
257
- comment: z.string().optional().describe('Optional feedback comment'),
258
- },
259
- async (params) => {
260
- const success: AnyResult = await call('insight.rate', {
261
- id: params.insight_id,
262
- rating: params.rating,
263
- comment: params.comment,
264
- });
265
- return textResult(success ? `Insight #${params.insight_id} rated.` : `Insight #${params.insight_id} not found.`);
266
- },
267
- );
268
-
269
- server.tool(
270
- 'brain_suggest',
271
- 'Ask Brain for suggestions: what to build next, what to improve, what patterns to extract.',
272
- {
273
- context: z.string().describe('Current context or question'),
274
- },
275
- async (params) => {
276
- const suggestions: AnyResult = await call('research.suggest', {
277
- context: params.context,
278
- });
279
- return textResult(suggestions);
280
- },
281
- );
282
-
283
- // === Status & Notifications ===
284
-
285
- server.tool(
286
- 'brain_status',
287
- 'Get current Brain status: errors, solutions, code modules, synapse network, insights.',
288
- {},
289
- async () => {
290
- const summary: AnyResult = await call('analytics.summary', {});
291
- const network: AnyResult = await call('synapse.stats', {});
292
- const lines = [
293
- `Errors: ${summary.errors?.total ?? 0} total, ${summary.errors?.unresolved ?? 0} unresolved`,
294
- `Solutions: ${summary.solutions?.total ?? 0}`,
295
- `Rules: ${summary.rules?.active ?? 0} active`,
296
- `Code modules: ${summary.modules?.total ?? 0}`,
297
- `Insights: ${summary.insights?.active ?? 0} active`,
298
- `Synapses: ${network.totalSynapses ?? 0} connections`,
299
- ];
300
- return textResult(lines.join('\n'));
301
- },
302
- );
303
-
304
- server.tool(
305
- 'brain_notifications',
306
- 'Get pending notifications (new solutions, recurring errors, research insights).',
307
- {},
308
- async () => {
309
- const notifications: AnyResult = await call('notification.list', {});
310
- if (!notifications?.length) return textResult('No pending notifications.');
311
- const lines = notifications.map((n: AnyResult) =>
312
- `[${n.type}] ${n.title}: ${n.message?.slice(0, 120)}`
313
- );
314
- return textResult(`${notifications.length} notifications:\n${lines.join('\n')}`);
315
- },
316
- );
317
- }
318
-
319
- function detectLanguage(filePath: string): string {
320
- const ext = filePath.split('.').pop()?.toLowerCase() ?? '';
321
- const map: Record<string, string> = {
322
- ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript',
323
- py: 'python', rs: 'rust', go: 'go', java: 'java',
324
- c: 'c', cpp: 'cpp', h: 'c', hpp: 'cpp',
325
- rb: 'ruby', sh: 'shell', bash: 'shell',
326
- };
327
- return map[ext] ?? ext;
328
- }