@timmeck/brain 1.2.0 → 1.8.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 (131) hide show
  1. package/README.md +225 -50
  2. package/dist/api/server.d.ts +19 -0
  3. package/dist/api/server.js +281 -0
  4. package/dist/api/server.js.map +1 -0
  5. package/dist/brain.d.ts +3 -0
  6. package/dist/brain.js +45 -8
  7. package/dist/brain.js.map +1 -1
  8. package/dist/cli/commands/dashboard.js +2 -0
  9. package/dist/cli/commands/dashboard.js.map +1 -1
  10. package/dist/cli/commands/explain.d.ts +2 -0
  11. package/dist/cli/commands/explain.js +76 -0
  12. package/dist/cli/commands/explain.js.map +1 -0
  13. package/dist/code/analyzer.d.ts +6 -0
  14. package/dist/code/analyzer.js +35 -0
  15. package/dist/code/analyzer.js.map +1 -1
  16. package/dist/code/matcher.d.ts +11 -1
  17. package/dist/code/matcher.js +49 -0
  18. package/dist/code/matcher.js.map +1 -1
  19. package/dist/code/scorer.d.ts +1 -0
  20. package/dist/code/scorer.js +15 -1
  21. package/dist/code/scorer.js.map +1 -1
  22. package/dist/config.js +31 -0
  23. package/dist/config.js.map +1 -1
  24. package/dist/dashboard/server.d.ts +15 -0
  25. package/dist/dashboard/server.js +124 -0
  26. package/dist/dashboard/server.js.map +1 -0
  27. package/dist/db/migrations/007_feedback.d.ts +2 -0
  28. package/dist/db/migrations/007_feedback.js +12 -0
  29. package/dist/db/migrations/007_feedback.js.map +1 -0
  30. package/dist/db/migrations/008_git_integration.d.ts +2 -0
  31. package/dist/db/migrations/008_git_integration.js +37 -0
  32. package/dist/db/migrations/008_git_integration.js.map +1 -0
  33. package/dist/db/migrations/009_embeddings.d.ts +2 -0
  34. package/dist/db/migrations/009_embeddings.js +7 -0
  35. package/dist/db/migrations/009_embeddings.js.map +1 -0
  36. package/dist/db/migrations/index.js +6 -0
  37. package/dist/db/migrations/index.js.map +1 -1
  38. package/dist/db/repositories/code-module.repository.d.ts +16 -0
  39. package/dist/db/repositories/code-module.repository.js +42 -0
  40. package/dist/db/repositories/code-module.repository.js.map +1 -1
  41. package/dist/db/repositories/error.repository.d.ts +5 -0
  42. package/dist/db/repositories/error.repository.js +27 -0
  43. package/dist/db/repositories/error.repository.js.map +1 -1
  44. package/dist/db/repositories/insight.repository.d.ts +2 -0
  45. package/dist/db/repositories/insight.repository.js +13 -0
  46. package/dist/db/repositories/insight.repository.js.map +1 -1
  47. package/dist/embeddings/engine.d.ts +42 -0
  48. package/dist/embeddings/engine.js +166 -0
  49. package/dist/embeddings/engine.js.map +1 -0
  50. package/dist/hooks/post-tool-use.js +2 -0
  51. package/dist/hooks/post-tool-use.js.map +1 -1
  52. package/dist/hooks/post-write.js +11 -0
  53. package/dist/hooks/post-write.js.map +1 -1
  54. package/dist/index.js +3 -1
  55. package/dist/index.js.map +1 -1
  56. package/dist/ipc/router.d.ts +2 -0
  57. package/dist/ipc/router.js +13 -0
  58. package/dist/ipc/router.js.map +1 -1
  59. package/dist/learning/confidence-scorer.d.ts +16 -0
  60. package/dist/learning/confidence-scorer.js +20 -0
  61. package/dist/learning/confidence-scorer.js.map +1 -1
  62. package/dist/learning/learning-engine.js +12 -5
  63. package/dist/learning/learning-engine.js.map +1 -1
  64. package/dist/matching/error-matcher.d.ts +9 -1
  65. package/dist/matching/error-matcher.js +50 -5
  66. package/dist/matching/error-matcher.js.map +1 -1
  67. package/dist/mcp/http-server.d.ts +14 -0
  68. package/dist/mcp/http-server.js +117 -0
  69. package/dist/mcp/http-server.js.map +1 -0
  70. package/dist/mcp/tools.d.ts +4 -0
  71. package/dist/mcp/tools.js +41 -14
  72. package/dist/mcp/tools.js.map +1 -1
  73. package/dist/services/analytics.service.d.ts +39 -0
  74. package/dist/services/analytics.service.js +111 -0
  75. package/dist/services/analytics.service.js.map +1 -1
  76. package/dist/services/code.service.d.ts +2 -0
  77. package/dist/services/code.service.js +62 -4
  78. package/dist/services/code.service.js.map +1 -1
  79. package/dist/services/error.service.d.ts +17 -1
  80. package/dist/services/error.service.js +90 -12
  81. package/dist/services/error.service.js.map +1 -1
  82. package/dist/services/git.service.d.ts +49 -0
  83. package/dist/services/git.service.js +112 -0
  84. package/dist/services/git.service.js.map +1 -0
  85. package/dist/services/prevention.service.d.ts +7 -0
  86. package/dist/services/prevention.service.js +38 -0
  87. package/dist/services/prevention.service.js.map +1 -1
  88. package/dist/services/research.service.d.ts +1 -0
  89. package/dist/services/research.service.js +4 -0
  90. package/dist/services/research.service.js.map +1 -1
  91. package/dist/services/solution.service.d.ts +10 -0
  92. package/dist/services/solution.service.js +48 -0
  93. package/dist/services/solution.service.js.map +1 -1
  94. package/dist/types/config.types.d.ts +21 -0
  95. package/dist/types/synapse.types.d.ts +1 -1
  96. package/package.json +8 -3
  97. package/src/api/server.ts +321 -0
  98. package/src/brain.ts +50 -8
  99. package/src/cli/commands/dashboard.ts +2 -0
  100. package/src/cli/commands/explain.ts +83 -0
  101. package/src/code/analyzer.ts +40 -0
  102. package/src/code/matcher.ts +67 -2
  103. package/src/code/scorer.ts +13 -1
  104. package/src/config.ts +24 -0
  105. package/src/dashboard/server.ts +142 -0
  106. package/src/db/migrations/007_feedback.ts +13 -0
  107. package/src/db/migrations/008_git_integration.ts +38 -0
  108. package/src/db/migrations/009_embeddings.ts +8 -0
  109. package/src/db/migrations/index.ts +6 -0
  110. package/src/db/repositories/code-module.repository.ts +53 -0
  111. package/src/db/repositories/error.repository.ts +40 -0
  112. package/src/db/repositories/insight.repository.ts +21 -0
  113. package/src/embeddings/engine.ts +217 -0
  114. package/src/hooks/post-tool-use.ts +2 -0
  115. package/src/hooks/post-write.ts +12 -0
  116. package/src/index.ts +3 -1
  117. package/src/ipc/router.ts +16 -0
  118. package/src/learning/confidence-scorer.ts +33 -0
  119. package/src/learning/learning-engine.ts +13 -5
  120. package/src/matching/error-matcher.ts +55 -4
  121. package/src/mcp/http-server.ts +137 -0
  122. package/src/mcp/tools.ts +52 -14
  123. package/src/services/analytics.service.ts +136 -0
  124. package/src/services/code.service.ts +87 -4
  125. package/src/services/error.service.ts +114 -13
  126. package/src/services/git.service.ts +132 -0
  127. package/src/services/prevention.service.ts +40 -0
  128. package/src/services/research.service.ts +5 -0
  129. package/src/services/solution.service.ts +58 -0
  130. package/src/types/config.types.ts +24 -0
  131. package/src/types/synapse.types.ts +1 -0
