@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.
- package/README.md +19 -0
- package/brain.log +1164 -0
- package/{src/cli/commands/dashboard.ts → dashboard.html} +688 -807
- package/dist/api/server.d.ts +4 -18
- package/dist/api/server.js +4 -173
- package/dist/api/server.js.map +1 -1
- package/dist/brain.d.ts +1 -0
- package/dist/brain.js +6 -1
- package/dist/brain.js.map +1 -1
- package/dist/cli/colors.d.ts +4 -25
- package/dist/cli/colors.js +3 -89
- package/dist/cli/colors.js.map +1 -1
- package/dist/cli/commands/peers.d.ts +2 -0
- package/dist/cli/commands/peers.js +38 -0
- package/dist/cli/commands/peers.js.map +1 -0
- package/dist/db/connection.d.ts +1 -2
- package/dist/db/connection.js +1 -18
- package/dist/db/connection.js.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/ipc/__tests__/protocol.test.d.ts +1 -0
- package/dist/ipc/__tests__/protocol.test.js +117 -0
- package/dist/ipc/__tests__/protocol.test.js.map +1 -0
- package/dist/ipc/client.d.ts +1 -16
- package/dist/ipc/client.js +1 -100
- package/dist/ipc/client.js.map +1 -1
- package/dist/ipc/protocol.d.ts +1 -8
- package/dist/ipc/protocol.js +1 -28
- package/dist/ipc/protocol.js.map +1 -1
- package/dist/ipc/router.js +8 -0
- package/dist/ipc/router.js.map +1 -1
- package/dist/ipc/server.d.ts +1 -22
- package/dist/ipc/server.js +1 -163
- package/dist/ipc/server.js.map +1 -1
- package/dist/mcp/http-server.d.ts +1 -7
- package/dist/mcp/http-server.js +6 -117
- package/dist/mcp/http-server.js.map +1 -1
- package/dist/mcp/server.js +5 -61
- package/dist/mcp/server.js.map +1 -1
- package/dist/signals/__tests__/fingerprint.test.d.ts +1 -0
- package/dist/signals/__tests__/fingerprint.test.js +118 -0
- package/dist/signals/__tests__/fingerprint.test.js.map +1 -0
- package/dist/types/ipc.types.d.ts +1 -11
- package/dist/utils/__tests__/hash.test.d.ts +1 -0
- package/dist/utils/__tests__/hash.test.js +32 -0
- package/dist/utils/__tests__/hash.test.js.map +1 -0
- package/dist/utils/__tests__/paths.test.d.ts +1 -0
- package/dist/utils/__tests__/paths.test.js +75 -0
- package/dist/utils/__tests__/paths.test.js.map +1 -0
- package/dist/utils/events.d.ts +4 -8
- package/dist/utils/events.js +2 -14
- package/dist/utils/events.js.map +1 -1
- package/dist/utils/hash.d.ts +1 -1
- package/dist/utils/hash.js +1 -4
- package/dist/utils/hash.js.map +1 -1
- package/dist/utils/logger.d.ts +3 -2
- package/dist/utils/logger.js +8 -35
- package/dist/utils/logger.js.map +1 -1
- package/dist/utils/paths.d.ts +2 -1
- package/dist/utils/paths.js +4 -13
- package/dist/utils/paths.js.map +1 -1
- package/package.json +2 -1
- package/BRAIN_PLAN.md +0 -3324
- package/reddit_post.md +0 -45
- package/src/api/server.ts +0 -395
- package/src/brain.ts +0 -313
- package/src/cli/colors.ts +0 -116
- package/src/cli/commands/config.ts +0 -169
- package/src/cli/commands/doctor.ts +0 -124
- package/src/cli/commands/explain.ts +0 -83
- package/src/cli/commands/export.ts +0 -31
- package/src/cli/commands/import.ts +0 -199
- package/src/cli/commands/insights.ts +0 -65
- package/src/cli/commands/learn.ts +0 -24
- package/src/cli/commands/modules.ts +0 -53
- package/src/cli/commands/network.ts +0 -67
- package/src/cli/commands/projects.ts +0 -42
- package/src/cli/commands/query.ts +0 -120
- package/src/cli/commands/start.ts +0 -105
- package/src/cli/commands/status.ts +0 -75
- package/src/cli/commands/stop.ts +0 -34
- package/src/cli/ipc-helper.ts +0 -22
- package/src/cli/update-check.ts +0 -63
- package/src/code/analyzer.ts +0 -117
- package/src/code/fingerprint.ts +0 -87
- package/src/code/matcher.ts +0 -129
- package/src/code/parsers/generic.ts +0 -29
- package/src/code/parsers/python.ts +0 -54
- package/src/code/parsers/typescript.ts +0 -65
- package/src/code/registry.ts +0 -60
- package/src/code/scorer.ts +0 -120
- package/src/config.ts +0 -135
- package/src/dashboard/server.ts +0 -142
- package/src/db/connection.ts +0 -22
- package/src/db/migrations/001_core_schema.ts +0 -120
- package/src/db/migrations/002_learning_schema.ts +0 -38
- package/src/db/migrations/003_code_schema.ts +0 -53
- package/src/db/migrations/004_synapses_schema.ts +0 -57
- package/src/db/migrations/005_fts_indexes.ts +0 -78
- package/src/db/migrations/006_synapses_phase3.ts +0 -17
- package/src/db/migrations/007_feedback.ts +0 -13
- package/src/db/migrations/008_git_integration.ts +0 -38
- package/src/db/migrations/009_embeddings.ts +0 -8
- package/src/db/migrations/index.ts +0 -70
- package/src/db/repositories/antipattern.repository.ts +0 -66
- package/src/db/repositories/code-module.repository.ts +0 -142
- package/src/db/repositories/error.repository.ts +0 -189
- package/src/db/repositories/insight.repository.ts +0 -99
- package/src/db/repositories/notification.repository.ts +0 -66
- package/src/db/repositories/project.repository.ts +0 -93
- package/src/db/repositories/rule.repository.ts +0 -108
- package/src/db/repositories/solution.repository.ts +0 -154
- package/src/db/repositories/synapse.repository.ts +0 -163
- package/src/db/repositories/terminal.repository.ts +0 -101
- package/src/embeddings/engine.ts +0 -238
- package/src/hooks/post-tool-use.ts +0 -92
- package/src/hooks/post-write.ts +0 -129
- package/src/index.ts +0 -63
- package/src/ipc/client.ts +0 -118
- package/src/ipc/protocol.ts +0 -35
- package/src/ipc/router.ts +0 -133
- package/src/ipc/server.ts +0 -176
- package/src/learning/confidence-scorer.ts +0 -80
- package/src/learning/decay.ts +0 -46
- package/src/learning/learning-engine.ts +0 -170
- package/src/learning/pattern-extractor.ts +0 -90
- package/src/learning/rule-generator.ts +0 -74
- package/src/main.rs:10:5 +0 -0
- package/src/matching/error-matcher.ts +0 -166
- package/src/matching/fingerprint.ts +0 -34
- package/src/matching/similarity.ts +0 -61
- package/src/matching/tfidf.ts +0 -74
- package/src/matching/tokenizer.ts +0 -41
- package/src/mcp/auto-detect.ts +0 -93
- package/src/mcp/http-server.ts +0 -140
- package/src/mcp/server.ts +0 -73
- package/src/mcp/tools.ts +0 -328
- package/src/parsing/error-parser.ts +0 -28
- package/src/parsing/parsers/compiler.ts +0 -93
- package/src/parsing/parsers/generic.ts +0 -28
- package/src/parsing/parsers/go.ts +0 -97
- package/src/parsing/parsers/node.ts +0 -69
- package/src/parsing/parsers/python.ts +0 -62
- package/src/parsing/parsers/rust.ts +0 -50
- package/src/parsing/parsers/shell.ts +0 -42
- package/src/parsing/types.ts +0 -47
- package/src/research/gap-analyzer.ts +0 -135
- package/src/research/insight-generator.ts +0 -123
- package/src/research/research-engine.ts +0 -116
- package/src/research/synergy-detector.ts +0 -126
- package/src/research/template-extractor.ts +0 -130
- package/src/research/trend-analyzer.ts +0 -127
- package/src/services/analytics.service.ts +0 -226
- package/src/services/code.service.ts +0 -271
- package/src/services/error.service.ts +0 -266
- package/src/services/git.service.ts +0 -132
- package/src/services/notification.service.ts +0 -41
- package/src/services/prevention.service.ts +0 -159
- package/src/services/research.service.ts +0 -98
- package/src/services/solution.service.ts +0 -174
- package/src/services/synapse.service.ts +0 -59
- package/src/services/terminal.service.ts +0 -81
- package/src/synapses/activation.ts +0 -80
- package/src/synapses/decay.ts +0 -38
- package/src/synapses/hebbian.ts +0 -69
- package/src/synapses/pathfinder.ts +0 -81
- package/src/synapses/synapse-manager.ts +0 -113
- package/src/types/code.types.ts +0 -52
- package/src/types/config.types.ts +0 -103
- package/src/types/error.types.ts +0 -67
- package/src/types/ipc.types.ts +0 -8
- package/src/types/mcp.types.ts +0 -53
- package/src/types/research.types.ts +0 -28
- package/src/types/solution.types.ts +0 -30
- package/src/types/synapse.types.ts +0 -50
- package/src/utils/events.ts +0 -45
- package/src/utils/hash.ts +0 -5
- package/src/utils/logger.ts +0 -48
- package/src/utils/paths.ts +0 -19
- package/tests/e2e/test_code_intelligence.py +0 -1015
- package/tests/e2e/test_error_memory.py +0 -451
- package/tests/e2e/test_full_integration.py +0 -534
- package/tests/fixtures/code-modules/modules.ts +0 -83
- package/tests/fixtures/errors/go.ts +0 -9
- package/tests/fixtures/errors/node.ts +0 -24
- package/tests/fixtures/errors/python.ts +0 -21
- package/tests/fixtures/errors/rust.ts +0 -25
- package/tests/fixtures/errors/shell.ts +0 -15
- package/tests/fixtures/solutions/solutions.ts +0 -27
- package/tests/helpers/setup-db.ts +0 -52
- package/tests/integration/code-flow.test.ts +0 -86
- package/tests/integration/error-flow.test.ts +0 -83
- package/tests/integration/ipc-flow.test.ts +0 -166
- package/tests/integration/learning-cycle.test.ts +0 -82
- package/tests/integration/synapse-flow.test.ts +0 -117
- package/tests/unit/code/analyzer.test.ts +0 -58
- package/tests/unit/code/fingerprint.test.ts +0 -51
- package/tests/unit/code/scorer.test.ts +0 -55
- package/tests/unit/learning/confidence-scorer.test.ts +0 -60
- package/tests/unit/learning/decay.test.ts +0 -45
- package/tests/unit/learning/pattern-extractor.test.ts +0 -50
- package/tests/unit/matching/error-matcher.test.ts +0 -69
- package/tests/unit/matching/fingerprint.test.ts +0 -47
- package/tests/unit/matching/similarity.test.ts +0 -65
- package/tests/unit/matching/tfidf.test.ts +0 -71
- package/tests/unit/matching/tokenizer.test.ts +0 -83
- package/tests/unit/parsing/parsers.test.ts +0 -113
- package/tests/unit/research/gap-analyzer.test.ts +0 -45
- package/tests/unit/research/trend-analyzer.test.ts +0 -45
- package/tests/unit/synapses/activation.test.ts +0 -80
- package/tests/unit/synapses/decay.test.ts +0 -27
- package/tests/unit/synapses/hebbian.test.ts +0 -96
- package/tests/unit/synapses/pathfinder.test.ts +0 -72
- 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
|
-
}
|