@timmeck/brain 1.9.0 → 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 (214) hide show
  1. package/README.md +19 -0
  2. package/brain.log +1164 -0
  3. package/{src/cli/commands/dashboard.ts → dashboard.html} +688 -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 +1 -0
  8. package/dist/brain.js +6 -1
  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/peers.d.ts +2 -0
  14. package/dist/cli/commands/peers.js +38 -0
  15. package/dist/cli/commands/peers.js.map +1 -0
  16. package/dist/db/connection.d.ts +1 -2
  17. package/dist/db/connection.js +1 -18
  18. package/dist/db/connection.js.map +1 -1
  19. package/dist/index.js +3 -1
  20. package/dist/index.js.map +1 -1
  21. package/dist/ipc/__tests__/protocol.test.d.ts +1 -0
  22. package/dist/ipc/__tests__/protocol.test.js +117 -0
  23. package/dist/ipc/__tests__/protocol.test.js.map +1 -0
  24. package/dist/ipc/client.d.ts +1 -16
  25. package/dist/ipc/client.js +1 -100
  26. package/dist/ipc/client.js.map +1 -1
  27. package/dist/ipc/protocol.d.ts +1 -8
  28. package/dist/ipc/protocol.js +1 -28
  29. package/dist/ipc/protocol.js.map +1 -1
  30. package/dist/ipc/router.js +8 -0
  31. package/dist/ipc/router.js.map +1 -1
  32. package/dist/ipc/server.d.ts +1 -22
  33. package/dist/ipc/server.js +1 -163
  34. package/dist/ipc/server.js.map +1 -1
  35. package/dist/mcp/http-server.d.ts +1 -7
  36. package/dist/mcp/http-server.js +6 -117
  37. package/dist/mcp/http-server.js.map +1 -1
  38. package/dist/mcp/server.js +5 -61
  39. package/dist/mcp/server.js.map +1 -1
  40. package/dist/signals/__tests__/fingerprint.test.d.ts +1 -0
  41. package/dist/signals/__tests__/fingerprint.test.js +118 -0
  42. package/dist/signals/__tests__/fingerprint.test.js.map +1 -0
  43. package/dist/types/ipc.types.d.ts +1 -11
  44. package/dist/utils/__tests__/hash.test.d.ts +1 -0
  45. package/dist/utils/__tests__/hash.test.js +32 -0
  46. package/dist/utils/__tests__/hash.test.js.map +1 -0
  47. package/dist/utils/__tests__/paths.test.d.ts +1 -0
  48. package/dist/utils/__tests__/paths.test.js +75 -0
  49. package/dist/utils/__tests__/paths.test.js.map +1 -0
  50. package/dist/utils/events.d.ts +4 -8
  51. package/dist/utils/events.js +2 -14
  52. package/dist/utils/events.js.map +1 -1
  53. package/dist/utils/hash.d.ts +1 -1
  54. package/dist/utils/hash.js +1 -4
  55. package/dist/utils/hash.js.map +1 -1
  56. package/dist/utils/logger.d.ts +3 -2
  57. package/dist/utils/logger.js +8 -35
  58. package/dist/utils/logger.js.map +1 -1
  59. package/dist/utils/paths.d.ts +2 -1
  60. package/dist/utils/paths.js +4 -13
  61. package/dist/utils/paths.js.map +1 -1
  62. package/package.json +2 -1
  63. package/BRAIN_PLAN.md +0 -3324
  64. package/reddit_post.md +0 -45
  65. package/src/api/server.ts +0 -395
  66. package/src/brain.ts +0 -313
  67. package/src/cli/colors.ts +0 -116
  68. package/src/cli/commands/config.ts +0 -169
  69. package/src/cli/commands/doctor.ts +0 -124
  70. package/src/cli/commands/explain.ts +0 -83
  71. package/src/cli/commands/export.ts +0 -31
  72. package/src/cli/commands/import.ts +0 -199
  73. package/src/cli/commands/insights.ts +0 -65
  74. package/src/cli/commands/learn.ts +0 -24
  75. package/src/cli/commands/modules.ts +0 -53
  76. package/src/cli/commands/network.ts +0 -67
  77. package/src/cli/commands/projects.ts +0 -42
  78. package/src/cli/commands/query.ts +0 -120
  79. package/src/cli/commands/start.ts +0 -105
  80. package/src/cli/commands/status.ts +0 -75
  81. package/src/cli/commands/stop.ts +0 -34
  82. package/src/cli/ipc-helper.ts +0 -22
  83. package/src/cli/update-check.ts +0 -63
  84. package/src/code/analyzer.ts +0 -117
  85. package/src/code/fingerprint.ts +0 -87
  86. package/src/code/matcher.ts +0 -129
  87. package/src/code/parsers/generic.ts +0 -29
  88. package/src/code/parsers/python.ts +0 -54
  89. package/src/code/parsers/typescript.ts +0 -65
  90. package/src/code/registry.ts +0 -60
  91. package/src/code/scorer.ts +0 -120
  92. package/src/config.ts +0 -135
  93. package/src/dashboard/server.ts +0 -142
  94. package/src/db/connection.ts +0 -22
  95. package/src/db/migrations/001_core_schema.ts +0 -120
  96. package/src/db/migrations/002_learning_schema.ts +0 -38
  97. package/src/db/migrations/003_code_schema.ts +0 -53
  98. package/src/db/migrations/004_synapses_schema.ts +0 -57
  99. package/src/db/migrations/005_fts_indexes.ts +0 -78
  100. package/src/db/migrations/006_synapses_phase3.ts +0 -17
  101. package/src/db/migrations/007_feedback.ts +0 -13
  102. package/src/db/migrations/008_git_integration.ts +0 -38
  103. package/src/db/migrations/009_embeddings.ts +0 -8
  104. package/src/db/migrations/index.ts +0 -70
  105. package/src/db/repositories/antipattern.repository.ts +0 -66
  106. package/src/db/repositories/code-module.repository.ts +0 -142
  107. package/src/db/repositories/error.repository.ts +0 -189
  108. package/src/db/repositories/insight.repository.ts +0 -99
  109. package/src/db/repositories/notification.repository.ts +0 -66
  110. package/src/db/repositories/project.repository.ts +0 -93
  111. package/src/db/repositories/rule.repository.ts +0 -108
  112. package/src/db/repositories/solution.repository.ts +0 -154
  113. package/src/db/repositories/synapse.repository.ts +0 -163
  114. package/src/db/repositories/terminal.repository.ts +0 -101
  115. package/src/embeddings/engine.ts +0 -238
  116. package/src/hooks/post-tool-use.ts +0 -92
  117. package/src/hooks/post-write.ts +0 -129
  118. package/src/index.ts +0 -63
  119. package/src/ipc/client.ts +0 -118
  120. package/src/ipc/protocol.ts +0 -35
  121. package/src/ipc/router.ts +0 -133
  122. package/src/ipc/server.ts +0 -176
  123. package/src/learning/confidence-scorer.ts +0 -80
  124. package/src/learning/decay.ts +0 -46
  125. package/src/learning/learning-engine.ts +0 -170
  126. package/src/learning/pattern-extractor.ts +0 -90
  127. package/src/learning/rule-generator.ts +0 -74
  128. package/src/main.rs:10:5 +0 -0
  129. package/src/matching/error-matcher.ts +0 -166
  130. package/src/matching/fingerprint.ts +0 -34
  131. package/src/matching/similarity.ts +0 -61
  132. package/src/matching/tfidf.ts +0 -74
  133. package/src/matching/tokenizer.ts +0 -41
  134. package/src/mcp/auto-detect.ts +0 -93
  135. package/src/mcp/http-server.ts +0 -140
  136. package/src/mcp/server.ts +0 -73
  137. package/src/mcp/tools.ts +0 -328
  138. package/src/parsing/error-parser.ts +0 -28
  139. package/src/parsing/parsers/compiler.ts +0 -93
  140. package/src/parsing/parsers/generic.ts +0 -28
  141. package/src/parsing/parsers/go.ts +0 -97
  142. package/src/parsing/parsers/node.ts +0 -69
  143. package/src/parsing/parsers/python.ts +0 -62
  144. package/src/parsing/parsers/rust.ts +0 -50
  145. package/src/parsing/parsers/shell.ts +0 -42
  146. package/src/parsing/types.ts +0 -47
  147. package/src/research/gap-analyzer.ts +0 -135
  148. package/src/research/insight-generator.ts +0 -123
  149. package/src/research/research-engine.ts +0 -116
  150. package/src/research/synergy-detector.ts +0 -126
  151. package/src/research/template-extractor.ts +0 -130
  152. package/src/research/trend-analyzer.ts +0 -127
  153. package/src/services/analytics.service.ts +0 -226
  154. package/src/services/code.service.ts +0 -271
  155. package/src/services/error.service.ts +0 -266
  156. package/src/services/git.service.ts +0 -132
  157. package/src/services/notification.service.ts +0 -41
  158. package/src/services/prevention.service.ts +0 -159
  159. package/src/services/research.service.ts +0 -98
  160. package/src/services/solution.service.ts +0 -174
  161. package/src/services/synapse.service.ts +0 -59
  162. package/src/services/terminal.service.ts +0 -81
  163. package/src/synapses/activation.ts +0 -80
  164. package/src/synapses/decay.ts +0 -38
  165. package/src/synapses/hebbian.ts +0 -69
  166. package/src/synapses/pathfinder.ts +0 -81
  167. package/src/synapses/synapse-manager.ts +0 -113
  168. package/src/types/code.types.ts +0 -52
  169. package/src/types/config.types.ts +0 -103
  170. package/src/types/error.types.ts +0 -67
  171. package/src/types/ipc.types.ts +0 -8
  172. package/src/types/mcp.types.ts +0 -53
  173. package/src/types/research.types.ts +0 -28
  174. package/src/types/solution.types.ts +0 -30
  175. package/src/types/synapse.types.ts +0 -50
  176. package/src/utils/events.ts +0 -45
  177. package/src/utils/hash.ts +0 -5
  178. package/src/utils/logger.ts +0 -48
  179. package/src/utils/paths.ts +0 -19
  180. package/tests/e2e/test_code_intelligence.py +0 -1015
  181. package/tests/e2e/test_error_memory.py +0 -451
  182. package/tests/e2e/test_full_integration.py +0 -534
  183. package/tests/fixtures/code-modules/modules.ts +0 -83
  184. package/tests/fixtures/errors/go.ts +0 -9
  185. package/tests/fixtures/errors/node.ts +0 -24
  186. package/tests/fixtures/errors/python.ts +0 -21
  187. package/tests/fixtures/errors/rust.ts +0 -25
  188. package/tests/fixtures/errors/shell.ts +0 -15
  189. package/tests/fixtures/solutions/solutions.ts +0 -27
  190. package/tests/helpers/setup-db.ts +0 -52
  191. package/tests/integration/code-flow.test.ts +0 -86
  192. package/tests/integration/error-flow.test.ts +0 -83
  193. package/tests/integration/ipc-flow.test.ts +0 -166
  194. package/tests/integration/learning-cycle.test.ts +0 -82
  195. package/tests/integration/synapse-flow.test.ts +0 -117
  196. package/tests/unit/code/analyzer.test.ts +0 -58
  197. package/tests/unit/code/fingerprint.test.ts +0 -51
  198. package/tests/unit/code/scorer.test.ts +0 -55
  199. package/tests/unit/learning/confidence-scorer.test.ts +0 -60
  200. package/tests/unit/learning/decay.test.ts +0 -45
  201. package/tests/unit/learning/pattern-extractor.test.ts +0 -50
  202. package/tests/unit/matching/error-matcher.test.ts +0 -69
  203. package/tests/unit/matching/fingerprint.test.ts +0 -47
  204. package/tests/unit/matching/similarity.test.ts +0 -65
  205. package/tests/unit/matching/tfidf.test.ts +0 -71
  206. package/tests/unit/matching/tokenizer.test.ts +0 -83
  207. package/tests/unit/parsing/parsers.test.ts +0 -113
  208. package/tests/unit/research/gap-analyzer.test.ts +0 -45
  209. package/tests/unit/research/trend-analyzer.test.ts +0 -45
  210. package/tests/unit/synapses/activation.test.ts +0 -80
  211. package/tests/unit/synapses/decay.test.ts +0 -27
  212. package/tests/unit/synapses/hebbian.test.ts +0 -96
  213. package/tests/unit/synapses/pathfinder.test.ts +0 -72
  214. package/tsconfig.json +0 -18
