@timmeck/brain 1.8.0 → 1.8.2

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 (177) hide show
  1. package/BRAIN_PLAN.md +3324 -3324
  2. package/LICENSE +21 -21
  3. package/dist/api/server.d.ts +4 -0
  4. package/dist/api/server.js +73 -0
  5. package/dist/api/server.js.map +1 -1
  6. package/dist/brain.js +2 -1
  7. package/dist/brain.js.map +1 -1
  8. package/dist/cli/commands/dashboard.js +606 -572
  9. package/dist/cli/commands/dashboard.js.map +1 -1
  10. package/dist/dashboard/server.js +25 -25
  11. package/dist/db/migrations/001_core_schema.js +115 -115
  12. package/dist/db/migrations/002_learning_schema.js +33 -33
  13. package/dist/db/migrations/003_code_schema.js +48 -48
  14. package/dist/db/migrations/004_synapses_schema.js +52 -52
  15. package/dist/db/migrations/005_fts_indexes.js +73 -73
  16. package/dist/db/migrations/007_feedback.js +8 -8
  17. package/dist/db/migrations/008_git_integration.js +33 -33
  18. package/dist/db/migrations/009_embeddings.js +3 -3
  19. package/dist/db/repositories/antipattern.repository.js +3 -3
  20. package/dist/db/repositories/code-module.repository.js +32 -32
  21. package/dist/db/repositories/notification.repository.js +3 -3
  22. package/dist/db/repositories/project.repository.js +21 -21
  23. package/dist/db/repositories/rule.repository.js +24 -24
  24. package/dist/db/repositories/solution.repository.js +50 -50
  25. package/dist/db/repositories/synapse.repository.js +18 -18
  26. package/dist/db/repositories/terminal.repository.js +24 -24
  27. package/dist/embeddings/engine.d.ts +2 -2
  28. package/dist/embeddings/engine.js +17 -4
  29. package/dist/embeddings/engine.js.map +1 -1
  30. package/dist/index.js +1 -1
  31. package/dist/ipc/server.d.ts +8 -0
  32. package/dist/ipc/server.js +67 -1
  33. package/dist/ipc/server.js.map +1 -1
  34. package/dist/matching/error-matcher.js +5 -5
  35. package/dist/matching/fingerprint.js +6 -1
  36. package/dist/matching/fingerprint.js.map +1 -1
  37. package/dist/mcp/http-server.js +8 -2
  38. package/dist/mcp/http-server.js.map +1 -1
  39. package/dist/services/code.service.d.ts +3 -0
  40. package/dist/services/code.service.js +33 -4
  41. package/dist/services/code.service.js.map +1 -1
  42. package/dist/services/error.service.js +4 -3
  43. package/dist/services/error.service.js.map +1 -1
  44. package/dist/services/git.service.js +14 -14
  45. package/package.json +49 -49
  46. package/src/api/server.ts +395 -321
  47. package/src/brain.ts +266 -265
  48. package/src/cli/colors.ts +116 -116
  49. package/src/cli/commands/config.ts +169 -169
  50. package/src/cli/commands/dashboard.ts +755 -720
  51. package/src/cli/commands/doctor.ts +118 -118
  52. package/src/cli/commands/explain.ts +83 -83
  53. package/src/cli/commands/export.ts +31 -31
  54. package/src/cli/commands/import.ts +199 -199
  55. package/src/cli/commands/insights.ts +65 -65
  56. package/src/cli/commands/learn.ts +24 -24
  57. package/src/cli/commands/modules.ts +53 -53
  58. package/src/cli/commands/network.ts +67 -67
  59. package/src/cli/commands/projects.ts +42 -42
  60. package/src/cli/commands/query.ts +120 -120
  61. package/src/cli/commands/start.ts +62 -62
  62. package/src/cli/commands/status.ts +75 -75
  63. package/src/cli/commands/stop.ts +34 -34
  64. package/src/cli/ipc-helper.ts +22 -22
  65. package/src/cli/update-check.ts +63 -63
  66. package/src/code/fingerprint.ts +87 -87
  67. package/src/code/parsers/generic.ts +29 -29
  68. package/src/code/parsers/python.ts +54 -54
  69. package/src/code/parsers/typescript.ts +65 -65
  70. package/src/code/registry.ts +60 -60
  71. package/src/dashboard/server.ts +142 -142
  72. package/src/db/connection.ts +22 -22
  73. package/src/db/migrations/001_core_schema.ts +120 -120
  74. package/src/db/migrations/002_learning_schema.ts +38 -38
  75. package/src/db/migrations/003_code_schema.ts +53 -53
  76. package/src/db/migrations/004_synapses_schema.ts +57 -57
  77. package/src/db/migrations/005_fts_indexes.ts +78 -78
  78. package/src/db/migrations/006_synapses_phase3.ts +17 -17
  79. package/src/db/migrations/007_feedback.ts +13 -13
  80. package/src/db/migrations/008_git_integration.ts +38 -38
  81. package/src/db/migrations/009_embeddings.ts +8 -8
  82. package/src/db/repositories/antipattern.repository.ts +66 -66
  83. package/src/db/repositories/code-module.repository.ts +142 -142
  84. package/src/db/repositories/notification.repository.ts +66 -66
  85. package/src/db/repositories/project.repository.ts +93 -93
  86. package/src/db/repositories/rule.repository.ts +108 -108
  87. package/src/db/repositories/solution.repository.ts +154 -154
  88. package/src/db/repositories/synapse.repository.ts +153 -153
  89. package/src/db/repositories/terminal.repository.ts +101 -101
  90. package/src/embeddings/engine.ts +238 -217
  91. package/src/index.ts +63 -63
  92. package/src/ipc/client.ts +118 -118
  93. package/src/ipc/protocol.ts +35 -35
  94. package/src/ipc/router.ts +133 -133
  95. package/src/ipc/server.ts +176 -110
  96. package/src/learning/decay.ts +46 -46
  97. package/src/learning/pattern-extractor.ts +90 -90
  98. package/src/learning/rule-generator.ts +74 -74
  99. package/src/matching/error-matcher.ts +5 -5
  100. package/src/matching/fingerprint.ts +34 -29
  101. package/src/matching/similarity.ts +61 -61
  102. package/src/matching/tfidf.ts +74 -74
  103. package/src/matching/tokenizer.ts +41 -41
  104. package/src/mcp/auto-detect.ts +93 -93
  105. package/src/mcp/http-server.ts +140 -137
  106. package/src/mcp/server.ts +73 -73
  107. package/src/parsing/error-parser.ts +28 -28
  108. package/src/parsing/parsers/compiler.ts +93 -93
  109. package/src/parsing/parsers/generic.ts +28 -28
  110. package/src/parsing/parsers/go.ts +97 -97
  111. package/src/parsing/parsers/node.ts +69 -69
  112. package/src/parsing/parsers/python.ts +62 -62
  113. package/src/parsing/parsers/rust.ts +50 -50
  114. package/src/parsing/parsers/shell.ts +42 -42
  115. package/src/parsing/types.ts +47 -47
  116. package/src/research/gap-analyzer.ts +135 -135
  117. package/src/research/insight-generator.ts +123 -123
  118. package/src/research/research-engine.ts +116 -116
  119. package/src/research/synergy-detector.ts +126 -126
  120. package/src/research/template-extractor.ts +130 -130
  121. package/src/research/trend-analyzer.ts +127 -127
  122. package/src/services/code.service.ts +271 -238
  123. package/src/services/error.service.ts +4 -3
  124. package/src/services/git.service.ts +132 -132
  125. package/src/services/notification.service.ts +41 -41
  126. package/src/services/synapse.service.ts +59 -59
  127. package/src/services/terminal.service.ts +81 -81
  128. package/src/synapses/activation.ts +80 -80
  129. package/src/synapses/decay.ts +38 -38
  130. package/src/synapses/hebbian.ts +69 -69
  131. package/src/synapses/pathfinder.ts +81 -81
  132. package/src/synapses/synapse-manager.ts +109 -109
  133. package/src/types/code.types.ts +52 -52
  134. package/src/types/error.types.ts +67 -67
  135. package/src/types/ipc.types.ts +8 -8
  136. package/src/types/mcp.types.ts +53 -53
  137. package/src/types/research.types.ts +28 -28
  138. package/src/types/solution.types.ts +30 -30
  139. package/src/utils/events.ts +45 -45
  140. package/src/utils/hash.ts +5 -5
  141. package/src/utils/logger.ts +48 -48
  142. package/src/utils/paths.ts +19 -19
  143. package/tests/e2e/test_code_intelligence.py +1015 -0
  144. package/tests/e2e/test_error_memory.py +451 -0
  145. package/tests/e2e/test_full_integration.py +534 -0
  146. package/tests/fixtures/code-modules/modules.ts +83 -83
  147. package/tests/fixtures/errors/go.ts +9 -9
  148. package/tests/fixtures/errors/node.ts +24 -24
  149. package/tests/fixtures/errors/python.ts +21 -21
  150. package/tests/fixtures/errors/rust.ts +25 -25
  151. package/tests/fixtures/errors/shell.ts +15 -15
  152. package/tests/fixtures/solutions/solutions.ts +27 -27
  153. package/tests/helpers/setup-db.ts +52 -52
  154. package/tests/integration/code-flow.test.ts +86 -86
  155. package/tests/integration/error-flow.test.ts +83 -83
  156. package/tests/integration/ipc-flow.test.ts +166 -166
  157. package/tests/integration/learning-cycle.test.ts +82 -82
  158. package/tests/integration/synapse-flow.test.ts +117 -117
  159. package/tests/unit/code/analyzer.test.ts +58 -58
  160. package/tests/unit/code/fingerprint.test.ts +51 -51
  161. package/tests/unit/code/scorer.test.ts +55 -55
  162. package/tests/unit/learning/confidence-scorer.test.ts +60 -60
  163. package/tests/unit/learning/decay.test.ts +45 -45
  164. package/tests/unit/learning/pattern-extractor.test.ts +50 -50
  165. package/tests/unit/matching/error-matcher.test.ts +69 -69
  166. package/tests/unit/matching/fingerprint.test.ts +47 -47
  167. package/tests/unit/matching/similarity.test.ts +65 -65
  168. package/tests/unit/matching/tfidf.test.ts +71 -71
  169. package/tests/unit/matching/tokenizer.test.ts +83 -83
  170. package/tests/unit/parsing/parsers.test.ts +113 -113
  171. package/tests/unit/research/gap-analyzer.test.ts +45 -45
  172. package/tests/unit/research/trend-analyzer.test.ts +45 -45
  173. package/tests/unit/synapses/activation.test.ts +80 -80
  174. package/tests/unit/synapses/decay.test.ts +27 -27
  175. package/tests/unit/synapses/hebbian.test.ts +96 -96
  176. package/tests/unit/synapses/pathfinder.test.ts +72 -72
  177. package/tsconfig.json +18 -18
