@timmeck/brain 1.8.5 → 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 (221) hide show
  1. package/.dockerignore +11 -0
  2. package/Dockerfile +21 -0
  3. package/README.md +19 -0
  4. package/assets/brain-avatar-256.png +0 -0
  5. package/assets/brain-avatar-512.png +0 -0
  6. package/assets/brain-avatar.png +0 -0
  7. package/brain.log +1164 -0
  8. package/{src/cli/commands/dashboard.ts → dashboard.html} +688 -807
  9. package/dist/api/server.d.ts +4 -18
  10. package/dist/api/server.js +4 -173
  11. package/dist/api/server.js.map +1 -1
  12. package/dist/brain.d.ts +6 -0
  13. package/dist/brain.js +56 -4
  14. package/dist/brain.js.map +1 -1
  15. package/dist/cli/colors.d.ts +4 -25
  16. package/dist/cli/colors.js +3 -89
  17. package/dist/cli/colors.js.map +1 -1
  18. package/dist/cli/commands/peers.d.ts +2 -0
  19. package/dist/cli/commands/peers.js +38 -0
  20. package/dist/cli/commands/peers.js.map +1 -0
  21. package/dist/cli/commands/start.js +54 -17
  22. package/dist/cli/commands/start.js.map +1 -1
  23. package/dist/db/connection.d.ts +1 -2
  24. package/dist/db/connection.js +1 -18
  25. package/dist/db/connection.js.map +1 -1
  26. package/dist/index.js +3 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/ipc/__tests__/protocol.test.d.ts +1 -0
  29. package/dist/ipc/__tests__/protocol.test.js +117 -0
  30. package/dist/ipc/__tests__/protocol.test.js.map +1 -0
  31. package/dist/ipc/client.d.ts +1 -16
  32. package/dist/ipc/client.js +1 -100
  33. package/dist/ipc/client.js.map +1 -1
  34. package/dist/ipc/protocol.d.ts +1 -8
  35. package/dist/ipc/protocol.js +1 -28
  36. package/dist/ipc/protocol.js.map +1 -1
  37. package/dist/ipc/router.js +8 -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/mcp/http-server.d.ts +1 -7
  43. package/dist/mcp/http-server.js +6 -117
  44. package/dist/mcp/http-server.js.map +1 -1
  45. package/dist/mcp/server.js +5 -61
  46. package/dist/mcp/server.js.map +1 -1
  47. package/dist/signals/__tests__/fingerprint.test.d.ts +1 -0
  48. package/dist/signals/__tests__/fingerprint.test.js +118 -0
  49. package/dist/signals/__tests__/fingerprint.test.js.map +1 -0
  50. package/dist/types/ipc.types.d.ts +1 -11
  51. package/dist/utils/__tests__/hash.test.d.ts +1 -0
  52. package/dist/utils/__tests__/hash.test.js +32 -0
  53. package/dist/utils/__tests__/hash.test.js.map +1 -0
  54. package/dist/utils/__tests__/paths.test.d.ts +1 -0
  55. package/dist/utils/__tests__/paths.test.js +75 -0
  56. package/dist/utils/__tests__/paths.test.js.map +1 -0
  57. package/dist/utils/events.d.ts +4 -8
  58. package/dist/utils/events.js +2 -14
  59. package/dist/utils/events.js.map +1 -1
  60. package/dist/utils/hash.d.ts +1 -1
  61. package/dist/utils/hash.js +1 -4
  62. package/dist/utils/hash.js.map +1 -1
  63. package/dist/utils/logger.d.ts +3 -2
  64. package/dist/utils/logger.js +8 -35
  65. package/dist/utils/logger.js.map +1 -1
  66. package/dist/utils/paths.d.ts +2 -1
  67. package/dist/utils/paths.js +4 -13
  68. package/dist/utils/paths.js.map +1 -1
  69. package/gen_avatar.py +142 -0
  70. package/package.json +2 -1
  71. package/BRAIN_PLAN.md +0 -3324
  72. package/src/api/server.ts +0 -395
  73. package/src/brain.ts +0 -266
  74. package/src/cli/colors.ts +0 -116
  75. package/src/cli/commands/config.ts +0 -169
  76. package/src/cli/commands/doctor.ts +0 -124
  77. package/src/cli/commands/explain.ts +0 -83
  78. package/src/cli/commands/export.ts +0 -31
  79. package/src/cli/commands/import.ts +0 -199
  80. package/src/cli/commands/insights.ts +0 -65
  81. package/src/cli/commands/learn.ts +0 -24
  82. package/src/cli/commands/modules.ts +0 -53
  83. package/src/cli/commands/network.ts +0 -67
  84. package/src/cli/commands/projects.ts +0 -42
  85. package/src/cli/commands/query.ts +0 -120
  86. package/src/cli/commands/start.ts +0 -62
  87. package/src/cli/commands/status.ts +0 -75
  88. package/src/cli/commands/stop.ts +0 -34
  89. package/src/cli/ipc-helper.ts +0 -22
  90. package/src/cli/update-check.ts +0 -63
  91. package/src/code/analyzer.ts +0 -117
  92. package/src/code/fingerprint.ts +0 -87
  93. package/src/code/matcher.ts +0 -129
  94. package/src/code/parsers/generic.ts +0 -29
  95. package/src/code/parsers/python.ts +0 -54
  96. package/src/code/parsers/typescript.ts +0 -65
  97. package/src/code/registry.ts +0 -60
  98. package/src/code/scorer.ts +0 -120
  99. package/src/config.ts +0 -135
  100. package/src/dashboard/server.ts +0 -142
  101. package/src/db/connection.ts +0 -22
  102. package/src/db/migrations/001_core_schema.ts +0 -120
  103. package/src/db/migrations/002_learning_schema.ts +0 -38
  104. package/src/db/migrations/003_code_schema.ts +0 -53
  105. package/src/db/migrations/004_synapses_schema.ts +0 -57
  106. package/src/db/migrations/005_fts_indexes.ts +0 -78
  107. package/src/db/migrations/006_synapses_phase3.ts +0 -17
  108. package/src/db/migrations/007_feedback.ts +0 -13
  109. package/src/db/migrations/008_git_integration.ts +0 -38
  110. package/src/db/migrations/009_embeddings.ts +0 -8
  111. package/src/db/migrations/index.ts +0 -70
  112. package/src/db/repositories/antipattern.repository.ts +0 -66
  113. package/src/db/repositories/code-module.repository.ts +0 -142
  114. package/src/db/repositories/error.repository.ts +0 -189
  115. package/src/db/repositories/insight.repository.ts +0 -99
  116. package/src/db/repositories/notification.repository.ts +0 -66
  117. package/src/db/repositories/project.repository.ts +0 -93
  118. package/src/db/repositories/rule.repository.ts +0 -108
  119. package/src/db/repositories/solution.repository.ts +0 -154
  120. package/src/db/repositories/synapse.repository.ts +0 -163
  121. package/src/db/repositories/terminal.repository.ts +0 -101
  122. package/src/embeddings/engine.ts +0 -238
  123. package/src/hooks/post-tool-use.ts +0 -92
  124. package/src/hooks/post-write.ts +0 -129
  125. package/src/index.ts +0 -63
  126. package/src/ipc/client.ts +0 -118
  127. package/src/ipc/protocol.ts +0 -35
  128. package/src/ipc/router.ts +0 -133
  129. package/src/ipc/server.ts +0 -176
  130. package/src/learning/confidence-scorer.ts +0 -80
  131. package/src/learning/decay.ts +0 -46
  132. package/src/learning/learning-engine.ts +0 -170
  133. package/src/learning/pattern-extractor.ts +0 -90
  134. package/src/learning/rule-generator.ts +0 -74
  135. package/src/main.rs:10:5 +0 -0
  136. package/src/matching/error-matcher.ts +0 -166
  137. package/src/matching/fingerprint.ts +0 -34
  138. package/src/matching/similarity.ts +0 -61
  139. package/src/matching/tfidf.ts +0 -74
  140. package/src/matching/tokenizer.ts +0 -41
  141. package/src/mcp/auto-detect.ts +0 -93
  142. package/src/mcp/http-server.ts +0 -140
  143. package/src/mcp/server.ts +0 -73
  144. package/src/mcp/tools.ts +0 -328
  145. package/src/parsing/error-parser.ts +0 -28
  146. package/src/parsing/parsers/compiler.ts +0 -93
  147. package/src/parsing/parsers/generic.ts +0 -28
  148. package/src/parsing/parsers/go.ts +0 -97
  149. package/src/parsing/parsers/node.ts +0 -69
  150. package/src/parsing/parsers/python.ts +0 -62
  151. package/src/parsing/parsers/rust.ts +0 -50
  152. package/src/parsing/parsers/shell.ts +0 -42
  153. package/src/parsing/types.ts +0 -47
  154. package/src/research/gap-analyzer.ts +0 -135
  155. package/src/research/insight-generator.ts +0 -123
  156. package/src/research/research-engine.ts +0 -116
  157. package/src/research/synergy-detector.ts +0 -126
  158. package/src/research/template-extractor.ts +0 -130
  159. package/src/research/trend-analyzer.ts +0 -127
  160. package/src/services/analytics.service.ts +0 -226
  161. package/src/services/code.service.ts +0 -271
  162. package/src/services/error.service.ts +0 -266
  163. package/src/services/git.service.ts +0 -132
  164. package/src/services/notification.service.ts +0 -41
  165. package/src/services/prevention.service.ts +0 -159
  166. package/src/services/research.service.ts +0 -98
  167. package/src/services/solution.service.ts +0 -174
  168. package/src/services/synapse.service.ts +0 -59
  169. package/src/services/terminal.service.ts +0 -81
  170. package/src/synapses/activation.ts +0 -80
  171. package/src/synapses/decay.ts +0 -38
  172. package/src/synapses/hebbian.ts +0 -69
  173. package/src/synapses/pathfinder.ts +0 -81
  174. package/src/synapses/synapse-manager.ts +0 -113
  175. package/src/types/code.types.ts +0 -52
  176. package/src/types/config.types.ts +0 -103
  177. package/src/types/error.types.ts +0 -67
  178. package/src/types/ipc.types.ts +0 -8
  179. package/src/types/mcp.types.ts +0 -53
  180. package/src/types/research.types.ts +0 -28
  181. package/src/types/solution.types.ts +0 -30
  182. package/src/types/synapse.types.ts +0 -50
  183. package/src/utils/events.ts +0 -45
  184. package/src/utils/hash.ts +0 -5
  185. package/src/utils/logger.ts +0 -48
  186. package/src/utils/paths.ts +0 -19
  187. package/tests/e2e/test_code_intelligence.py +0 -1015
  188. package/tests/e2e/test_error_memory.py +0 -451
  189. package/tests/e2e/test_full_integration.py +0 -534
  190. package/tests/fixtures/code-modules/modules.ts +0 -83
  191. package/tests/fixtures/errors/go.ts +0 -9
  192. package/tests/fixtures/errors/node.ts +0 -24
  193. package/tests/fixtures/errors/python.ts +0 -21
  194. package/tests/fixtures/errors/rust.ts +0 -25
  195. package/tests/fixtures/errors/shell.ts +0 -15
  196. package/tests/fixtures/solutions/solutions.ts +0 -27
  197. package/tests/helpers/setup-db.ts +0 -52
  198. package/tests/integration/code-flow.test.ts +0 -86
  199. package/tests/integration/error-flow.test.ts +0 -83
  200. package/tests/integration/ipc-flow.test.ts +0 -166
  201. package/tests/integration/learning-cycle.test.ts +0 -82
  202. package/tests/integration/synapse-flow.test.ts +0 -117
  203. package/tests/unit/code/analyzer.test.ts +0 -58
  204. package/tests/unit/code/fingerprint.test.ts +0 -51
  205. package/tests/unit/code/scorer.test.ts +0 -55
  206. package/tests/unit/learning/confidence-scorer.test.ts +0 -60
  207. package/tests/unit/learning/decay.test.ts +0 -45
  208. package/tests/unit/learning/pattern-extractor.test.ts +0 -50
  209. package/tests/unit/matching/error-matcher.test.ts +0 -69
  210. package/tests/unit/matching/fingerprint.test.ts +0 -47
  211. package/tests/unit/matching/similarity.test.ts +0 -65
  212. package/tests/unit/matching/tfidf.test.ts +0 -71
  213. package/tests/unit/matching/tokenizer.test.ts +0 -83
  214. package/tests/unit/parsing/parsers.test.ts +0 -113
  215. package/tests/unit/research/gap-analyzer.test.ts +0 -45
  216. package/tests/unit/research/trend-analyzer.test.ts +0 -45
  217. package/tests/unit/synapses/activation.test.ts +0 -80
  218. package/tests/unit/synapses/decay.test.ts +0 -27
  219. package/tests/unit/synapses/hebbian.test.ts +0 -96
  220. package/tests/unit/synapses/pathfinder.test.ts +0 -72
  221. package/tsconfig.json +0 -18