package/reddit_post.md DELETED
@@ -1,45 +0,0 @@
1
- # Reddit Post for r/ClaudeAI
2
-
3
- ## Title:
4
- I built a persistent memory system for Claude Code — it remembers every error, solution, and code module across all your projects
5
-
6
- ## Body:
7
-
8
- Claude Code starts fresh every session. After months of hitting the same errors and rewriting the same code, I built **Brain** — an MCP server that gives Claude Code a persistent memory.
9
-
10
- ### What it does
11
-
12
- - **Error Memory** — Brain remembers every error you've encountered and every solution that worked. Next time you hit the same bug, it suggests the fix with a confidence score.
13
- - **Cross-Project Learning** — Fixed a bug in project A? Brain suggests the same fix when a similar error appears in project B.
14
- - **Code Intelligence** — Before writing new code, Brain checks if similar modules already exist across your 36 projects. No more reinventing the wheel.
15
- - **Hebbian Synapse Network** — 37,000+ weighted connections between errors, solutions, and code modules. Connections strengthen with use, like biological synapses.
16
- - **Proactive Prevention** — Post-write hooks check your code against known antipatterns *before* errors occur.
17
- - **Auto-Detect** — Hooks catch errors in real-time from Bash output and report them to Brain automatically. You don't have to do anything.
18
-
19
- ### The stack
20
-
21
- - TypeScript, better-sqlite3, MCP SDK
22
- - 13 MCP tools for Claude Code
23
- - REST API (30+ endpoints) + MCP over HTTP/SSE (works with Cursor, Windsurf, Cline)
24
- - Local embeddings (all-MiniLM-L6-v2) for hybrid search — no cloud, no API keys
25
- - Interactive dashboard with live synapse network visualization
26
- - Full CLI: `brain status`, `brain query`, `brain explain`, `brain dashboard`
27
-
28
- ### Real numbers from my setup
29
-
30
- - 18,160 code modules indexed across 36 projects
31
- - 37,277 synapse connections
32
- - 4,902 research insights generated automatically
33
- - 139 tests, all passing
34
-
35
- ### Install
36
-
37
- ```
38
- npm install -g @timmeck/brain
39
- ```
40
-
41
- Setup takes 2 minutes — add the MCP server + hooks to your Claude Code settings and run `brain start`. That's it.
42
-
43
- GitHub: https://github.com/timmeck/brain
44
-
45
- MIT licensed, open source. Would love feedback — especially on what MCP tools would be most useful to add next.
package/src/api/server.ts DELETED
@@ -1,395 +0,0 @@
1
- import http from 'node:http';
2
- import { getLogger } from '../utils/logger.js';
3
- import { getEventBus } from '../utils/events.js';
4
- import type { IpcRouter } from '../ipc/router.js';
5
-
6
- export interface ApiServerOptions {
7
- port: number;
8
- router: IpcRouter;
9
- apiKey?: string;
10
- }
11
-
12
- interface RouteDefinition {
13
- method: string;
14
- pattern: RegExp;
15
- ipcMethod: string;
16
- extractParams: (match: RegExpMatchArray, query: URLSearchParams, body?: unknown) => unknown;
17
- }
18
-
19
- export class ApiServer {
20
- private server: http.Server | null = null;
21
- private logger = getLogger();
22
- private routes: RouteDefinition[];
23
- private sseClients: Set<http.ServerResponse> = new Set();
24
- private statsTimer: ReturnType<typeof setInterval> | null = null;
25
-
26
- constructor(private options: ApiServerOptions) {
27
- this.routes = this.buildRoutes();
28
- }
29
-
30
- start(): void {
31
- const { port, apiKey } = this.options;
32
-
33
- this.server = http.createServer((req, res) => {
34
- res.setHeader('Access-Control-Allow-Origin', '*');
35
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
36
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');
37
-
38
- if (req.method === 'OPTIONS') {
39
- res.writeHead(204);
40
- res.end();
41
- return;
42
- }
43
-
44
- if (apiKey) {
45
- const provided = (req.headers['x-api-key'] as string) ??
46
- req.headers.authorization?.replace('Bearer ', '');
47
- if (provided !== apiKey) {
48
- this.json(res, 401, { error: 'Unauthorized', message: 'Invalid or missing API key' });
49
- return;
50
- }
51
- }
52
-
53
- this.handleRequest(req, res).catch((err) => {
54
- this.logger.error('API error:', err);
55
- this.json(res, 500, {
56
- error: 'Internal Server Error',
57
- message: err instanceof Error ? err.message : String(err),
58
- });
59
- });
60
- });
61
-
62
- this.server.listen(port, () => {
63
- this.logger.info(`REST API server started on http://localhost:${port}`);
64
- });
65
-
66
- this.setupSSE();
67
- }
68
-
69
- stop(): void {
70
- if (this.statsTimer) {
71
- clearInterval(this.statsTimer);
72
- this.statsTimer = null;
73
- }
74
- for (const client of this.sseClients) {
75
- try { client.end(); } catch { /* ignore */ }
76
- }
77
- this.sseClients.clear();
78
- this.server?.close();
79
- this.server = null;
80
- this.logger.info('REST API server stopped');
81
- }
82
-
83
- private async handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
84
- const url = new URL(req.url ?? '/', 'http://localhost');
85
- const pathname = url.pathname;
86
- const method = req.method ?? 'GET';
87
- const query = url.searchParams;
88
-
89
- // Health check
90
- if (pathname === '/api/v1/health') {
91
- this.json(res, 200, { status: 'ok', timestamp: new Date().toISOString() });
92
- return;
93
- }
94
-
95
- // SSE event stream (for live dashboard)
96
- if (pathname === '/api/v1/events' && method === 'GET') {
97
- res.writeHead(200, {
98
- 'Content-Type': 'text/event-stream',
99
- 'Cache-Control': 'no-cache',
100
- 'Connection': 'keep-alive',
101
- });
102
- res.write('data: {"type":"connected"}\n\n');
103
- this.sseClients.add(res);
104
- req.on('close', () => this.sseClients.delete(res));
105
- return;
106
- }
107
-
108
- // List all available methods
109
- if (pathname === '/api/v1/methods' && method === 'GET') {
110
- const methods = this.options.router.listMethods();
111
- this.json(res, 200, {
112
- methods,
113
- rpcEndpoint: '/api/v1/rpc',
114
- usage: 'POST /api/v1/rpc with body { "method": "<method>", "params": {...} }',
115
- });
116
- return;
117
- }
118
-
119
- // Generic RPC endpoint — the universal gateway
120
- if (pathname === '/api/v1/rpc' && method === 'POST') {
121
- const body = await this.readBody(req);
122
- if (!body) {
123
- this.json(res, 400, { error: 'Bad Request', message: 'Empty request body' });
124
- return;
125
- }
126
-
127
- const parsed = JSON.parse(body);
128
-
129
- // Batch RPC support
130
- if (Array.isArray(parsed)) {
131
- const results = parsed.map((call: { method: string; params?: unknown; id?: string | number }) => {
132
- try {
133
- const result = this.options.router.handle(call.method, call.params ?? {});
134
- return { id: call.id, result };
135
- } catch (err) {
136
- return { id: call.id, error: err instanceof Error ? err.message : String(err) };
137
- }
138
- });
139
- this.json(res, 200, results);
140
- return;
141
- }
142
-
143
- if (!parsed.method) {
144
- this.json(res, 400, { error: 'Bad Request', message: 'Missing "method" field' });
145
- return;
146
- }
147
-
148
- try {
149
- const result = this.options.router.handle(parsed.method, parsed.params ?? {});
150
- this.json(res, 200, { result });
151
- } catch (err) {
152
- this.json(res, 400, { error: err instanceof Error ? err.message : String(err) });
153
- }
154
- return;
155
- }
156
-
157
- // RESTful routes
158
- let body: unknown = undefined;
159
- if (method === 'POST' || method === 'PUT') {
160
- try {
161
- const raw = await this.readBody(req);
162
- body = raw ? JSON.parse(raw) : {};
163
- } catch {
164
- this.json(res, 400, { error: 'Bad Request', message: 'Invalid JSON body' });
165
- return;
166
- }
167
- }
168
-
169
- for (const route of this.routes) {
170
- if (route.method !== method) continue;
171
- const match = pathname.match(route.pattern);
172
- if (!match) continue;
173
-
174
- try {
175
- const params = route.extractParams(match, query, body);
176
- const result = this.options.router.handle(route.ipcMethod, params);
177
- this.json(res, method === 'POST' ? 201 : 200, { result });
178
- } catch (err) {
179
- const msg = err instanceof Error ? err.message : String(err);
180
- const status = msg.startsWith('Unknown method') ? 404 : 400;
181
- this.json(res, status, { error: msg });
182
- }
183
- return;
184
- }
185
-
186
- this.json(res, 404, { error: 'Not Found', message: `No route for ${method} ${pathname}` });
187
- }
188
-
189
- private buildRoutes(): RouteDefinition[] {
190
- return [
191
- // ─── Errors ────────────────────────────────────────────
192
- { method: 'POST', pattern: /^\/api\/v1\/errors$/, ipcMethod: 'error.report',
193
- extractParams: (_m, _q, body) => body },
194
- { method: 'GET', pattern: /^\/api\/v1\/errors$/, ipcMethod: 'error.query',
195
- extractParams: (_m, q) => ({
196
- search: q.get('search') ?? '',
197
- projectId: q.get('projectId') ? Number(q.get('projectId')) : undefined,
198
- }) },
199
- { method: 'GET', pattern: /^\/api\/v1\/errors\/(\d+)$/, ipcMethod: 'error.get',
200
- extractParams: (m) => ({ id: Number(m[1]) }) },
201
- { method: 'GET', pattern: /^\/api\/v1\/errors\/(\d+)\/match$/, ipcMethod: 'error.match',
202
- extractParams: (m) => ({ errorId: Number(m[1]) }) },
203
- { method: 'GET', pattern: /^\/api\/v1\/errors\/(\d+)\/chain$/, ipcMethod: 'error.chain',
204
- extractParams: (m) => ({ errorId: Number(m[1]) }) },
205
- { method: 'POST', pattern: /^\/api\/v1\/errors\/(\d+)\/resolve$/, ipcMethod: 'error.resolve',
206
- extractParams: (m, _q, body) => ({ errorId: Number(m[1]), ...(body as object) }) },
207
-
208
- // ─── Solutions ─────────────────────────────────────────
209
- { method: 'POST', pattern: /^\/api\/v1\/solutions$/, ipcMethod: 'solution.report',
210
- extractParams: (_m, _q, body) => body },
211
- { method: 'GET', pattern: /^\/api\/v1\/solutions$/, ipcMethod: 'solution.query',
212
- extractParams: (_m, q) => ({
213
- errorId: q.get('errorId') ? Number(q.get('errorId')) : undefined,
214
- }) },
215
- { method: 'POST', pattern: /^\/api\/v1\/solutions\/rate$/, ipcMethod: 'solution.rate',
216
- extractParams: (_m, _q, body) => body },
217
- { method: 'GET', pattern: /^\/api\/v1\/solutions\/efficiency$/, ipcMethod: 'solution.efficiency',
218
- extractParams: () => ({}) },
219
-
220
- // ─── Projects ──────────────────────────────────────────
221
- { method: 'GET', pattern: /^\/api\/v1\/projects$/, ipcMethod: 'project.list',
222
- extractParams: () => ({}) },
223
-
224
- // ─── Code ──────────────────────────────────────────────
225
- { method: 'POST', pattern: /^\/api\/v1\/code\/analyze$/, ipcMethod: 'code.analyze',
226
- extractParams: (_m, _q, body) => body },
227
- { method: 'POST', pattern: /^\/api\/v1\/code\/find$/, ipcMethod: 'code.find',
228
- extractParams: (_m, _q, body) => body },
229
- { method: 'POST', pattern: /^\/api\/v1\/code\/similarity$/, ipcMethod: 'code.similarity',
230
- extractParams: (_m, _q, body) => body },
231
- { method: 'GET', pattern: /^\/api\/v1\/code\/modules$/, ipcMethod: 'code.modules',
232
- extractParams: (_m, q) => ({
233
- projectId: q.get('projectId') ? Number(q.get('projectId')) : undefined,
234
- language: q.get('language') ?? undefined,
235
- limit: q.get('limit') ? Number(q.get('limit')) : undefined,
236
- }) },
237
- { method: 'GET', pattern: /^\/api\/v1\/code\/(\d+)$/, ipcMethod: 'code.get',
238
- extractParams: (m) => ({ id: Number(m[1]) }) },
239
-
240
- // ─── Prevention ────────────────────────────────────────
241
- { method: 'POST', pattern: /^\/api\/v1\/prevention\/check$/, ipcMethod: 'prevention.check',
242
- extractParams: (_m, _q, body) => body },
243
- { method: 'POST', pattern: /^\/api\/v1\/prevention\/antipatterns$/, ipcMethod: 'prevention.antipatterns',
244
- extractParams: (_m, _q, body) => body },
245
- { method: 'POST', pattern: /^\/api\/v1\/prevention\/code$/, ipcMethod: 'prevention.checkCode',
246
- extractParams: (_m, _q, body) => body },
247
-
248
- // ─── Synapses ─────────────────────────────────────────
249
- { method: 'GET', pattern: /^\/api\/v1\/synapses\/context\/(\d+)$/, ipcMethod: 'synapse.context',
250
- extractParams: (m) => ({ errorId: Number(m[1]) }) },
251
- { method: 'POST', pattern: /^\/api\/v1\/synapses\/path$/, ipcMethod: 'synapse.path',
252
- extractParams: (_m, _q, body) => body },
253
- { method: 'POST', pattern: /^\/api\/v1\/synapses\/related$/, ipcMethod: 'synapse.related',
254
- extractParams: (_m, _q, body) => body },
255
- { method: 'GET', pattern: /^\/api\/v1\/synapses\/stats$/, ipcMethod: 'synapse.stats',
256
- extractParams: () => ({}) },
257
-
258
- // ─── Research ──────────────────────────────────────────
259
- { method: 'GET', pattern: /^\/api\/v1\/research\/insights$/, ipcMethod: 'research.insights',
260
- extractParams: (_m, q) => ({
261
- type: q.get('type') ?? undefined,
262
- limit: q.get('limit') ? Number(q.get('limit')) : 20,
263
- activeOnly: q.get('activeOnly') !== 'false',
264
- }) },
265
- { method: 'POST', pattern: /^\/api\/v1\/research\/insights\/(\d+)\/rate$/, ipcMethod: 'insight.rate',
266
- extractParams: (m, _q, body) => ({ id: Number(m[1]), ...(body as object) }) },
267
- { method: 'GET', pattern: /^\/api\/v1\/research\/suggest$/, ipcMethod: 'research.suggest',
268
- extractParams: (_m, q) => ({
269
- context: q.get('context') ?? '',
270
- limit: 10,
271
- activeOnly: true,
272
- }) },
273
- { method: 'GET', pattern: /^\/api\/v1\/research\/trends$/, ipcMethod: 'research.trends',
274
- extractParams: (_m, q) => ({
275
- projectId: q.get('projectId') ? Number(q.get('projectId')) : undefined,
276
- windowDays: q.get('windowDays') ? Number(q.get('windowDays')) : undefined,
277
- }) },
278
-
279
- // ─── Notifications ────────────────────────────────────
280
- { method: 'GET', pattern: /^\/api\/v1\/notifications$/, ipcMethod: 'notification.list',
281
- extractParams: (_m, q) => ({
282
- projectId: q.get('projectId') ? Number(q.get('projectId')) : undefined,
283
- }) },
284
- { method: 'POST', pattern: /^\/api\/v1\/notifications\/(\d+)\/ack$/, ipcMethod: 'notification.ack',
285
- extractParams: (m) => ({ id: Number(m[1]) }) },
286
-
287
- // ─── Analytics ─────────────────────────────────────────
288
- { method: 'GET', pattern: /^\/api\/v1\/analytics\/summary$/, ipcMethod: 'analytics.summary',
289
- extractParams: (_m, q) => ({
290
- projectId: q.get('projectId') ? Number(q.get('projectId')) : undefined,
291
- }) },
292
- { method: 'GET', pattern: /^\/api\/v1\/analytics\/network$/, ipcMethod: 'analytics.network',
293
- extractParams: (_m, q) => ({
294
- limit: q.get('limit') ? Number(q.get('limit')) : undefined,
295
- }) },
296
- { method: 'GET', pattern: /^\/api\/v1\/analytics\/health$/, ipcMethod: 'analytics.health',
297
- extractParams: (_m, q) => ({
298
- projectId: q.get('projectId') ? Number(q.get('projectId')) : undefined,
299
- }) },
300
- { method: 'GET', pattern: /^\/api\/v1\/analytics\/timeline$/, ipcMethod: 'analytics.timeline',
301
- extractParams: (_m, q) => ({
302
- projectId: q.get('projectId') ? Number(q.get('projectId')) : undefined,
303
- days: q.get('days') ? Number(q.get('days')) : undefined,
304
- }) },
305
- { method: 'GET', pattern: /^\/api\/v1\/analytics\/explain\/(\d+)$/, ipcMethod: 'analytics.explain',
306
- extractParams: (m) => ({ errorId: Number(m[1]) }) },
307
-
308
- // ─── Git ───────────────────────────────────────────────
309
- { method: 'GET', pattern: /^\/api\/v1\/git\/context$/, ipcMethod: 'git.context',
310
- extractParams: (_m, q) => ({ cwd: q.get('cwd') ?? undefined }) },
311
- { method: 'POST', pattern: /^\/api\/v1\/git\/link-error$/, ipcMethod: 'git.linkError',
312
- extractParams: (_m, _q, body) => body },
313
- { method: 'GET', pattern: /^\/api\/v1\/git\/errors\/(\d+)\/commits$/, ipcMethod: 'git.errorCommits',
314
- extractParams: (m) => ({ errorId: Number(m[1]) }) },
315
- { method: 'GET', pattern: /^\/api\/v1\/git\/commits\/([a-f0-9]+)\/errors$/, ipcMethod: 'git.commitErrors',
316
- extractParams: (m) => ({ commitHash: m[1] }) },
317
- { method: 'GET', pattern: /^\/api\/v1\/git\/diff$/, ipcMethod: 'git.diff',
318
- extractParams: (_m, q) => ({ cwd: q.get('cwd') ?? undefined }) },
319
-
320
- // ─── Terminal ──────────────────────────────────────────
321
- { method: 'POST', pattern: /^\/api\/v1\/terminal\/register$/, ipcMethod: 'terminal.register',
322
- extractParams: (_m, _q, body) => body },
323
- { method: 'POST', pattern: /^\/api\/v1\/terminal\/heartbeat$/, ipcMethod: 'terminal.heartbeat',
324
- extractParams: (_m, _q, body) => body },
325
- { method: 'POST', pattern: /^\/api\/v1\/terminal\/disconnect$/, ipcMethod: 'terminal.disconnect',
326
- extractParams: (_m, _q, body) => body },
327
-
328
- // ─── Learning ──────────────────────────────────────────
329
- { method: 'POST', pattern: /^\/api\/v1\/learning\/run$/, ipcMethod: 'learning.run',
330
- extractParams: () => ({}) },
331
- ];
332
- }
333
-
334
- private json(res: http.ServerResponse, status: number, data: unknown): void {
335
- res.writeHead(status, { 'Content-Type': 'application/json' });
336
- res.end(JSON.stringify(data));
337
- }
338
-
339
- private readBody(req: http.IncomingMessage): Promise<string> {
340
- return new Promise((resolve, reject) => {
341
- const chunks: Buffer[] = [];
342
- req.on('data', (chunk: Buffer) => chunks.push(chunk));
343
- req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
344
- req.on('error', reject);
345
- });
346
- }
347
-
348
- private setupSSE(): void {
349
- const bus = getEventBus();
350
- const eventNames = [
351
- 'error:reported', 'error:resolved', 'solution:applied',
352
- 'solution:created', 'module:registered', 'module:updated',
353
- 'synapse:created', 'synapse:strengthened',
354
- 'insight:created', 'rule:learned',
355
- ] as const;
356
-
357
- for (const eventName of eventNames) {
358
- bus.on(eventName, (data: unknown) => {
359
- this.broadcastSSE({ type: 'event', event: eventName, data });
360
- });
361
- }
362
-
363
- // Periodic stats broadcast every 30s
364
- this.statsTimer = setInterval(() => {
365
- if (this.sseClients.size > 0) {
366
- try {
367
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
368
- const summary = this.options.router.handle('analytics.summary', {}) as any;
369
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
370
- const network = this.options.router.handle('synapse.stats', {}) as any;
371
- const stats = {
372
- modules: summary?.modules?.total ?? 0,
373
- synapses: network?.totalSynapses ?? 0,
374
- errors: summary?.errors?.total ?? 0,
375
- solutions: summary?.solutions?.total ?? 0,
376
- rules: summary?.rules?.active ?? 0,
377
- insights: summary?.insights?.total ?? 0,
378
- };
379
- this.broadcastSSE({ type: 'stats_update', stats });
380
- } catch { /* ignore stats errors */ }
381
- }
382
- }, 30_000);
383
- }
384
-
385
- private broadcastSSE(data: unknown): void {
386
- const msg = `data: ${JSON.stringify(data)}\n\n`;
387
- for (const client of this.sseClients) {
388
- try {
389
- client.write(msg);
390
- } catch {
391
- this.sseClients.delete(client);
392
- }
393
- }
394
- }
395
- }