@timmeck/brain 1.9.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (253) hide show
  1. package/README.md +33 -0
  2. package/brain.log +3876 -0
  3. package/{src/cli/commands/dashboard.ts → dashboard.html} +694 -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 +2 -0
  8. package/dist/brain.js +15 -4
  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/dashboard.js +21 -2
  14. package/dist/cli/commands/dashboard.js.map +1 -1
  15. package/dist/cli/commands/peers.d.ts +2 -0
  16. package/dist/cli/commands/peers.js +38 -0
  17. package/dist/cli/commands/peers.js.map +1 -0
  18. package/dist/cli/commands/status.js +0 -1
  19. package/dist/cli/commands/status.js.map +1 -1
  20. package/dist/config.js +2 -29
  21. package/dist/config.js.map +1 -1
  22. package/dist/db/connection.d.ts +1 -2
  23. package/dist/db/connection.js +1 -18
  24. package/dist/db/connection.js.map +1 -1
  25. package/dist/index.js +3 -1
  26. package/dist/index.js.map +1 -1
  27. package/dist/ipc/__tests__/protocol.test.d.ts +1 -0
  28. package/dist/ipc/__tests__/protocol.test.js +117 -0
  29. package/dist/ipc/__tests__/protocol.test.js.map +1 -0
  30. package/dist/ipc/client.d.ts +1 -16
  31. package/dist/ipc/client.js +1 -100
  32. package/dist/ipc/client.js.map +1 -1
  33. package/dist/ipc/protocol.d.ts +1 -8
  34. package/dist/ipc/protocol.js +1 -28
  35. package/dist/ipc/protocol.js.map +1 -1
  36. package/dist/ipc/router.d.ts +2 -0
  37. package/dist/ipc/router.js +30 -0
  38. package/dist/ipc/router.js.map +1 -1
  39. package/dist/ipc/server.d.ts +1 -22
  40. package/dist/ipc/server.js +1 -163
  41. package/dist/ipc/server.js.map +1 -1
  42. package/dist/learning/confidence-scorer.d.ts +2 -5
  43. package/dist/learning/confidence-scorer.js +4 -19
  44. package/dist/learning/confidence-scorer.js.map +1 -1
  45. package/dist/learning/decay.js +2 -3
  46. package/dist/learning/decay.js.map +1 -1
  47. package/dist/learning/learning-engine.d.ts +2 -5
  48. package/dist/learning/learning-engine.js +3 -15
  49. package/dist/learning/learning-engine.js.map +1 -1
  50. package/dist/mcp/http-server.d.ts +1 -7
  51. package/dist/mcp/http-server.js +6 -117
  52. package/dist/mcp/http-server.js.map +1 -1
  53. package/dist/mcp/server.js +5 -61
  54. package/dist/mcp/server.js.map +1 -1
  55. package/dist/mcp/tools.js +36 -0
  56. package/dist/mcp/tools.js.map +1 -1
  57. package/dist/parsing/parsers/compiler.js +1 -1
  58. package/dist/parsing/parsers/compiler.js.map +1 -1
  59. package/dist/research/research-engine.d.ts +2 -6
  60. package/dist/research/research-engine.js +3 -23
  61. package/dist/research/research-engine.js.map +1 -1
  62. package/dist/services/synapse.service.d.ts +3 -3
  63. package/dist/signals/__tests__/fingerprint.test.d.ts +1 -0
  64. package/dist/signals/__tests__/fingerprint.test.js +118 -0
  65. package/dist/signals/__tests__/fingerprint.test.js.map +1 -0
  66. package/dist/synapses/activation.d.ts +3 -13
  67. package/dist/synapses/activation.js +2 -49
  68. package/dist/synapses/activation.js.map +1 -1
  69. package/dist/synapses/decay.d.ts +2 -11
  70. package/dist/synapses/decay.js +2 -26
  71. package/dist/synapses/decay.js.map +1 -1
  72. package/dist/synapses/hebbian.d.ts +2 -13
  73. package/dist/synapses/hebbian.js +2 -35
  74. package/dist/synapses/hebbian.js.map +1 -1
  75. package/dist/synapses/pathfinder.d.ts +2 -14
  76. package/dist/synapses/pathfinder.js +2 -49
  77. package/dist/synapses/pathfinder.js.map +1 -1
  78. package/dist/synapses/synapse-manager.d.ts +7 -23
  79. package/dist/synapses/synapse-manager.js +6 -63
  80. package/dist/synapses/synapse-manager.js.map +1 -1
  81. package/dist/types/ipc.types.d.ts +1 -11
  82. package/dist/utils/__tests__/hash.test.d.ts +1 -0
  83. package/dist/utils/__tests__/hash.test.js +32 -0
  84. package/dist/utils/__tests__/hash.test.js.map +1 -0
  85. package/dist/utils/__tests__/paths.test.d.ts +1 -0
  86. package/dist/utils/__tests__/paths.test.js +75 -0
  87. package/dist/utils/__tests__/paths.test.js.map +1 -0
  88. package/dist/utils/events.d.ts +4 -8
  89. package/dist/utils/events.js +2 -14
  90. package/dist/utils/events.js.map +1 -1
  91. package/dist/utils/hash.d.ts +1 -1
  92. package/dist/utils/hash.js +1 -4
  93. package/dist/utils/hash.js.map +1 -1
  94. package/dist/utils/logger.d.ts +3 -2
  95. package/dist/utils/logger.js +8 -35
  96. package/dist/utils/logger.js.map +1 -1
  97. package/dist/utils/paths.d.ts +2 -1
  98. package/dist/utils/paths.js +4 -13
  99. package/dist/utils/paths.js.map +1 -1
  100. package/eslint.config.js +14 -0
  101. package/package.json +56 -49
  102. package/BRAIN_PLAN.md +0 -3324
  103. package/reddit_post.md +0 -45
  104. package/src/api/server.ts +0 -395
  105. package/src/brain.ts +0 -313
  106. package/src/cli/colors.ts +0 -116
  107. package/src/cli/commands/config.ts +0 -169
  108. package/src/cli/commands/doctor.ts +0 -124
  109. package/src/cli/commands/explain.ts +0 -83
  110. package/src/cli/commands/export.ts +0 -31
  111. package/src/cli/commands/import.ts +0 -199
  112. package/src/cli/commands/insights.ts +0 -65
  113. package/src/cli/commands/learn.ts +0 -24
  114. package/src/cli/commands/modules.ts +0 -53
  115. package/src/cli/commands/network.ts +0 -67
  116. package/src/cli/commands/projects.ts +0 -42
  117. package/src/cli/commands/query.ts +0 -120
  118. package/src/cli/commands/start.ts +0 -105
  119. package/src/cli/commands/status.ts +0 -75
  120. package/src/cli/commands/stop.ts +0 -34
  121. package/src/cli/ipc-helper.ts +0 -22
  122. package/src/cli/update-check.ts +0 -63
  123. package/src/code/analyzer.ts +0 -117
  124. package/src/code/fingerprint.ts +0 -87
  125. package/src/code/matcher.ts +0 -129
  126. package/src/code/parsers/generic.ts +0 -29
  127. package/src/code/parsers/python.ts +0 -54
  128. package/src/code/parsers/typescript.ts +0 -65
  129. package/src/code/registry.ts +0 -60
  130. package/src/code/scorer.ts +0 -120
  131. package/src/config.ts +0 -135
  132. package/src/dashboard/server.ts +0 -142
  133. package/src/db/connection.ts +0 -22
  134. package/src/db/migrations/001_core_schema.ts +0 -120
  135. package/src/db/migrations/002_learning_schema.ts +0 -38
  136. package/src/db/migrations/003_code_schema.ts +0 -53
  137. package/src/db/migrations/004_synapses_schema.ts +0 -57
  138. package/src/db/migrations/005_fts_indexes.ts +0 -78
  139. package/src/db/migrations/006_synapses_phase3.ts +0 -17
  140. package/src/db/migrations/007_feedback.ts +0 -13
  141. package/src/db/migrations/008_git_integration.ts +0 -38
  142. package/src/db/migrations/009_embeddings.ts +0 -8
  143. package/src/db/migrations/index.ts +0 -70
  144. package/src/db/repositories/antipattern.repository.ts +0 -66
  145. package/src/db/repositories/code-module.repository.ts +0 -142
  146. package/src/db/repositories/error.repository.ts +0 -189
  147. package/src/db/repositories/insight.repository.ts +0 -99
  148. package/src/db/repositories/notification.repository.ts +0 -66
  149. package/src/db/repositories/project.repository.ts +0 -93
  150. package/src/db/repositories/rule.repository.ts +0 -108
  151. package/src/db/repositories/solution.repository.ts +0 -154
  152. package/src/db/repositories/synapse.repository.ts +0 -163
  153. package/src/db/repositories/terminal.repository.ts +0 -101
  154. package/src/embeddings/engine.ts +0 -238
  155. package/src/hooks/post-tool-use.ts +0 -92
  156. package/src/hooks/post-write.ts +0 -129
  157. package/src/index.ts +0 -63
  158. package/src/ipc/client.ts +0 -118
  159. package/src/ipc/protocol.ts +0 -35
  160. package/src/ipc/router.ts +0 -133
  161. package/src/ipc/server.ts +0 -176
  162. package/src/learning/confidence-scorer.ts +0 -80
  163. package/src/learning/decay.ts +0 -46
  164. package/src/learning/learning-engine.ts +0 -170
  165. package/src/learning/pattern-extractor.ts +0 -90
  166. package/src/learning/rule-generator.ts +0 -74
  167. package/src/main.rs:10:5 +0 -0
  168. package/src/matching/error-matcher.ts +0 -166
  169. package/src/matching/fingerprint.ts +0 -34
  170. package/src/matching/similarity.ts +0 -61
  171. package/src/matching/tfidf.ts +0 -74
  172. package/src/matching/tokenizer.ts +0 -41
  173. package/src/mcp/auto-detect.ts +0 -93
  174. package/src/mcp/http-server.ts +0 -140
  175. package/src/mcp/server.ts +0 -73
  176. package/src/mcp/tools.ts +0 -328
  177. package/src/parsing/error-parser.ts +0 -28
  178. package/src/parsing/parsers/compiler.ts +0 -93
  179. package/src/parsing/parsers/generic.ts +0 -28
  180. package/src/parsing/parsers/go.ts +0 -97
  181. package/src/parsing/parsers/node.ts +0 -69
  182. package/src/parsing/parsers/python.ts +0 -62
  183. package/src/parsing/parsers/rust.ts +0 -50
  184. package/src/parsing/parsers/shell.ts +0 -42
  185. package/src/parsing/types.ts +0 -47
  186. package/src/research/gap-analyzer.ts +0 -135
  187. package/src/research/insight-generator.ts +0 -123
  188. package/src/research/research-engine.ts +0 -116
  189. package/src/research/synergy-detector.ts +0 -126
  190. package/src/research/template-extractor.ts +0 -130
  191. package/src/research/trend-analyzer.ts +0 -127
  192. package/src/services/analytics.service.ts +0 -226
  193. package/src/services/code.service.ts +0 -271
  194. package/src/services/error.service.ts +0 -266
  195. package/src/services/git.service.ts +0 -132
  196. package/src/services/notification.service.ts +0 -41
  197. package/src/services/prevention.service.ts +0 -159
  198. package/src/services/research.service.ts +0 -98
  199. package/src/services/solution.service.ts +0 -174
  200. package/src/services/synapse.service.ts +0 -59
  201. package/src/services/terminal.service.ts +0 -81
  202. package/src/synapses/activation.ts +0 -80
  203. package/src/synapses/decay.ts +0 -38
  204. package/src/synapses/hebbian.ts +0 -69
  205. package/src/synapses/pathfinder.ts +0 -81
  206. package/src/synapses/synapse-manager.ts +0 -113
  207. package/src/types/code.types.ts +0 -52
  208. package/src/types/config.types.ts +0 -103
  209. package/src/types/error.types.ts +0 -67
  210. package/src/types/ipc.types.ts +0 -8
  211. package/src/types/mcp.types.ts +0 -53
  212. package/src/types/research.types.ts +0 -28
  213. package/src/types/solution.types.ts +0 -30
  214. package/src/types/synapse.types.ts +0 -50
  215. package/src/utils/events.ts +0 -45
  216. package/src/utils/hash.ts +0 -5
  217. package/src/utils/logger.ts +0 -48
  218. package/src/utils/paths.ts +0 -19
  219. package/tests/e2e/test_code_intelligence.py +0 -1015
  220. package/tests/e2e/test_error_memory.py +0 -451
  221. package/tests/e2e/test_full_integration.py +0 -534
  222. package/tests/fixtures/code-modules/modules.ts +0 -83
  223. package/tests/fixtures/errors/go.ts +0 -9
  224. package/tests/fixtures/errors/node.ts +0 -24
  225. package/tests/fixtures/errors/python.ts +0 -21
  226. package/tests/fixtures/errors/rust.ts +0 -25
  227. package/tests/fixtures/errors/shell.ts +0 -15
  228. package/tests/fixtures/solutions/solutions.ts +0 -27
  229. package/tests/helpers/setup-db.ts +0 -52
  230. package/tests/integration/code-flow.test.ts +0 -86
  231. package/tests/integration/error-flow.test.ts +0 -83
  232. package/tests/integration/ipc-flow.test.ts +0 -166
  233. package/tests/integration/learning-cycle.test.ts +0 -82
  234. package/tests/integration/synapse-flow.test.ts +0 -117
  235. package/tests/unit/code/analyzer.test.ts +0 -58
  236. package/tests/unit/code/fingerprint.test.ts +0 -51
  237. package/tests/unit/code/scorer.test.ts +0 -55
  238. package/tests/unit/learning/confidence-scorer.test.ts +0 -60
  239. package/tests/unit/learning/decay.test.ts +0 -45
  240. package/tests/unit/learning/pattern-extractor.test.ts +0 -50
  241. package/tests/unit/matching/error-matcher.test.ts +0 -69
  242. package/tests/unit/matching/fingerprint.test.ts +0 -47
  243. package/tests/unit/matching/similarity.test.ts +0 -65
  244. package/tests/unit/matching/tfidf.test.ts +0 -71
  245. package/tests/unit/matching/tokenizer.test.ts +0 -83
  246. package/tests/unit/parsing/parsers.test.ts +0 -113
  247. package/tests/unit/research/gap-analyzer.test.ts +0 -45
  248. package/tests/unit/research/trend-analyzer.test.ts +0 -45
  249. package/tests/unit/synapses/activation.test.ts +0 -80
  250. package/tests/unit/synapses/decay.test.ts +0 -27
  251. package/tests/unit/synapses/hebbian.test.ts +0 -96
  252. package/tests/unit/synapses/pathfinder.test.ts +0 -72
  253. 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
- }