@@ -1,170 +0,0 @@
1
- import type { LearningConfig } from '../types/config.types.js';
2
- import type { ErrorRepository } from '../db/repositories/error.repository.js';
3
- import type { SolutionRepository } from '../db/repositories/solution.repository.js';
4
- import type { RuleRepository } from '../db/repositories/rule.repository.js';
5
- import type { AntipatternRepository } from '../db/repositories/antipattern.repository.js';
6
- import type { SynapseManager } from '../synapses/synapse-manager.js';
7
- import { extractPatterns } from './pattern-extractor.js';
8
- import { generateRules, persistRules } from './rule-generator.js';
9
- import { shouldPruneRule } from './decay.js';
10
- import { computeAdaptiveThresholds, type AdaptiveThresholds } from './confidence-scorer.js';
11
- import { getLogger } from '../utils/logger.js';
12
-
13
- export interface LearningCycleResult {
14
- newPatterns: number;
15
- updatedRules: number;
16
- prunedRules: number;
17
- newAntipatterns: number;
18
- duration: number;
19
- }
20
-
21
- export class LearningEngine {
22
- private timer: ReturnType<typeof setInterval> | null = null;
23
- private logger = getLogger();
24
- private lastCycleAt: string | null = null;
25
-
26
- constructor(
27
- private config: LearningConfig,
28
- private errorRepo: ErrorRepository,
29
- private solutionRepo: SolutionRepository,
30
- private ruleRepo: RuleRepository,
31
- private antipatternRepo: AntipatternRepository,
32
- private synapseManager: SynapseManager,
33
- ) {}
34
-
35
- start(): void {
36
- this.logger.info(`Learning engine starting (interval: ${this.config.intervalMs}ms)`);
37
- this.timer = setInterval(() => this.runCycle(), this.config.intervalMs);
38
- }
39
-
40
- stop(): void {
41
- if (this.timer) {
42
- clearInterval(this.timer);
43
- this.timer = null;
44
- }
45
- this.logger.info('Learning engine stopped');
46
- }
47
-
48
- runCycle(): LearningCycleResult {
49
- const start = Date.now();
50
- this.logger.info('Learning cycle starting');
51
-
52
- const result: LearningCycleResult = {
53
- newPatterns: 0,
54
- updatedRules: 0,
55
- prunedRules: 0,
56
- newAntipatterns: 0,
57
- duration: 0,
58
- };
59
-
60
- // Phase 0: Compute adaptive thresholds
61
- const totalErrors = this.errorRepo.findUnresolved().length + this.errorRepo.countSince(new Date(0).toISOString());
62
- const totalSolutions = this.solutionRepo.getAll().length;
63
- const adaptive = computeAdaptiveThresholds(totalErrors, totalSolutions, this.config);
64
- this.logger.debug(`Adaptive thresholds: minOcc=${adaptive.minOccurrences}, minSuccess=${adaptive.minSuccessRate.toFixed(2)}, minConf=${adaptive.minConfidence.toFixed(2)}`);
65
-
66
- // Phase 1: Collect recent errors
67
- const since = this.lastCycleAt ?? new Date(Date.now() - this.config.intervalMs).toISOString();
68
- const recentErrors = this.errorRepo.findUnresolved();
69
-
70
- // Phase 2: Extract patterns
71
- const patterns = extractPatterns(recentErrors, adaptive.minSuccessRate);
72
- result.newPatterns = patterns.length;
73
-
74
- // Phase 3: Enrich patterns with solution data
75
- for (const pattern of patterns) {
76
- let totalSuccess = 0;
77
- let totalAttempts = 0;
78
-
79
- for (const errorId of pattern.errorIds) {
80
- const solutions = this.solutionRepo.findForError(errorId);
81
- pattern.solutionIds.push(...solutions.map(s => s.id));
82
-
83
- for (const sol of solutions) {
84
- const rate = this.solutionRepo.successRate(sol.id);
85
- totalSuccess += rate;
86
- totalAttempts++;
87
- }
88
- }
89
-
90
- pattern.successRate = totalAttempts > 0 ? totalSuccess / totalAttempts : 0;
91
- pattern.confidence = Math.min(
92
- 0.95,
93
- pattern.successRate * 0.6 + Math.min(1, pattern.occurrences / 10) * 0.4,
94
- );
95
- }
96
-
97
- // Phase 4: Generate rules from patterns (using adaptive thresholds)
98
- const adaptiveConfig = { ...this.config, ...adaptive };
99
- const rules = generateRules(patterns, adaptiveConfig);
100
- result.updatedRules = persistRules(rules, this.ruleRepo);
101
-
102
- // Phase 5: Prune weak rules (using adaptive thresholds)
103
- const activeRules = this.ruleRepo.findActive();
104
- for (const rule of activeRules) {
105
- if (shouldPruneRule(
106
- rule.confidence,
107
- 0, // rejection count not tracked yet
108
- rule.occurrences,
109
- adaptive.pruneThreshold,
110
- this.config.maxRejectionRate,
111
- )) {
112
- this.ruleRepo.update(rule.id, { active: 0 });
113
- result.prunedRules++;
114
- }
115
- }
116
-
117
- // Phase 6: Detect antipatterns
118
- result.newAntipatterns = this.detectAntipatterns();
119
-
120
- // Phase 7: Run synapse decay
121
- this.synapseManager.runDecay();
122
-
123
- this.lastCycleAt = new Date().toISOString();
124
- result.duration = Date.now() - start;
125
- this.logger.info(`Learning cycle complete: ${result.newPatterns} patterns, ${result.updatedRules} rules, ${result.prunedRules} pruned, ${result.newAntipatterns} antipatterns (${result.duration}ms)`);
126
-
127
- return result;
128
- }
129
-
130
- private detectAntipatterns(): number {
131
- const unresolvedErrors = this.errorRepo.findUnresolved();
132
- const recurring = unresolvedErrors.filter(e => e.occurrence_count >= this.config.minOccurrences);
133
- let count = 0;
134
-
135
- for (const error of recurring) {
136
- const solutions = this.solutionRepo.findForError(error.id);
137
- const allFailed = solutions.length > 0 && solutions.every(s => {
138
- const rate = this.solutionRepo.successRate(s.id);
139
- return rate < 0.3;
140
- });
141
-
142
- if (solutions.length >= 2 && allFailed) {
143
- // Check if antipattern already exists
144
- const existing = this.antipatternRepo.findByProject(error.project_id);
145
- const alreadyDetected = existing.some(ap =>
146
- ap.pattern.includes(error.type) && ap.description.includes(error.message.substring(0, 30))
147
- );
148
-
149
- if (!alreadyDetected) {
150
- this.antipatternRepo.create({
151
- pattern: `${error.type}.*${error.message.substring(0, 40).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}`,
152
- description: `Recurring error without solution: ${error.type}: ${error.message}`,
153
- severity: solutions.length >= 4 ? 'critical' : 'warning',
154
- suggestion: null,
155
- occurrences: error.occurrence_count,
156
- project_id: error.project_id,
157
- global: 0,
158
- });
159
- count++;
160
- }
161
- }
162
- }
163
-
164
- return count;
165
- }
166
-
167
- getLastCycleAt(): string | null {
168
- return this.lastCycleAt;
169
- }
170
- }
@@ -1,90 +0,0 @@
1
- import type { ErrorRecord } from '../types/error.types.js';
2
- import { tokenize } from '../matching/tokenizer.js';
3
- import { cosineSimilarity } from '../matching/similarity.js';
4
-
5
- export interface ErrorPattern {
6
- errorType: string;
7
- messageTemplate: string;
8
- messageRegex: string;
9
- filePattern: string | null;
10
- occurrences: number;
11
- errorIds: number[];
12
- solutionIds: number[];
13
- confidence: number;
14
- successRate: number;
15
- }
16
-
17
- interface Centroid {
18
- errorType: string;
19
- tokens: string[];
20
- errorIds: number[];
21
- filePattern: string | null;
22
- }
23
-
24
- /**
25
- * Extract patterns from error records using centroid-based clustering.
26
- */
27
- export function extractPatterns(
28
- errors: ErrorRecord[],
29
- similarityThreshold: number = 0.7,
30
- ): ErrorPattern[] {
31
- const centroids: Centroid[] = [];
32
-
33
- for (const error of errors) {
34
- const tokens = tokenize(`${error.type} ${error.message}`);
35
- let merged = false;
36
-
37
- for (const centroid of centroids) {
38
- if (centroid.errorType !== error.type) continue;
39
-
40
- const sim = cosineSimilarity(centroid.tokens, tokens);
41
- if (sim >= similarityThreshold) {
42
- // Merge into existing centroid (running average)
43
- const allTokens = [...centroid.tokens, ...tokens];
44
- centroid.tokens = [...new Set(allTokens)];
45
- centroid.errorIds.push(error.id);
46
- if (!centroid.filePattern && error.file_path) {
47
- centroid.filePattern = extractFilePattern(error.file_path);
48
- }
49
- merged = true;
50
- break;
51
- }
52
- }
53
-
54
- if (!merged) {
55
- centroids.push({
56
- errorType: error.type,
57
- tokens,
58
- errorIds: [error.id],
59
- filePattern: error.file_path ? extractFilePattern(error.file_path) : null,
60
- });
61
- }
62
- }
63
-
64
- return centroids
65
- .filter(c => c.errorIds.length >= 2)
66
- .map(c => ({
67
- errorType: c.errorType,
68
- messageTemplate: c.tokens.join(' '),
69
- messageRegex: buildRegex(c.tokens),
70
- filePattern: c.filePattern,
71
- occurrences: c.errorIds.length,
72
- errorIds: c.errorIds,
73
- solutionIds: [],
74
- confidence: 0,
75
- successRate: 0,
76
- }));
77
- }
78
-
79
- function extractFilePattern(filePath: string): string {
80
- // Extract the meaningful part: last directory + extension
81
- const parts = filePath.replace(/\\/g, '/').split('/');
82
- const fileName = parts[parts.length - 1] ?? '';
83
- const ext = fileName.split('.').pop() ?? '';
84
- return ext ? `*.${ext}` : '*';
85
- }
86
-
87
- function buildRegex(tokens: string[]): string {
88
- const escaped = tokens.map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
89
- return escaped.join('.*');
90
- }
@@ -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
- }