@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
@@ -1,74 +0,0 @@
1
- import type { LearningConfig } from '../types/config.types.js';
2
- import type { RuleRepository } from '../db/repositories/rule.repository.js';
3
- import type { ErrorPattern } from './pattern-extractor.js';
4
- import { getLogger } from '../utils/logger.js';
5
-
6
- export interface GeneratedRule {
7
- pattern: string;
8
- action: string;
9
- description: string;
10
- confidence: number;
11
- sourceErrorIds: number[];
12
- }
13
-
14
- /**
15
- * Generate prevention rules from extracted patterns.
16
- */
17
- export function generateRules(
18
- patterns: ErrorPattern[],
19
- config: LearningConfig,
20
- ): GeneratedRule[] {
21
- return patterns
22
- .filter(p =>
23
- p.occurrences >= config.minOccurrences &&
24
- p.confidence >= config.minConfidence,
25
- )
26
- .map(pattern => ({
27
- pattern: pattern.messageRegex,
28
- action: pattern.confidence >= 0.90
29
- ? `Auto-fix available for ${pattern.errorType}`
30
- : `Suggestion: check ${pattern.errorType} pattern (${pattern.occurrences} occurrences)`,
31
- description: `Auto-generated from ${pattern.occurrences} occurrences of ${pattern.errorType}`,
32
- confidence: pattern.confidence,
33
- sourceErrorIds: pattern.errorIds,
34
- }));
35
- }
36
-
37
- /**
38
- * Persist generated rules to the database.
39
- */
40
- export function persistRules(
41
- rules: GeneratedRule[],
42
- ruleRepo: RuleRepository,
43
- projectId?: number,
44
- ): number {
45
- const logger = getLogger();
46
- let created = 0;
47
-
48
- for (const rule of rules) {
49
- // Check if similar rule already exists
50
- const existing = ruleRepo.findByPattern(rule.pattern);
51
- if (existing.length > 0) {
52
- // Update confidence of existing rule
53
- const best = existing[0]!;
54
- if (rule.confidence > best.confidence) {
55
- ruleRepo.update(best.id, { confidence: rule.confidence });
56
- }
57
- continue;
58
- }
59
-
60
- ruleRepo.create({
61
- pattern: rule.pattern,
62
- action: rule.action,
63
- description: rule.description,
64
- confidence: rule.confidence,
65
- occurrences: 0,
66
- active: 1,
67
- project_id: projectId ?? null,
68
- });
69
- created++;
70
- logger.info(`New rule generated: ${rule.pattern.substring(0, 50)}...`);
71
- }
72
-
73
- return created;
74
- }
package/src/main.rs:10:5 DELETED
File without changes
@@ -1,166 +0,0 @@
1
- import type { ErrorRecord } from '../types/error.types.js';
2
- import { tokenize } from './tokenizer.js';
3
- import { cosineSimilarity, jaccardSimilarity, levenshteinDistance } from './similarity.js';
4
-
5
- export interface SignalScore {
6
- signal: string;
7
- score: number;
8
- weighted: number;
9
- }
10
-
11
- export interface MatchResult {
12
- errorId: number;
13
- score: number;
14
- signals: SignalScore[];
15
- isStrong: boolean;
16
- }
17
-
18
- interface MatchSignal {
19
- name: string;
20
- weight: number;
21
- compute: (a: ErrorRecord, b: ErrorRecord) => number;
22
- }
23
-
24
- // Base signals (used when vector search is NOT available)
25
- const SIGNALS_BASE: MatchSignal[] = [
26
- { name: 'fingerprint', weight: 0.20, compute: fingerprintMatch },
27
- { name: 'message_similarity', weight: 0.25, compute: messageSimilarity },
28
- { name: 'type_match', weight: 0.15, compute: typeMatch },
29
- { name: 'stack_similarity', weight: 0.15, compute: stackSimilarity },
30
- { name: 'file_similarity', weight: 0.12, compute: fileSimilarity },
31
- { name: 'context_similarity', weight: 0.13, compute: contextSimilarity },
32
- ];
33
-
34
- // Hybrid signals (used when vector search IS available — vector gets 20% weight)
35
- const SIGNALS_HYBRID: MatchSignal[] = [
36
- { name: 'fingerprint', weight: 0.25, compute: fingerprintMatch },
37
- { name: 'message_similarity', weight: 0.15, compute: messageSimilarity },
38
- { name: 'type_match', weight: 0.12, compute: typeMatch },
39
- { name: 'stack_similarity', weight: 0.12, compute: stackSimilarity },
40
- { name: 'file_similarity', weight: 0.08, compute: fileSimilarity },
41
- { name: 'context_similarity', weight: 0.08, compute: contextSimilarity },
42
- ];
43
-
44
- const VECTOR_WEIGHT = 0.20;
45
- const MATCH_THRESHOLD = 0.55;
46
- const STRONG_MATCH_THRESHOLD = 0.90;
47
-
48
- /**
49
- * Hybrid error matching: TF-IDF signals + optional vector similarity + synapse boost.
50
- *
51
- * @param incoming - The error to match
52
- * @param candidates - Candidate errors to compare against
53
- * @param vectorScores - Pre-computed vector similarity scores (errorId → score)
54
- * @param synapseScores - Pre-computed synapse proximity scores (errorId → score)
55
- */
56
- export function matchError(
57
- incoming: ErrorRecord,
58
- candidates: ErrorRecord[],
59
- vectorScores?: Map<number, number>,
60
- synapseScores?: Map<number, number>,
61
- ): MatchResult[] {
62
- const useHybrid = vectorScores && vectorScores.size > 0;
63
- const useSynapse = synapseScores && synapseScores.size > 0;
64
- const signals = useHybrid ? SIGNALS_HYBRID : SIGNALS_BASE;
65
-
66
- return candidates
67
- .map(candidate => {
68
- const signalResults = signals.map(signal => {
69
- const score = signal.compute(incoming, candidate);
70
- return {
71
- signal: signal.name,
72
- score,
73
- weighted: score * signal.weight,
74
- };
75
- });
76
-
77
- // Add vector similarity signal (if available)
78
- if (useHybrid) {
79
- const vectorScore = vectorScores.get(candidate.id) ?? 0;
80
- signalResults.push({
81
- signal: 'vector_similarity',
82
- score: vectorScore,
83
- weighted: vectorScore * VECTOR_WEIGHT,
84
- });
85
- }
86
-
87
- let totalScore = signalResults.reduce((sum, s) => sum + s.weighted, 0);
88
-
89
- // Synapse boost: if errors are already connected in the synapse network,
90
- // give up to 5% bonus (doesn't create false positives, only reinforces)
91
- if (useSynapse) {
92
- const synapseScore = synapseScores.get(candidate.id) ?? 0;
93
- if (synapseScore > 0) {
94
- const bonus = Math.min(synapseScore * 0.05, 0.05);
95
- totalScore = Math.min(1.0, totalScore + bonus);
96
- signalResults.push({
97
- signal: 'synapse_boost',
98
- score: synapseScore,
99
- weighted: bonus,
100
- });
101
- }
102
- }
103
-
104
- return {
105
- errorId: candidate.id,
106
- score: totalScore,
107
- signals: signalResults,
108
- isStrong: totalScore >= STRONG_MATCH_THRESHOLD,
109
- };
110
- })
111
- .filter(result => result.score >= MATCH_THRESHOLD)
112
- .sort((a, b) => b.score - a.score);
113
- }
114
-
115
- function fingerprintMatch(a: ErrorRecord, b: ErrorRecord): number {
116
- return a.fingerprint === b.fingerprint ? 1.0 : 0.0;
117
- }
118
-
119
- function messageSimilarity(a: ErrorRecord, b: ErrorRecord): number {
120
- const tokensA = tokenize(a.message);
121
- const tokensB = tokenize(b.message);
122
- return cosineSimilarity(tokensA, tokensB);
123
- }
124
-
125
- function typeMatch(a: ErrorRecord, b: ErrorRecord): number {
126
- return a.type === b.type ? 1.0 : 0.0;
127
- }
128
-
129
- function stackSimilarity(a: ErrorRecord, b: ErrorRecord): number {
130
- const rawA = a.raw_output ?? '';
131
- const rawB = b.raw_output ?? '';
132
-
133
- const frameRe = /at (?:(.+?) )?\(/g;
134
- const extractFuncs = (raw: string) => {
135
- const funcs: string[] = [];
136
- let m: RegExpExecArray | null;
137
- const re = new RegExp(frameRe.source, 'g');
138
- while ((m = re.exec(raw)) !== null) {
139
- if (m[1]) funcs.push(m[1]);
140
- }
141
- return funcs;
142
- };
143
-
144
- const funcsA = extractFuncs(rawA);
145
- const funcsB = extractFuncs(rawB);
146
-
147
- if (funcsA.length === 0 && funcsB.length === 0) return 0.5;
148
- return jaccardSimilarity(funcsA, funcsB);
149
- }
150
-
151
- function fileSimilarity(a: ErrorRecord, b: ErrorRecord): number {
152
- const pathA = a.file_path ?? '';
153
- const pathB = b.file_path ?? '';
154
- if (!pathA || !pathB) return 0.0;
155
- if (pathA === pathB) return 1.0;
156
- return levenshteinDistance(pathA, pathB);
157
- }
158
-
159
- function contextSimilarity(a: ErrorRecord, b: ErrorRecord): number {
160
- const ctxA = a.context ?? '';
161
- const ctxB = b.context ?? '';
162
- if (!ctxA || !ctxB) return 0.0;
163
- const tokensA = tokenize(ctxA);
164
- const tokensB = tokenize(ctxB);
165
- return cosineSimilarity(tokensA, tokensB);
166
- }
@@ -1,34 +0,0 @@
1
- import path from 'node:path';
2
- import { sha256 } from '../utils/hash.js';
3
- import type { StackFrame } from '../parsing/types.js';
4
-
5
- export function templateMessage(msg: string): string {
6
- return msg
7
- .replace(/[A-Z]:\\[\w\-.\\ ]+\.\w+/g, '<PATH>')
8
- .replace(/\/[\w\-./ ]+\.\w+/g, '<PATH>')
9
- .replace(/:(\d+):(\d+)/g, ':<LINE>:<COL>')
10
- .replace(/line \d+/gi, 'line <LINE>')
11
- .replace(/0x[0-9a-fA-F]+/g, '<ADDR>')
12
- .replace(/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/gi, '<UUID>')
13
- .replace(/https?:\/\/[^\s]+/g, '<URL>')
14
- .replace(/\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}/g, '<TIMESTAMP>')
15
- // Normalize JS/TS property access patterns so "reading 'map'" ≈ "reading 'forEach'"
16
- .replace(/\(reading ['"][^'"]*['"]\)/g, "(reading '<PROP>')")
17
- .replace(/\(writing ['"][^'"]*['"]\)/g, "(writing '<PROP>')")
18
- // Normalize quoted identifiers (e.g., 'someVar', "someFunc")
19
- .replace(/['"][a-zA-Z_$][\w$]*['"]/g, "'<IDENT>'");
20
- }
21
-
22
- export function generateFingerprint(
23
- errorType: string,
24
- message: string,
25
- frames: StackFrame[],
26
- ): string {
27
- const template = templateMessage(message);
28
- const topFrames = frames
29
- .slice(0, 3)
30
- .map(f => `${f.function_name || '<anon>'}@${path.basename(f.file_path || '<unknown>')}`)
31
- .join('|');
32
- const input = `${errorType}::${template}::${topFrames}`;
33
- return sha256(input);
34
- }
@@ -1,61 +0,0 @@
1
- export function levenshteinDistance(a: string, b: string): number {
2
- if (a === b) return 1.0;
3
- if (a.length === 0 || b.length === 0) return 0.0;
4
-
5
- const dp: number[][] = Array(b.length + 1)
6
- .fill(0)
7
- .map(() => Array(a.length + 1).fill(0) as number[]);
8
-
9
- for (let i = 0; i <= a.length; i++) dp[0]![i] = i;
10
- for (let j = 0; j <= b.length; j++) dp[j]![0] = j;
11
-
12
- for (let i = 1; i <= b.length; i++) {
13
- for (let j = 1; j <= a.length; j++) {
14
- const cost = a[j - 1] === b[i - 1] ? 0 : 1;
15
- dp[i]![j] = Math.min(
16
- dp[i - 1]![j]! + 1,
17
- dp[i]![j - 1]! + 1,
18
- dp[i - 1]![j - 1]! + cost,
19
- );
20
- }
21
- }
22
-
23
- return 1 - dp[b.length]![a.length]! / Math.max(a.length, b.length);
24
- }
25
-
26
- export function cosineSimilarity(tokensA: string[], tokensB: string[]): number {
27
- if (tokensA.length === 0 || tokensB.length === 0) return 0.0;
28
-
29
- const vocab = new Set([...tokensA, ...tokensB]);
30
- const vecA = new Map<string, number>();
31
- const vecB = new Map<string, number>();
32
-
33
- for (const t of tokensA) vecA.set(t, (vecA.get(t) ?? 0) + 1);
34
- for (const t of tokensB) vecB.set(t, (vecB.get(t) ?? 0) + 1);
35
-
36
- let dot = 0;
37
- let magA = 0;
38
- let magB = 0;
39
-
40
- for (const word of vocab) {
41
- const a = vecA.get(word) ?? 0;
42
- const b = vecB.get(word) ?? 0;
43
- dot += a * b;
44
- magA += a * a;
45
- magB += b * b;
46
- }
47
-
48
- const denom = Math.sqrt(magA) * Math.sqrt(magB);
49
- return denom === 0 ? 0 : dot / denom;
50
- }
51
-
52
- export function jaccardSimilarity(tokensA: string[], tokensB: string[]): number {
53
- if (tokensA.length === 0 && tokensB.length === 0) return 0.0;
54
-
55
- const setA = new Set(tokensA);
56
- const setB = new Set(tokensB);
57
- const intersection = new Set([...setA].filter(x => setB.has(x)));
58
- const union = new Set([...setA, ...setB]);
59
-
60
- return union.size === 0 ? 0 : intersection.size / union.size;
61
- }
@@ -1,74 +0,0 @@
1
- export class TfIdfIndex {
2
- private documents = new Map<number, string[]>();
3
- private df = new Map<string, number>();
4
- private idf = new Map<string, number>();
5
- private documentCount = 0;
6
-
7
- addDocument(id: number, tokens: string[]): void {
8
- if (this.documents.has(id)) {
9
- this.removeDocument(id);
10
- }
11
- const unique = new Set(tokens);
12
- for (const token of unique) {
13
- this.df.set(token, (this.df.get(token) ?? 0) + 1);
14
- }
15
- this.documents.set(id, tokens);
16
- this.documentCount++;
17
- this.recomputeIdfForTerms(unique);
18
- }
19
-
20
- removeDocument(id: number): void {
21
- const tokens = this.documents.get(id);
22
- if (!tokens) return;
23
-
24
- const unique = new Set(tokens);
25
- for (const token of unique) {
26
- const count = this.df.get(token) ?? 0;
27
- if (count <= 1) {
28
- this.df.delete(token);
29
- this.idf.delete(token);
30
- } else {
31
- this.df.set(token, count - 1);
32
- }
33
- }
34
- this.documents.delete(id);
35
- this.documentCount--;
36
- }
37
-
38
- query(tokens: string[], topK: number = 10): Array<{ id: number; score: number }> {
39
- const scores = new Map<number, number>();
40
-
41
- for (const token of tokens) {
42
- const idfVal = this.idf.get(token) ?? 0;
43
- if (idfVal === 0) continue;
44
-
45
- for (const [docId, docTokens] of this.documents) {
46
- const tf = docTokens.filter(t => t === token).length / docTokens.length;
47
- const score = (scores.get(docId) ?? 0) + tf * idfVal;
48
- scores.set(docId, score);
49
- }
50
- }
51
-
52
- return Array.from(scores.entries())
53
- .map(([id, score]) => ({ id, score }))
54
- .sort((a, b) => b.score - a.score)
55
- .slice(0, topK);
56
- }
57
-
58
- getDocumentCount(): number {
59
- return this.documentCount;
60
- }
61
-
62
- getIdf(): ReadonlyMap<string, number> {
63
- return this.idf;
64
- }
65
-
66
- private recomputeIdfForTerms(terms: Set<string>): void {
67
- for (const term of terms) {
68
- const dfVal = this.df.get(term) ?? 0;
69
- if (dfVal > 0 && this.documentCount > 0) {
70
- this.idf.set(term, Math.log(this.documentCount / dfVal));
71
- }
72
- }
73
- }
74
- }
@@ -1,41 +0,0 @@
1
- const STOPWORDS = new Set([
2
- 'the', 'is', 'are', 'a', 'an', 'and', 'or', 'not', 'in', 'at', 'by', 'for',
3
- 'from', 'of', 'on', 'to', 'with', 'as', 'error', 'exception', 'throw', 'catch',
4
- 'was', 'be', 'been', 'being', 'have', 'has', 'had', 'do', 'does', 'did',
5
- 'will', 'would', 'could', 'should', 'may', 'might', 'can', 'it', 'its',
6
- 'this', 'that', 'these', 'those', 'i', 'we', 'you', 'he', 'she', 'they',
7
- ]);
8
-
9
- export function splitCamelCase(text: string): string[] {
10
- return text
11
- .replace(/([A-Z]+)([A-Z][a-z])/g, '$1 $2')
12
- .replace(/([a-z\d])([A-Z])/g, '$1 $2')
13
- .split(/\s+/)
14
- .filter(t => t.length > 0);
15
- }
16
-
17
- export function splitSnakeCase(text: string): string[] {
18
- return text.split(/[_\-]+/).filter(t => t.length > 0);
19
- }
20
-
21
- export function removeStopwords(tokens: string[]): string[] {
22
- return tokens.filter(t => !STOPWORDS.has(t.toLowerCase()));
23
- }
24
-
25
- export function tokenize(text: string): string[] {
26
- const words = text
27
- .replace(/[^\w\s]/g, ' ')
28
- .split(/\s+/)
29
- .filter(t => t.length > 0);
30
-
31
- const tokens: string[] = [];
32
- for (const word of words) {
33
- tokens.push(...splitCamelCase(word));
34
- if (word.includes('_') || word.includes('-')) {
35
- tokens.push(...splitSnakeCase(word));
36
- }
37
- }
38
-
39
- const cleaned = removeStopwords(tokens);
40
- return [...new Set(cleaned.map(t => t.toLowerCase()))];
41
- }
@@ -1,93 +0,0 @@
1
- import { IpcClient } from '../ipc/client.js';
2
- import { getPipeName } from '../utils/paths.js';
3
-
4
- interface HookInput {
5
- tool_name: string;
6
- tool_input: { command?: string; file_path?: string };
7
- tool_output: string;
8
- exit_code?: number;
9
- }
10
-
11
- const ERROR_PATTERNS = [
12
- /Error:/i,
13
- /error\[E\d+\]/, // Rust errors
14
- /Traceback \(most recent call last\)/, // Python
15
- /FATAL|PANIC/i,
16
- /npm ERR!/,
17
- /SyntaxError|TypeError|ReferenceError|RangeError/,
18
- /ENOENT|EACCES|ECONNREFUSED|ETIMEDOUT/,
19
- /ModuleNotFoundError|ImportError/,
20
- /failed to compile/i,
21
- /BUILD FAILED/i,
22
- /Cannot find module/,
23
- /command not found/,
24
- /Permission denied/,
25
- ];
26
-
27
- function isError(input: HookInput): boolean {
28
- if (input.exit_code !== undefined && input.exit_code !== 0) return true;
29
- return ERROR_PATTERNS.some(p => p.test(input.tool_output));
30
- }
31
-
32
- function readStdin(): Promise<string> {
33
- return new Promise((resolve) => {
34
- let data = '';
35
- process.stdin.setEncoding('utf8');
36
- process.stdin.on('data', (chunk) => { data += chunk; });
37
- process.stdin.on('end', () => resolve(data));
38
- });
39
- }
40
-
41
- async function main(): Promise<void> {
42
- const raw = await readStdin();
43
- if (!raw.trim()) return;
44
-
45
- let input: HookInput;
46
- try {
47
- input = JSON.parse(raw);
48
- } catch {
49
- return;
50
- }
51
-
52
- if (!isError(input)) return;
53
-
54
- const client = new IpcClient(getPipeName(), 3000);
55
- try {
56
- await client.connect();
57
-
58
- // Report error to Brain
59
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
60
- const result: any = await client.request('error.report', {
61
- project: 'auto-detected',
62
- errorOutput: input.tool_output,
63
- command: input.tool_input.command,
64
- autoDetected: true,
65
- });
66
-
67
- // If Brain knows a solution, output as feedback
68
- if (result.matches?.length > 0) {
69
- const best = result.matches[0];
70
- process.stderr.write(`Brain: Similar error found (#${best.errorId}, ${Math.round(best.score * 100)}% match)\n`);
71
- if (best.solutions?.length > 0) {
72
- process.stderr.write(`Brain: Solution available — use brain_query_error to see details\n`);
73
- }
74
- }
75
-
76
- // Anti-pattern check
77
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
- const antipatterns: any = await client.request('prevention.antipatterns', {
79
- errorType: '',
80
- message: input.tool_output,
81
- });
82
- if (antipatterns?.length > 0 && antipatterns[0].matched) {
83
- process.stderr.write(`Brain WARNING: Known anti-pattern detected: ${antipatterns[0].description}\n`);
84
- }
85
-
86
- } catch {
87
- // Hook must never block the workflow — silent failure
88
- } finally {
89
- client.disconnect();
90
- }
91
- }
92
-
93
- main();
@@ -1,140 +0,0 @@
1
- import http from 'node:http';
2
- import { randomUUID } from 'node:crypto';
3
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
4
- import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js';
5
- import { getLogger } from '../utils/logger.js';
6
- import type { IpcRouter } from '../ipc/router.js';
7
- import { registerToolsDirect } from './tools.js';
8
-
9
- export class McpHttpServer {
10
- private server: http.Server | null = null;
11
- private transports = new Map<string, SSEServerTransport>();
12
- private logger = getLogger();
13
-
14
- constructor(
15
- private port: number,
16
- private router: IpcRouter,
17
- ) {}
18
-
19
- start(): void {
20
- this.server = http.createServer((req, res) => {
21
- res.setHeader('Access-Control-Allow-Origin', '*');
22
- res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
23
- res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
24
-
25
- if (req.method === 'OPTIONS') {
26
- res.writeHead(204);
27
- res.end();
28
- return;
29
- }
30
-
31
- const url = new URL(req.url ?? '/', `http://localhost:${this.port}`);
32
-
33
- if (url.pathname === '/sse' && req.method === 'GET') {
34
- this.handleSSE(res);
35
- return;
36
- }
37
-
38
- if (url.pathname === '/messages' && req.method === 'POST') {
39
- this.handleMessage(req, res, url);
40
- return;
41
- }
42
-
43
- if (url.pathname === '/' && req.method === 'GET') {
44
- res.writeHead(200, { 'Content-Type': 'application/json' });
45
- res.end(JSON.stringify({
46
- name: 'brain',
47
- version: '1.8.0',
48
- protocol: 'MCP',
49
- transport: 'sse',
50
- endpoints: {
51
- sse: '/sse',
52
- messages: '/messages',
53
- },
54
- clients: this.transports.size,
55
- }));
56
- return;
57
- }
58
-
59
- res.writeHead(404, { 'Content-Type': 'text/plain' });
60
- res.end('Not Found');
61
- });
62
-
63
- this.server.listen(this.port, () => {
64
- this.logger.info(`MCP HTTP server (SSE) started on http://localhost:${this.port}`);
65
- });
66
- }
67
-
68
- stop(): void {
69
- for (const transport of this.transports.values()) {
70
- try { transport.close?.(); } catch { /* ignore */ }
71
- }
72
- this.transports.clear();
73
- this.server?.close();
74
- this.server = null;
75
- this.logger.info('MCP HTTP server stopped');
76
- }
77
-
78
- getClientCount(): number {
79
- return this.transports.size;
80
- }
81
-
82
- private async handleSSE(res: http.ServerResponse): Promise<void> {
83
- try {
84
- const transport = new SSEServerTransport('/messages', res);
85
- const sessionId = transport.sessionId ?? randomUUID();
86
- this.transports.set(sessionId, transport);
87
-
88
- const server = new McpServer({
89
- name: 'brain',
90
- version: '1.8.0',
91
- });
92
-
93
- registerToolsDirect(server, this.router);
94
-
95
- res.on('close', () => {
96
- this.transports.delete(sessionId);
97
- this.logger.debug(`MCP SSE client disconnected: ${sessionId}`);
98
- });
99
-
100
- await server.connect(transport);
101
- this.logger.info(`MCP SSE client connected: ${sessionId}`);
102
- } catch (err) {
103
- this.logger.error('MCP SSE connection error:', err);
104
- if (!res.headersSent) {
105
- res.writeHead(500, { 'Content-Type': 'text/plain' });
106
- res.end('Internal Server Error');
107
- }
108
- }
109
- }
110
-
111
- private async handleMessage(
112
- req: http.IncomingMessage,
113
- res: http.ServerResponse,
114
- url: URL,
115
- ): Promise<void> {
116
- try {
117
- const sessionId = url.searchParams.get('sessionId');
118
- if (!sessionId) {
119
- res.writeHead(400, { 'Content-Type': 'text/plain' });
120
- res.end('Missing sessionId parameter');
121
- return;
122
- }
123
-
124
- const transport = this.transports.get(sessionId);
125
- if (!transport) {
126
- res.writeHead(404, { 'Content-Type': 'text/plain' });
127
- res.end('Session not found. Connect to /sse first.');
128
- return;
129
- }
130
-
131
- await transport.handlePostMessage(req, res);
132
- } catch (err) {
133
- this.logger.error('MCP message error:', err);
134
- if (!res.headersSent) {
135
- res.writeHead(500, { 'Content-Type': 'text/plain' });
136
- res.end('Internal Server Error');
137
- }
138
- }
139
- }
140
- }