@@ -1,90 +1,90 @@
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
+ 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 +1,74 @@
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
- }
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
+ }
@@ -23,12 +23,12 @@ interface MatchSignal {
23
23
 
24
24
  // Base signals (used when vector search is NOT available)
25
25
  const SIGNALS_BASE: MatchSignal[] = [
26
- { name: 'fingerprint', weight: 0.30, compute: fingerprintMatch },
27
- { name: 'message_similarity', weight: 0.20, compute: messageSimilarity },
26
+ { name: 'fingerprint', weight: 0.20, compute: fingerprintMatch },
27
+ { name: 'message_similarity', weight: 0.25, compute: messageSimilarity },
28
28
  { name: 'type_match', weight: 0.15, compute: typeMatch },
29
29
  { name: 'stack_similarity', weight: 0.15, compute: stackSimilarity },
30
- { name: 'file_similarity', weight: 0.10, compute: fileSimilarity },
31
- { name: 'context_similarity', weight: 0.10, compute: contextSimilarity },
30
+ { name: 'file_similarity', weight: 0.12, compute: fileSimilarity },
31
+ { name: 'context_similarity', weight: 0.13, compute: contextSimilarity },
32
32
  ];
33
33
 
34
34
  // Hybrid signals (used when vector search IS available — vector gets 20% weight)
@@ -42,7 +42,7 @@ const SIGNALS_HYBRID: MatchSignal[] = [
42
42
  ];
43
43
 
44
44
  const VECTOR_WEIGHT = 0.20;
45
- const MATCH_THRESHOLD = 0.70;
45
+ const MATCH_THRESHOLD = 0.55;
46
46
  const STRONG_MATCH_THRESHOLD = 0.90;
47
47
 
48
48
  /**
@@ -1,29 +1,34 @@
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
- }
16
-
17
- export function generateFingerprint(
18
- errorType: string,
19
- message: string,
20
- frames: StackFrame[],
21
- ): string {
22
- const template = templateMessage(message);
23
- const topFrames = frames
24
- .slice(0, 3)
25
- .map(f => `${f.function_name || '<anon>'}@${path.basename(f.file_path || '<unknown>')}`)
26
- .join('|');
27
- const input = `${errorType}::${template}::${topFrames}`;
28
- return sha256(input);
29
- }
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 +1,61 @@
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
+ 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
+ }