@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
package/src/mcp/tools.ts CHANGED
@@ -1,290 +1,290 @@
1
- import { z } from 'zod';
2
- import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import type { IpcClient } from '../ipc/client.js';
4
-
5
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
- type AnyResult = any;
7
-
8
- function textResult(data: unknown): { content: Array<{ type: 'text'; text: string }> } {
9
- const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
10
- return { content: [{ type: 'text' as const, text }] };
11
- }
12
-
13
- export function registerTools(server: McpServer, ipc: IpcClient): void {
14
-
15
- // === Error Brain Tools ===
16
-
17
- server.tool(
18
- 'brain_report_error',
19
- 'Report an error that occurred. Brain stores it, matches against known errors, returns solutions if available.',
20
- {
21
- error_output: z.string().describe('The raw error output from the terminal'),
22
- command: z.string().optional().describe('The command that caused the error'),
23
- task_context: z.string().optional().describe('What was the user trying to accomplish'),
24
- working_directory: z.string().optional().describe('Working directory when error occurred'),
25
- project: z.string().optional().describe('Project name'),
26
- },
27
- async (params) => {
28
- const result: AnyResult = await ipc.request('error.report', {
29
- project: params.project ?? 'default',
30
- errorOutput: params.error_output,
31
- filePath: params.working_directory,
32
- });
33
- let response = `Error #${result.errorId} recorded (${result.isNew ? 'new' : 'seen before'}).`;
34
- if (result.matches?.length > 0) {
35
- const best = result.matches[0];
36
- response += `\nSimilar error found (#${best.errorId}, ${Math.round(best.score * 100)}% match).`;
37
- }
38
- return textResult(response);
39
- },
40
- );
41
-
42
- server.tool(
43
- 'brain_query_error',
44
- 'Search for similar errors and their solutions in the Brain database.',
45
- {
46
- query: z.string().describe('Error message or description to search for'),
47
- project_only: z.boolean().optional().describe('Only search in current project'),
48
- },
49
- async (params) => {
50
- const results: AnyResult = await ipc.request('error.query', {
51
- search: params.query,
52
- });
53
- if (!results?.length) return textResult('No matching errors found.');
54
- const lines = results.map((e: AnyResult) =>
55
- `#${e.id} [${e.errorType}] ${e.message?.slice(0, 120)}${e.resolved ? ' (resolved)' : ''}`
56
- );
57
- return textResult(`Found ${results.length} errors:\n${lines.join('\n')}`);
58
- },
59
- );
60
-
61
- server.tool(
62
- 'brain_report_solution',
63
- 'Report a successful solution for an error. Brain will learn from this.',
64
- {
65
- error_id: z.number().describe('The error ID this solution fixes'),
66
- description: z.string().describe('What was done to fix the error'),
67
- commands: z.string().optional().describe('Commands used to fix'),
68
- code_change: z.string().optional().describe('Code changes or diff'),
69
- },
70
- async (params) => {
71
- const solutionId: AnyResult = await ipc.request('solution.report', {
72
- errorId: params.error_id,
73
- description: params.description,
74
- commands: params.commands,
75
- codeChange: params.code_change,
76
- });
77
- return textResult(`Solution #${solutionId} recorded for error #${params.error_id}. Brain will use this to help with similar errors in the future.`);
78
- },
79
- );
80
-
81
- server.tool(
82
- 'brain_report_attempt',
83
- 'Report a failed solution attempt. Brain learns what does NOT work.',
84
- {
85
- error_id: z.number().describe('The error ID'),
86
- solution_id: z.number().describe('The solution ID that was attempted'),
87
- description: z.string().optional().describe('What was tried'),
88
- output: z.string().optional().describe('Output of the failed attempt'),
89
- },
90
- async (params) => {
91
- await ipc.request('solution.rate', {
92
- errorId: params.error_id,
93
- solutionId: params.solution_id,
94
- success: false,
95
- output: params.output,
96
- });
97
- return textResult(`Failed attempt recorded for error #${params.error_id}. Brain will avoid suggesting this approach for similar errors.`);
98
- },
99
- );
100
-
101
- // === Code Brain Tools ===
102
-
103
- server.tool(
104
- 'brain_find_reusable_code',
105
- 'Search for reusable code modules from other projects. Use when starting new functionality.',
106
- {
107
- purpose: z.string().describe('What the code should do (e.g., "retry with backoff", "JWT authentication")'),
108
- language: z.string().optional().describe('Programming language'),
109
- },
110
- async (params) => {
111
- const results: AnyResult = await ipc.request('code.find', {
112
- query: params.purpose,
113
- language: params.language,
114
- });
115
- if (!results?.length) return textResult('No reusable code modules found.');
116
- const lines = results.map((m: AnyResult) =>
117
- `#${m.id} [${m.language}] ${m.name} — ${m.description ?? 'no description'} (reusability: ${m.reusabilityScore ?? '?'})`
118
- );
119
- return textResult(`Found ${results.length} modules:\n${lines.join('\n')}`);
120
- },
121
- );
122
-
123
- server.tool(
124
- 'brain_register_code',
125
- 'Register a code module as reusable. Brain will analyze it and make it available to other projects.',
126
- {
127
- source_code: z.string().describe('The source code'),
128
- file_path: z.string().describe('File path relative to project root'),
129
- project: z.string().optional().describe('Project name'),
130
- name: z.string().optional().describe('Module name (optional - Brain auto-detects)'),
131
- language: z.string().optional().describe('Programming language'),
132
- description: z.string().optional().describe('What this code does'),
133
- },
134
- async (params) => {
135
- const result: AnyResult = await ipc.request('code.analyze', {
136
- project: params.project ?? 'default',
137
- name: params.name ?? params.file_path.split('/').pop() ?? 'unknown',
138
- filePath: params.file_path,
139
- language: params.language ?? detectLanguage(params.file_path),
140
- source: params.source_code,
141
- description: params.description,
142
- });
143
- return textResult(`Module #${result.moduleId} registered (${result.isNew ? 'new' : 'updated'}). Reusability score: ${result.reusabilityScore}.`);
144
- },
145
- );
146
-
147
- server.tool(
148
- 'brain_check_code_similarity',
149
- 'Check if similar code already exists in other projects before writing new code.',
150
- {
151
- source_code: z.string().describe('The code to check'),
152
- language: z.string().optional().describe('Programming language'),
153
- file_path: z.string().optional().describe('File path for context'),
154
- },
155
- async (params) => {
156
- const results: AnyResult = await ipc.request('code.similarity', {
157
- source: params.source_code,
158
- language: params.language ?? detectLanguage(params.file_path ?? ''),
159
- });
160
- if (!results?.length) return textResult('No similar code found. This appears to be unique.');
161
- const lines = results.map((m: AnyResult) =>
162
- `Module #${m.moduleId}: ${Math.round(m.score * 100)}% match (${m.matchType})`
163
- );
164
- return textResult(`Found ${results.length} similar modules:\n${lines.join('\n')}`);
165
- },
166
- );
167
-
168
- // === Synapse Network Tools ===
169
-
170
- server.tool(
171
- 'brain_explore',
172
- 'Explore what Brain knows about a topic. Uses spreading activation through the synapse network.',
173
- {
174
- node_type: z.string().describe('Type: error, solution, code_module, project'),
175
- node_id: z.number().describe('ID of the node to explore from'),
176
- max_depth: z.number().optional().describe('How many hops to follow (default: 3)'),
177
- },
178
- async (params) => {
179
- const context: AnyResult = await ipc.request('synapse.context', {
180
- errorId: params.node_id,
181
- });
182
- const sections: string[] = [];
183
- if (context.solutions?.length) sections.push(`Solutions: ${context.solutions.length} found`);
184
- if (context.relatedErrors?.length) sections.push(`Related errors: ${context.relatedErrors.length}`);
185
- if (context.relevantModules?.length) sections.push(`Relevant modules: ${context.relevantModules.length}`);
186
- if (context.preventionRules?.length) sections.push(`Prevention rules: ${context.preventionRules.length}`);
187
- if (context.insights?.length) sections.push(`Insights: ${context.insights.length}`);
188
- return textResult(sections.length ? sections.join('\n') : 'No connections found for this node.');
189
- },
190
- );
191
-
192
- server.tool(
193
- 'brain_connections',
194
- 'Find how two things are connected in Brain (e.g., how an error relates to a code module).',
195
- {
196
- from_type: z.string().describe('Source type: error, solution, code_module, project'),
197
- from_id: z.number().describe('Source ID'),
198
- to_type: z.string().describe('Target type'),
199
- to_id: z.number().describe('Target ID'),
200
- },
201
- async (params) => {
202
- const path: AnyResult = await ipc.request('synapse.path', params);
203
- if (!path) return textResult('No connection found between these nodes.');
204
- return textResult(path);
205
- },
206
- );
207
-
208
- // === Research Brain Tools ===
209
-
210
- server.tool(
211
- 'brain_insights',
212
- 'Get research insights: trends, gaps, synergies, template candidates, and project suggestions.',
213
- {
214
- type: z.string().optional().describe('Filter by type: trend, pattern, gap, synergy, optimization, template_candidate, project_suggestion, warning'),
215
- priority: z.string().optional().describe('Minimum priority: low, medium, high, critical'),
216
- },
217
- async (params) => {
218
- const insights: AnyResult = await ipc.request('research.insights', {
219
- type: params.type,
220
- activeOnly: true,
221
- limit: 20,
222
- });
223
- if (!insights?.length) return textResult('No active insights.');
224
- const lines = insights.map((i: AnyResult) =>
225
- `[${i.type}] ${i.title}: ${i.description?.slice(0, 150)}`
226
- );
227
- return textResult(`${insights.length} insights:\n${lines.join('\n')}`);
228
- },
229
- );
230
-
231
- server.tool(
232
- 'brain_suggest',
233
- 'Ask Brain for suggestions: what to build next, what to improve, what patterns to extract.',
234
- {
235
- context: z.string().describe('Current context or question'),
236
- },
237
- async (params) => {
238
- const suggestions: AnyResult = await ipc.request('research.suggest', {
239
- context: params.context,
240
- });
241
- return textResult(suggestions);
242
- },
243
- );
244
-
245
- // === Status & Notifications ===
246
-
247
- server.tool(
248
- 'brain_status',
249
- 'Get current Brain status: errors, solutions, code modules, synapse network, insights.',
250
- {},
251
- async () => {
252
- const summary: AnyResult = await ipc.request('analytics.summary', {});
253
- const network: AnyResult = await ipc.request('synapse.stats', {});
254
- const lines = [
255
- `Errors: ${summary.errors?.total ?? 0} total, ${summary.errors?.unresolved ?? 0} unresolved`,
256
- `Solutions: ${summary.solutions?.total ?? 0}`,
257
- `Rules: ${summary.rules?.active ?? 0} active`,
258
- `Code modules: ${summary.modules?.total ?? 0}`,
259
- `Insights: ${summary.insights?.active ?? 0} active`,
260
- `Synapses: ${network.totalSynapses ?? 0} connections`,
261
- ];
262
- return textResult(lines.join('\n'));
263
- },
264
- );
265
-
266
- server.tool(
267
- 'brain_notifications',
268
- 'Get pending notifications (new solutions, recurring errors, research insights).',
269
- {},
270
- async () => {
271
- const notifications: AnyResult = await ipc.request('notification.list', {});
272
- if (!notifications?.length) return textResult('No pending notifications.');
273
- const lines = notifications.map((n: AnyResult) =>
274
- `[${n.type}] ${n.title}: ${n.message?.slice(0, 120)}`
275
- );
276
- return textResult(`${notifications.length} notifications:\n${lines.join('\n')}`);
277
- },
278
- );
279
- }
280
-
281
- function detectLanguage(filePath: string): string {
282
- const ext = filePath.split('.').pop()?.toLowerCase() ?? '';
283
- const map: Record<string, string> = {
284
- ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript',
285
- py: 'python', rs: 'rust', go: 'go', java: 'java',
286
- c: 'c', cpp: 'cpp', h: 'c', hpp: 'cpp',
287
- rb: 'ruby', sh: 'shell', bash: 'shell',
288
- };
289
- return map[ext] ?? ext;
290
- }
1
+ import { z } from 'zod';
2
+ import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
+ import type { IpcClient } from '../ipc/client.js';
4
+
5
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
6
+ type AnyResult = any;
7
+
8
+ function textResult(data: unknown): { content: Array<{ type: 'text'; text: string }> } {
9
+ const text = typeof data === 'string' ? data : JSON.stringify(data, null, 2);
10
+ return { content: [{ type: 'text' as const, text }] };
11
+ }
12
+
13
+ export function registerTools(server: McpServer, ipc: IpcClient): void {
14
+
15
+ // === Error Brain Tools ===
16
+
17
+ server.tool(
18
+ 'brain_report_error',
19
+ 'Report an error that occurred. Brain stores it, matches against known errors, returns solutions if available.',
20
+ {
21
+ error_output: z.string().describe('The raw error output from the terminal'),
22
+ command: z.string().optional().describe('The command that caused the error'),
23
+ task_context: z.string().optional().describe('What was the user trying to accomplish'),
24
+ working_directory: z.string().optional().describe('Working directory when error occurred'),
25
+ project: z.string().optional().describe('Project name'),
26
+ },
27
+ async (params) => {
28
+ const result: AnyResult = await ipc.request('error.report', {
29
+ project: params.project ?? 'default',
30
+ errorOutput: params.error_output,
31
+ filePath: params.working_directory,
32
+ });
33
+ let response = `Error #${result.errorId} recorded (${result.isNew ? 'new' : 'seen before'}).`;
34
+ if (result.matches?.length > 0) {
35
+ const best = result.matches[0];
36
+ response += `\nSimilar error found (#${best.errorId}, ${Math.round(best.score * 100)}% match).`;
37
+ }
38
+ return textResult(response);
39
+ },
40
+ );
41
+
42
+ server.tool(
43
+ 'brain_query_error',
44
+ 'Search for similar errors and their solutions in the Brain database.',
45
+ {
46
+ query: z.string().describe('Error message or description to search for'),
47
+ project_only: z.boolean().optional().describe('Only search in current project'),
48
+ },
49
+ async (params) => {
50
+ const results: AnyResult = await ipc.request('error.query', {
51
+ search: params.query,
52
+ });
53
+ if (!results?.length) return textResult('No matching errors found.');
54
+ const lines = results.map((e: AnyResult) =>
55
+ `#${e.id} [${e.errorType}] ${e.message?.slice(0, 120)}${e.resolved ? ' (resolved)' : ''}`
56
+ );
57
+ return textResult(`Found ${results.length} errors:\n${lines.join('\n')}`);
58
+ },
59
+ );
60
+
61
+ server.tool(
62
+ 'brain_report_solution',
63
+ 'Report a successful solution for an error. Brain will learn from this.',
64
+ {
65
+ error_id: z.number().describe('The error ID this solution fixes'),
66
+ description: z.string().describe('What was done to fix the error'),
67
+ commands: z.string().optional().describe('Commands used to fix'),
68
+ code_change: z.string().optional().describe('Code changes or diff'),
69
+ },
70
+ async (params) => {
71
+ const solutionId: AnyResult = await ipc.request('solution.report', {
72
+ errorId: params.error_id,
73
+ description: params.description,
74
+ commands: params.commands,
75
+ codeChange: params.code_change,
76
+ });
77
+ return textResult(`Solution #${solutionId} recorded for error #${params.error_id}. Brain will use this to help with similar errors in the future.`);
78
+ },
79
+ );
80
+
81
+ server.tool(
82
+ 'brain_report_attempt',
83
+ 'Report a failed solution attempt. Brain learns what does NOT work.',
84
+ {
85
+ error_id: z.number().describe('The error ID'),
86
+ solution_id: z.number().describe('The solution ID that was attempted'),
87
+ description: z.string().optional().describe('What was tried'),
88
+ output: z.string().optional().describe('Output of the failed attempt'),
89
+ },
90
+ async (params) => {
91
+ await ipc.request('solution.rate', {
92
+ errorId: params.error_id,
93
+ solutionId: params.solution_id,
94
+ success: false,
95
+ output: params.output,
96
+ });
97
+ return textResult(`Failed attempt recorded for error #${params.error_id}. Brain will avoid suggesting this approach for similar errors.`);
98
+ },
99
+ );
100
+
101
+ // === Code Brain Tools ===
102
+
103
+ server.tool(
104
+ 'brain_find_reusable_code',
105
+ 'Search for reusable code modules from other projects. Use when starting new functionality.',
106
+ {
107
+ purpose: z.string().describe('What the code should do (e.g., "retry with backoff", "JWT authentication")'),
108
+ language: z.string().optional().describe('Programming language'),
109
+ },
110
+ async (params) => {
111
+ const results: AnyResult = await ipc.request('code.find', {
112
+ query: params.purpose,
113
+ language: params.language,
114
+ });
115
+ if (!results?.length) return textResult('No reusable code modules found.');
116
+ const lines = results.map((m: AnyResult) =>
117
+ `#${m.id} [${m.language}] ${m.name} — ${m.description ?? 'no description'} (reusability: ${m.reusabilityScore ?? '?'})`
118
+ );
119
+ return textResult(`Found ${results.length} modules:\n${lines.join('\n')}`);
120
+ },
121
+ );
122
+
123
+ server.tool(
124
+ 'brain_register_code',
125
+ 'Register a code module as reusable. Brain will analyze it and make it available to other projects.',
126
+ {
127
+ source_code: z.string().describe('The source code'),
128
+ file_path: z.string().describe('File path relative to project root'),
129
+ project: z.string().optional().describe('Project name'),
130
+ name: z.string().optional().describe('Module name (optional - Brain auto-detects)'),
131
+ language: z.string().optional().describe('Programming language'),
132
+ description: z.string().optional().describe('What this code does'),
133
+ },
134
+ async (params) => {
135
+ const result: AnyResult = await ipc.request('code.analyze', {
136
+ project: params.project ?? 'default',
137
+ name: params.name ?? params.file_path.split('/').pop() ?? 'unknown',
138
+ filePath: params.file_path,
139
+ language: params.language ?? detectLanguage(params.file_path),
140
+ source: params.source_code,
141
+ description: params.description,
142
+ });
143
+ return textResult(`Module #${result.moduleId} registered (${result.isNew ? 'new' : 'updated'}). Reusability score: ${result.reusabilityScore}.`);
144
+ },
145
+ );
146
+
147
+ server.tool(
148
+ 'brain_check_code_similarity',
149
+ 'Check if similar code already exists in other projects before writing new code.',
150
+ {
151
+ source_code: z.string().describe('The code to check'),
152
+ language: z.string().optional().describe('Programming language'),
153
+ file_path: z.string().optional().describe('File path for context'),
154
+ },
155
+ async (params) => {
156
+ const results: AnyResult = await ipc.request('code.similarity', {
157
+ source: params.source_code,
158
+ language: params.language ?? detectLanguage(params.file_path ?? ''),
159
+ });
160
+ if (!results?.length) return textResult('No similar code found. This appears to be unique.');
161
+ const lines = results.map((m: AnyResult) =>
162
+ `Module #${m.moduleId}: ${Math.round(m.score * 100)}% match (${m.matchType})`
163
+ );
164
+ return textResult(`Found ${results.length} similar modules:\n${lines.join('\n')}`);
165
+ },
166
+ );
167
+
168
+ // === Synapse Network Tools ===
169
+
170
+ server.tool(
171
+ 'brain_explore',
172
+ 'Explore what Brain knows about a topic. Uses spreading activation through the synapse network.',
173
+ {
174
+ node_type: z.string().describe('Type: error, solution, code_module, project'),
175
+ node_id: z.number().describe('ID of the node to explore from'),
176
+ max_depth: z.number().optional().describe('How many hops to follow (default: 3)'),
177
+ },
178
+ async (params) => {
179
+ const context: AnyResult = await ipc.request('synapse.context', {
180
+ errorId: params.node_id,
181
+ });
182
+ const sections: string[] = [];
183
+ if (context.solutions?.length) sections.push(`Solutions: ${context.solutions.length} found`);
184
+ if (context.relatedErrors?.length) sections.push(`Related errors: ${context.relatedErrors.length}`);
185
+ if (context.relevantModules?.length) sections.push(`Relevant modules: ${context.relevantModules.length}`);
186
+ if (context.preventionRules?.length) sections.push(`Prevention rules: ${context.preventionRules.length}`);
187
+ if (context.insights?.length) sections.push(`Insights: ${context.insights.length}`);
188
+ return textResult(sections.length ? sections.join('\n') : 'No connections found for this node.');
189
+ },
190
+ );
191
+
192
+ server.tool(
193
+ 'brain_connections',
194
+ 'Find how two things are connected in Brain (e.g., how an error relates to a code module).',
195
+ {
196
+ from_type: z.string().describe('Source type: error, solution, code_module, project'),
197
+ from_id: z.number().describe('Source ID'),
198
+ to_type: z.string().describe('Target type'),
199
+ to_id: z.number().describe('Target ID'),
200
+ },
201
+ async (params) => {
202
+ const path: AnyResult = await ipc.request('synapse.path', params);
203
+ if (!path) return textResult('No connection found between these nodes.');
204
+ return textResult(path);
205
+ },
206
+ );
207
+
208
+ // === Research Brain Tools ===
209
+
210
+ server.tool(
211
+ 'brain_insights',
212
+ 'Get research insights: trends, gaps, synergies, template candidates, and project suggestions.',
213
+ {
214
+ type: z.string().optional().describe('Filter by type: trend, pattern, gap, synergy, optimization, template_candidate, project_suggestion, warning'),
215
+ priority: z.string().optional().describe('Minimum priority: low, medium, high, critical'),
216
+ },
217
+ async (params) => {
218
+ const insights: AnyResult = await ipc.request('research.insights', {
219
+ type: params.type,
220
+ activeOnly: true,
221
+ limit: 20,
222
+ });
223
+ if (!insights?.length) return textResult('No active insights.');
224
+ const lines = insights.map((i: AnyResult) =>
225
+ `[${i.type}] ${i.title}: ${i.description?.slice(0, 150)}`
226
+ );
227
+ return textResult(`${insights.length} insights:\n${lines.join('\n')}`);
228
+ },
229
+ );
230
+
231
+ server.tool(
232
+ 'brain_suggest',
233
+ 'Ask Brain for suggestions: what to build next, what to improve, what patterns to extract.',
234
+ {
235
+ context: z.string().describe('Current context or question'),
236
+ },
237
+ async (params) => {
238
+ const suggestions: AnyResult = await ipc.request('research.suggest', {
239
+ context: params.context,
240
+ });
241
+ return textResult(suggestions);
242
+ },
243
+ );
244
+
245
+ // === Status & Notifications ===
246
+
247
+ server.tool(
248
+ 'brain_status',
249
+ 'Get current Brain status: errors, solutions, code modules, synapse network, insights.',
250
+ {},
251
+ async () => {
252
+ const summary: AnyResult = await ipc.request('analytics.summary', {});
253
+ const network: AnyResult = await ipc.request('synapse.stats', {});
254
+ const lines = [
255
+ `Errors: ${summary.errors?.total ?? 0} total, ${summary.errors?.unresolved ?? 0} unresolved`,
256
+ `Solutions: ${summary.solutions?.total ?? 0}`,
257
+ `Rules: ${summary.rules?.active ?? 0} active`,
258
+ `Code modules: ${summary.modules?.total ?? 0}`,
259
+ `Insights: ${summary.insights?.active ?? 0} active`,
260
+ `Synapses: ${network.totalSynapses ?? 0} connections`,
261
+ ];
262
+ return textResult(lines.join('\n'));
263
+ },
264
+ );
265
+
266
+ server.tool(
267
+ 'brain_notifications',
268
+ 'Get pending notifications (new solutions, recurring errors, research insights).',
269
+ {},
270
+ async () => {
271
+ const notifications: AnyResult = await ipc.request('notification.list', {});
272
+ if (!notifications?.length) return textResult('No pending notifications.');
273
+ const lines = notifications.map((n: AnyResult) =>
274
+ `[${n.type}] ${n.title}: ${n.message?.slice(0, 120)}`
275
+ );
276
+ return textResult(`${notifications.length} notifications:\n${lines.join('\n')}`);
277
+ },
278
+ );
279
+ }
280
+
281
+ function detectLanguage(filePath: string): string {
282
+ const ext = filePath.split('.').pop()?.toLowerCase() ?? '';
283
+ const map: Record<string, string> = {
284
+ ts: 'typescript', tsx: 'typescript', js: 'javascript', jsx: 'javascript',
285
+ py: 'python', rs: 'rust', go: 'go', java: 'java',
286
+ c: 'c', cpp: 'cpp', h: 'c', hpp: 'cpp',
287
+ rb: 'ruby', sh: 'shell', bash: 'shell',
288
+ };
289
+ return map[ext] ?? ext;
290
+ }
@@ -1,28 +1,28 @@
1
- import { ErrorParserRegistry } from './types.js';
2
- import { nodeParser } from './parsers/node.js';
3
- import { pythonParser } from './parsers/python.js';
4
- import { rustParser } from './parsers/rust.js';
5
- import { goParser } from './parsers/go.js';
6
- import { shellParser } from './parsers/shell.js';
7
- import { compilerParser } from './parsers/compiler.js';
8
- import { genericParser } from './parsers/generic.js';
9
-
10
- let registryInstance: ErrorParserRegistry | null = null;
11
-
12
- export function getParserRegistry(): ErrorParserRegistry {
13
- if (!registryInstance) {
14
- registryInstance = new ErrorParserRegistry();
15
- registryInstance.register(nodeParser);
16
- registryInstance.register(pythonParser);
17
- registryInstance.register(rustParser);
18
- registryInstance.register(goParser);
19
- registryInstance.register(shellParser);
20
- registryInstance.register(compilerParser);
21
- registryInstance.register(genericParser);
22
- }
23
- return registryInstance;
24
- }
25
-
26
- export function parseError(input: string) {
27
- return getParserRegistry().parse(input);
28
- }
1
+ import { ErrorParserRegistry } from './types.js';
2
+ import { nodeParser } from './parsers/node.js';
3
+ import { pythonParser } from './parsers/python.js';
4
+ import { rustParser } from './parsers/rust.js';
5
+ import { goParser } from './parsers/go.js';
6
+ import { shellParser } from './parsers/shell.js';
7
+ import { compilerParser } from './parsers/compiler.js';
8
+ import { genericParser } from './parsers/generic.js';
9
+
10
+ let registryInstance: ErrorParserRegistry | null = null;
11
+
12
+ export function getParserRegistry(): ErrorParserRegistry {
13
+ if (!registryInstance) {
14
+ registryInstance = new ErrorParserRegistry();
15
+ registryInstance.register(nodeParser);
16
+ registryInstance.register(pythonParser);
17
+ registryInstance.register(rustParser);
18
+ registryInstance.register(goParser);
19
+ registryInstance.register(shellParser);
20
+ registryInstance.register(compilerParser);
21
+ registryInstance.register(genericParser);
22
+ }
23
+ return registryInstance;
24
+ }
25
+
26
+ export function parseError(input: string) {
27
+ return getParserRegistry().parse(input);
28
+ }