@@ -2,6 +2,8 @@ import type { ErrorRecord } from '../types/error.types.js';
2
2
  import type { ErrorRepository } from '../db/repositories/error.repository.js';
3
3
  import type { ProjectRepository } from '../db/repositories/project.repository.js';
4
4
  import type { SynapseManager } from '../synapses/synapse-manager.js';
5
+ import type { MatchingConfig } from '../types/config.types.js';
6
+ import type { EmbeddingEngine } from '../embeddings/engine.js';
5
7
  import { parseError } from '../parsing/error-parser.js';
6
8
  import { generateFingerprint } from '../matching/fingerprint.js';
7
9
  import { matchError, type MatchResult } from '../matching/error-matcher.js';
@@ -13,6 +15,9 @@ export interface ReportErrorInput {
13
15
  errorOutput: string;
14
16
  filePath?: string;
15
17
  terminalId?: number;
18
+ taskContext?: string;
19
+ workingDirectory?: string;
20
+ command?: string;
16
21
  }
17
22
 
18
23
  export interface ErrorQueryInput {
@@ -25,14 +30,23 @@ export interface ErrorQueryInput {
25
30
  export class ErrorService {
26
31
  private logger = getLogger();
27
32
  private eventBus = getEventBus();
33
+ private matchingConfig: MatchingConfig | null = null;
34
+ private embeddingEngine: EmbeddingEngine | null = null;
28
35
 
29
36
  constructor(
30
37
  private errorRepo: ErrorRepository,
31
38
  private projectRepo: ProjectRepository,
32
39
  private synapseManager: SynapseManager,
33
- ) {}
40
+ matchingConfig?: MatchingConfig,
41
+ ) {
42
+ this.matchingConfig = matchingConfig ?? null;
43
+ }
44
+
45
+ setEmbeddingEngine(engine: EmbeddingEngine): void {
46
+ this.embeddingEngine = engine;
47
+ }
34
48
 
35
- report(input: ReportErrorInput): { errorId: number; isNew: boolean; matches: MatchResult[] } {
49
+ report(input: ReportErrorInput): { errorId: number; isNew: boolean; matches: MatchResult[]; crossProjectMatches?: MatchResult[] } {
36
50
  // 1. Ensure project exists
37
51
  let project = this.projectRepo.findByName(input.project);
38
52
  if (!project) {
@@ -40,7 +54,10 @@ export class ErrorService {
40
54
  project = this.projectRepo.getById(id)!;
41
55
  }
42
56
 
43
- // 2. Parse the error
57
+ // 2. Build context from available information
58
+ const context = this.buildContext(input);
59
+
60
+ // 3. Parse the error
44
61
  const parsed = parseError(input.errorOutput);
45
62
  if (!parsed) {
46
63
  this.logger.warn('Could not parse error output');
@@ -51,7 +68,7 @@ export class ErrorService {
51
68
  type: 'UnknownError',
52
69
  message: input.errorOutput.split('\n')[0] ?? input.errorOutput,
53
70
  raw_output: input.errorOutput,
54
- context: null,
71
+ context,
55
72
  file_path: input.filePath ?? null,
56
73
  line_number: null,
57
74
  column_number: null,
@@ -59,14 +76,18 @@ export class ErrorService {
59
76
  return { errorId, isNew: true, matches: [] };
60
77
  }
61
78
 
62
- // 3. Generate fingerprint
79
+ // 4. Generate fingerprint
63
80
  const fingerprint = generateFingerprint(parsed.errorType, parsed.message, parsed.frames);
64
81
 
65
- // 4. Check for existing error with same fingerprint
82
+ // 5. Check for existing error with same fingerprint
66
83
  const existing = this.errorRepo.findByFingerprint(fingerprint);
67
84
  if (existing.length > 0) {
68
85
  const err = existing[0]!;
69
86
  this.errorRepo.incrementOccurrence(err.id);
87
+ // Update context if previously null
88
+ if (!err.context && context) {
89
+ this.errorRepo.update(err.id, { context });
90
+ }
70
91
  this.logger.info(`Known error (id=${err.id}), occurrence incremented`);
71
92
 
72
93
  // Strengthen synapse
@@ -79,7 +100,7 @@ export class ErrorService {
79
100
  return { errorId: err.id, isNew: false, matches: [] };
80
101
  }
81
102
 
82
- // 5. Create new error record
103
+ // 6. Create new error record
83
104
  const errorId = this.errorRepo.create({
84
105
  project_id: project.id,
85
106
  terminal_id: input.terminalId ?? null,
@@ -87,26 +108,26 @@ export class ErrorService {
87
108
  type: parsed.errorType,
88
109
  message: parsed.message,
89
110
  raw_output: input.errorOutput,
90
- context: null,
111
+ context,
91
112
  file_path: parsed.sourceFile ?? input.filePath ?? null,
92
113
  line_number: parsed.sourceLine ?? null,
93
114
  column_number: null,
94
115
  });
95
116
 
96
- // 6. Create synapse: error ↔ project
117
+ // 7. Create synapse: error ↔ project
97
118
  this.synapseManager.strengthen(
98
119
  { type: 'error', id: errorId },
99
120
  { type: 'project', id: project.id },
100
121
  'co_occurs',
101
122
  );
102
123
 
103
- // 7. Find similar errors
124
+ // 8. Find similar errors
104
125
  const candidates = this.errorRepo.findByProject(project.id)
105
126
  .filter(e => e.id !== errorId);
106
127
  const newError = this.errorRepo.getById(errorId)!;
107
128
  const matches = matchError(newError, candidates);
108
129
 
109
- // 8. Create similarity synapses for strong matches
130
+ // 9. Create similarity synapses for strong matches
110
131
  for (const match of matches.filter(m => m.isStrong)) {
111
132
  this.synapseManager.strengthen(
112
133
  { type: 'error', id: errorId },
@@ -115,10 +136,26 @@ export class ErrorService {
115
136
  );
116
137
  }
117
138
 
139
+ // 10. Error Chain Detection: link to recent unresolved errors in same project
140
+ this.detectErrorChain(errorId, project.id);
141
+
142
+ // 11. Cross-Project Transfer Learning
143
+ let crossProjectMatches: MatchResult[] = [];
144
+ if (this.matchingConfig?.crossProjectMatching) {
145
+ crossProjectMatches = this.findCrossProjectMatches(newError, project.id);
146
+ for (const match of crossProjectMatches.filter(m => m.isStrong)) {
147
+ this.synapseManager.strengthen(
148
+ { type: 'error', id: errorId },
149
+ { type: 'error', id: match.errorId },
150
+ 'cross_project',
151
+ );
152
+ }
153
+ }
154
+
118
155
  this.eventBus.emit('error:reported', { errorId, projectId: project.id, fingerprint });
119
156
  this.logger.info(`New error reported (id=${errorId}, type=${parsed.errorType})`);
120
157
 
121
- return { errorId, isNew: true, matches };
158
+ return { errorId, isNew: true, matches, crossProjectMatches };
122
159
  }
123
160
 
124
161
  query(input: ErrorQueryInput): ErrorRecord[] {
@@ -140,7 +177,13 @@ export class ErrorService {
140
177
 
141
178
  const candidates = this.errorRepo.findByProject(error.project_id)
142
179
  .filter(e => e.id !== errorId);
143
- return matchError(error, candidates);
180
+
181
+ // Hybrid search: include vector scores if embedding engine is available
182
+ const vectorScores = this.embeddingEngine?.isReady()
183
+ ? this.embeddingEngine.computeErrorVectorScores(errorId, error.project_id)
184
+ : undefined;
185
+
186
+ return matchError(error, candidates, vectorScores);
144
187
  }
145
188
 
146
189
  resolve(errorId: number, solutionId?: number): void {
@@ -161,4 +204,62 @@ export class ErrorService {
161
204
  countSince(since: string, projectId?: number): number {
162
205
  return this.errorRepo.countSince(since, projectId);
163
206
  }
207
+
208
+ getErrorChain(errorId: number): { parents: ErrorRecord[]; children: ErrorRecord[] } {
209
+ return {
210
+ parents: this.errorRepo.findChainParents(errorId),
211
+ children: this.errorRepo.findChainChildren(errorId),
212
+ };
213
+ }
214
+
215
+ private findCrossProjectMatches(error: ErrorRecord, currentProjectId: number): MatchResult[] {
216
+ const allProjects = this.projectRepo.getAll();
217
+ const otherProjects = allProjects.filter(p => p.id !== currentProjectId);
218
+ const weight = this.matchingConfig?.crossProjectWeight ?? 0.7;
219
+
220
+ const allMatches: MatchResult[] = [];
221
+ for (const project of otherProjects) {
222
+ const candidates = this.errorRepo.findByProject(project.id)
223
+ .filter(e => e.resolved === 1); // Only look at resolved errors from other projects
224
+ const matches = matchError(error, candidates);
225
+ // Apply cross-project weight discount
226
+ for (const match of matches) {
227
+ match.score *= weight;
228
+ match.isStrong = match.score >= 0.90;
229
+ allMatches.push(match);
230
+ }
231
+ }
232
+
233
+ return allMatches.sort((a, b) => b.score - a.score).slice(0, 5);
234
+ }
235
+
236
+ private detectErrorChain(newErrorId: number, projectId: number): void {
237
+ // Look for recent unresolved errors in the same project (last 10 minutes)
238
+ const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000).toISOString();
239
+ const recentErrors = this.errorRepo.findRecentByProject(projectId, tenMinutesAgo, 5);
240
+
241
+ for (const recent of recentErrors) {
242
+ if (recent.id === newErrorId) continue;
243
+ if (recent.resolved === 0) {
244
+ // This new error appeared while trying to fix a recent error → chain
245
+ this.errorRepo.createChain(recent.id, newErrorId, 'caused_by_fix');
246
+ this.synapseManager.strengthen(
247
+ { type: 'error', id: recent.id },
248
+ { type: 'error', id: newErrorId },
249
+ 'causes',
250
+ );
251
+ this.logger.info(`Error chain: #${recent.id} → #${newErrorId} (caused_by_fix)`);
252
+ break; // Link to most recent parent only
253
+ }
254
+ }
255
+ }
256
+
257
+ private buildContext(input: ReportErrorInput): string | null {
258
+ const parts: string[] = [];
259
+ if (input.taskContext) parts.push(`task: ${input.taskContext}`);
260
+ if (input.command) parts.push(`command: ${input.command}`);
261
+ if (input.workingDirectory) parts.push(`cwd: ${input.workingDirectory}`);
262
+ if (input.filePath) parts.push(`file: ${input.filePath}`);
263
+ return parts.length > 0 ? parts.join(' | ') : null;
264
+ }
164
265
  }
@@ -0,0 +1,132 @@
1
+ import { execSync } from 'node:child_process';
2
+ import type Database from 'better-sqlite3';
3
+ import type { SynapseManager } from '../synapses/synapse-manager.js';
4
+ import { getLogger } from '../utils/logger.js';
5
+
6
+ export interface GitCommitInfo {
7
+ hash: string;
8
+ message: string;
9
+ author: string;
10
+ timestamp: string;
11
+ filesChanged: number;
12
+ insertions: number;
13
+ deletions: number;
14
+ }
15
+
16
+ export class GitService {
17
+ private logger = getLogger();
18
+
19
+ constructor(
20
+ private db: Database.Database,
21
+ private synapseManager: SynapseManager,
22
+ ) {}
23
+
24
+ /**
25
+ * Get current git info for context enrichment
26
+ */
27
+ getGitContext(cwd?: string): { branch: string | null; diff: string | null; lastCommit: string | null } {
28
+ try {
29
+ const opts = cwd ? { cwd, timeout: 5000 } : { timeout: 5000 };
30
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { ...opts, encoding: 'utf8' }).trim();
31
+ const diff = execSync('git diff --stat HEAD', { ...opts, encoding: 'utf8' }).trim();
32
+ const lastCommit = execSync('git log -1 --pretty=format:"%H %s"', { ...opts, encoding: 'utf8' }).trim();
33
+ return { branch, diff: diff || null, lastCommit };
34
+ } catch {
35
+ return { branch: null, diff: null, lastCommit: null };
36
+ }
37
+ }
38
+
39
+ /**
40
+ * Store a git commit and link it to an error
41
+ */
42
+ linkErrorToCommit(errorId: number, projectId: number, commitHash: string, relationship: string = 'introduced_by'): void {
43
+ try {
44
+ // Store commit info
45
+ const commitInfo = this.getCommitInfo(commitHash);
46
+ if (commitInfo) {
47
+ this.db.prepare(`
48
+ INSERT OR IGNORE INTO git_commits (project_id, commit_hash, message, author, timestamp, files_changed, insertions, deletions)
49
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
50
+ `).run(projectId, commitInfo.hash, commitInfo.message, commitInfo.author, commitInfo.timestamp,
51
+ commitInfo.filesChanged, commitInfo.insertions, commitInfo.deletions);
52
+ }
53
+
54
+ // Link error to commit
55
+ this.db.prepare(`
56
+ INSERT OR IGNORE INTO error_commits (error_id, commit_hash, relationship)
57
+ VALUES (?, ?, ?)
58
+ `).run(errorId, commitHash, relationship);
59
+
60
+ this.logger.info(`Linked error #${errorId} to commit ${commitHash.slice(0, 8)} (${relationship})`);
61
+ } catch (err) {
62
+ this.logger.warn(`Failed to link error to commit: ${err}`);
63
+ }
64
+ }
65
+
66
+ /**
67
+ * Find which commit introduced an error
68
+ */
69
+ findIntroducingCommit(errorId: number): Array<{ commitHash: string; message: string; relationship: string }> {
70
+ const rows = this.db.prepare(`
71
+ SELECT ec.commit_hash, ec.relationship, gc.message
72
+ FROM error_commits ec
73
+ LEFT JOIN git_commits gc ON ec.commit_hash = gc.commit_hash
74
+ WHERE ec.error_id = ?
75
+ ORDER BY ec.created_at DESC
76
+ `).all(errorId) as Array<{ commit_hash: string; message: string | null; relationship: string }>;
77
+
78
+ return rows.map(r => ({
79
+ commitHash: r.commit_hash,
80
+ message: r.message ?? 'unknown',
81
+ relationship: r.relationship,
82
+ }));
83
+ }
84
+
85
+ /**
86
+ * Find errors introduced by a specific commit
87
+ */
88
+ findErrorsByCommit(commitHash: string): Array<{ errorId: number; relationship: string }> {
89
+ const rows = this.db.prepare(`
90
+ SELECT error_id, relationship FROM error_commits WHERE commit_hash = ?
91
+ `).all(commitHash) as Array<{ error_id: number; relationship: string }>;
92
+
93
+ return rows.map(r => ({ errorId: r.error_id, relationship: r.relationship }));
94
+ }
95
+
96
+ /**
97
+ * Capture current git diff for error context
98
+ */
99
+ captureDiff(cwd?: string): string | null {
100
+ try {
101
+ const opts = cwd ? { cwd, timeout: 5000, encoding: 'utf8' as const } : { timeout: 5000, encoding: 'utf8' as const };
102
+ const diff = execSync('git diff HEAD --no-color', opts).trim();
103
+ // Truncate to avoid huge diffs
104
+ return diff.length > 5000 ? diff.slice(0, 5000) + '\n... (truncated)' : diff || null;
105
+ } catch {
106
+ return null;
107
+ }
108
+ }
109
+
110
+ private getCommitInfo(hash: string, cwd?: string): GitCommitInfo | null {
111
+ try {
112
+ const opts = cwd ? { cwd, timeout: 5000, encoding: 'utf8' as const } : { timeout: 5000, encoding: 'utf8' as const };
113
+ const info = execSync(`git log -1 --pretty=format:"%H|||%s|||%an|||%aI" ${hash}`, opts).trim();
114
+ const [commitHash, message, author, timestamp] = info.split('|||');
115
+
116
+ const stat = execSync(`git diff --stat ${hash}~1..${hash}`, opts).trim();
117
+ const statMatch = stat.match(/(\d+) files? changed(?:, (\d+) insertions?\(\+\))?(?:, (\d+) deletions?\(-\))?/);
118
+
119
+ return {
120
+ hash: commitHash ?? hash,
121
+ message: message ?? '',
122
+ author: author ?? '',
123
+ timestamp: timestamp ?? new Date().toISOString(),
124
+ filesChanged: parseInt(statMatch?.[1] ?? '0', 10),
125
+ insertions: parseInt(statMatch?.[2] ?? '0', 10),
126
+ deletions: parseInt(statMatch?.[3] ?? '0', 10),
127
+ };
128
+ } catch {
129
+ return null;
130
+ }
131
+ }
132
+ }
@@ -105,6 +105,46 @@ export class PreventionService {
105
105
  });
106
106
  }
107
107
 
108
+ checkCodeForPatterns(source: string, filePath?: string): { warnings: Array<{ message: string; severity: string; ruleId?: number }> } {
109
+ const warnings: Array<{ message: string; severity: string; ruleId?: number }> = [];
110
+
111
+ // Check antipatterns against the code itself
112
+ const globalAntipatterns = this.antipatternRepo.findGlobal();
113
+ for (const ap of globalAntipatterns) {
114
+ try {
115
+ const pattern = new RegExp(ap.pattern, 'i');
116
+ if (pattern.test(source)) {
117
+ warnings.push({
118
+ message: `Code matches known error pattern: ${ap.description}${ap.suggestion ? `. Suggestion: ${ap.suggestion}` : ''}`,
119
+ severity: ap.severity,
120
+ ruleId: undefined,
121
+ });
122
+ }
123
+ } catch {
124
+ // Invalid regex, skip
125
+ }
126
+ }
127
+
128
+ // Check active rules
129
+ const rules = this.ruleRepo.findActive();
130
+ for (const rule of rules) {
131
+ try {
132
+ const pattern = new RegExp(rule.pattern, 'i');
133
+ if (pattern.test(source)) {
134
+ warnings.push({
135
+ message: `Code matches learned rule: ${rule.description ?? rule.pattern}. Action: ${rule.action}`,
136
+ severity: 'warning',
137
+ ruleId: rule.id,
138
+ });
139
+ }
140
+ } catch {
141
+ // Invalid regex, skip
142
+ }
143
+ }
144
+
145
+ return { warnings: warnings.slice(0, 5) };
146
+ }
147
+
108
148
  reportPrevention(ruleId: number, errorId: number): void {
109
149
  const rule = this.ruleRepo.getById(ruleId);
110
150
  if (rule) {
@@ -90,4 +90,9 @@ export class ResearchService {
90
90
  expireOldInsights(): number {
91
91
  return this.insightRepo.expire();
92
92
  }
93
+
94
+ rateInsight(id: number, rating: number, comment?: string): boolean {
95
+ const clamped = Math.max(-1, Math.min(1, rating)); // -1 (bad), 0 (neutral), 1 (useful)
96
+ return this.insightRepo.rate(id, clamped, comment);
97
+ }
93
98
  }
@@ -113,4 +113,62 @@ export class SolutionService {
113
113
  successRate(solutionId: number): number {
114
114
  return this.solutionRepo.successRate(solutionId);
115
115
  }
116
+
117
+ analyzeEfficiency(): {
118
+ avgDurationMs: number;
119
+ slowSolutions: Array<{ solutionId: number; avgDuration: number; description: string }>;
120
+ successRateOverall: number;
121
+ totalAttempts: number;
122
+ } {
123
+ const allSolutions = this.solutionRepo.getAll();
124
+ let totalDuration = 0;
125
+ let totalAttempts = 0;
126
+ let totalSuccessRate = 0;
127
+ let solutionCount = 0;
128
+ const solutionDurations: Array<{ solutionId: number; avgDuration: number; description: string }> = [];
129
+
130
+ for (const solution of allSolutions) {
131
+ const rate = this.solutionRepo.successRate(solution.id);
132
+ if (solution.success_count + solution.fail_count > 0) {
133
+ totalSuccessRate += rate;
134
+ solutionCount++;
135
+ }
136
+
137
+ // Check attempts for duration data
138
+ const attempts = this.solutionRepo.getAttempts(solution.id);
139
+ if (attempts.length > 0) {
140
+ let solDuration = 0;
141
+ let solAttemptCount = 0;
142
+ for (const attempt of attempts) {
143
+ if (attempt.duration_ms && attempt.duration_ms > 0) {
144
+ solDuration += attempt.duration_ms;
145
+ solAttemptCount++;
146
+ totalDuration += attempt.duration_ms;
147
+ totalAttempts++;
148
+ }
149
+ }
150
+ if (solAttemptCount > 0) {
151
+ solutionDurations.push({
152
+ solutionId: solution.id,
153
+ avgDuration: solDuration / solAttemptCount,
154
+ description: solution.description,
155
+ });
156
+ }
157
+ }
158
+ }
159
+
160
+ // Find slow solutions (above 2x average)
161
+ const avgDurationMs = totalAttempts > 0 ? totalDuration / totalAttempts : 0;
162
+ const slowSolutions = solutionDurations
163
+ .filter(s => s.avgDuration > avgDurationMs * 2)
164
+ .sort((a, b) => b.avgDuration - a.avgDuration)
165
+ .slice(0, 10);
166
+
167
+ return {
168
+ avgDurationMs,
169
+ slowSolutions,
170
+ successRateOverall: solutionCount > 0 ? totalSuccessRate / solutionCount : 0,
171
+ totalAttempts,
172
+ };
173
+ }
116
174
  }
@@ -22,6 +22,8 @@ export interface MatchingConfig {
22
22
  fingerprintFields: string[];
23
23
  similarityThreshold: number;
24
24
  maxResults: number;
25
+ crossProjectMatching: boolean;
26
+ crossProjectWeight: number;
25
27
  }
26
28
 
27
29
  export interface CodeConfig {
@@ -64,10 +66,32 @@ export interface RetentionConfig {
64
66
  insightDays: number;
65
67
  }
66
68
 
69
+ export interface ApiConfig {
70
+ port: number;
71
+ enabled: boolean;
72
+ apiKey?: string;
73
+ }
74
+
75
+ export interface McpHttpConfig {
76
+ port: number;
77
+ enabled: boolean;
78
+ }
79
+
80
+ export interface EmbeddingsConfig {
81
+ enabled: boolean;
82
+ modelName: string;
83
+ cacheDir: string;
84
+ sweepIntervalMs: number;
85
+ batchSize: number;
86
+ }
87
+
67
88
  export interface BrainConfig {
68
89
  dataDir: string;
69
90
  dbPath: string;
70
91
  ipc: IpcConfig;
92
+ api: ApiConfig;
93
+ mcpHttp: McpHttpConfig;
94
+ embeddings: EmbeddingsConfig;
71
95
  learning: LearningConfig;
72
96
  terminal: TerminalConfig;
73
97
  matching: MatchingConfig;
@@ -5,6 +5,7 @@ export type SynapseType =
5
5
  | 'causes'
6
6
  | 'similar_to'
7
7
  | 'uses_module'
8
+ | 'depends_on'
8
9
  | 'derived_from'
9
10
  | 'co_occurs'
10
11
  | 'prevents'