@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,266 +0,0 @@
1
- import type { ErrorRecord } from '../types/error.types.js';
2
- import type { ErrorRepository } from '../db/repositories/error.repository.js';
3
- import type { ProjectRepository } from '../db/repositories/project.repository.js';
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';
7
- import { parseError } from '../parsing/error-parser.js';
8
- import { generateFingerprint } from '../matching/fingerprint.js';
9
- import { matchError, type MatchResult } from '../matching/error-matcher.js';
10
- import { getEventBus } from '../utils/events.js';
11
- import { getLogger } from '../utils/logger.js';
12
-
13
- export interface ReportErrorInput {
14
- project: string;
15
- errorOutput: string;
16
- filePath?: string;
17
- terminalId?: number;
18
- taskContext?: string;
19
- workingDirectory?: string;
20
- command?: string;
21
- }
22
-
23
- export interface ErrorQueryInput {
24
- projectId?: number;
25
- resolved?: boolean;
26
- search?: string;
27
- limit?: number;
28
- }
29
-
30
- export class ErrorService {
31
- private logger = getLogger();
32
- private eventBus = getEventBus();
33
- private matchingConfig: MatchingConfig | null = null;
34
- private embeddingEngine: EmbeddingEngine | null = null;
35
-
36
- constructor(
37
- private errorRepo: ErrorRepository,
38
- private projectRepo: ProjectRepository,
39
- private synapseManager: SynapseManager,
40
- matchingConfig?: MatchingConfig,
41
- ) {
42
- this.matchingConfig = matchingConfig ?? null;
43
- }
44
-
45
- setEmbeddingEngine(engine: EmbeddingEngine): void {
46
- this.embeddingEngine = engine;
47
- }
48
-
49
- report(input: ReportErrorInput): { errorId: number; isNew: boolean; matches: MatchResult[]; crossProjectMatches?: MatchResult[] } {
50
- // 1. Ensure project exists
51
- let project = this.projectRepo.findByName(input.project);
52
- if (!project) {
53
- const id = this.projectRepo.create({ name: input.project, path: null, language: null, framework: null });
54
- project = this.projectRepo.getById(id)!;
55
- }
56
-
57
- // 2. Build context from available information
58
- const context = this.buildContext(input);
59
-
60
- // 3. Parse the error
61
- const parsed = parseError(input.errorOutput);
62
- if (!parsed) {
63
- this.logger.warn('Could not parse error output');
64
- const errorId = this.errorRepo.create({
65
- project_id: project.id,
66
- terminal_id: input.terminalId ?? null,
67
- fingerprint: '',
68
- type: 'UnknownError',
69
- message: input.errorOutput.split('\n')[0] ?? input.errorOutput,
70
- raw_output: input.errorOutput,
71
- context,
72
- file_path: input.filePath ?? null,
73
- line_number: null,
74
- column_number: null,
75
- });
76
- return { errorId, isNew: true, matches: [] };
77
- }
78
-
79
- // 4. Generate fingerprint
80
- const fingerprint = generateFingerprint(parsed.errorType, parsed.message, parsed.frames);
81
-
82
- // 5. Check for existing error with same fingerprint
83
- const existing = this.errorRepo.findByFingerprint(fingerprint);
84
- if (existing.length > 0) {
85
- const err = existing[0]!;
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
- }
91
- this.logger.info(`Known error (id=${err.id}), occurrence incremented`);
92
-
93
- // Strengthen synapse
94
- this.synapseManager.strengthen(
95
- { type: 'error', id: err.id },
96
- { type: 'project', id: project.id },
97
- 'co_occurs',
98
- );
99
-
100
- return { errorId: err.id, isNew: false, matches: [] };
101
- }
102
-
103
- // 6. Create new error record
104
- const errorId = this.errorRepo.create({
105
- project_id: project.id,
106
- terminal_id: input.terminalId ?? null,
107
- fingerprint,
108
- type: parsed.errorType,
109
- message: parsed.message,
110
- raw_output: input.errorOutput,
111
- context,
112
- file_path: parsed.sourceFile ?? input.filePath ?? null,
113
- line_number: parsed.sourceLine ?? null,
114
- column_number: null,
115
- });
116
-
117
- // 7. Create synapse: error ↔ project
118
- this.synapseManager.strengthen(
119
- { type: 'error', id: errorId },
120
- { type: 'project', id: project.id },
121
- 'co_occurs',
122
- );
123
-
124
- // 8. Find similar errors
125
- const candidates = this.errorRepo.findByProject(project.id)
126
- .filter(e => e.id !== errorId);
127
- const newError = this.errorRepo.getById(errorId)!;
128
- const matches = matchError(newError, candidates);
129
-
130
- // 9. Create similarity synapses for strong matches
131
- for (const match of matches.filter(m => m.isStrong)) {
132
- this.synapseManager.strengthen(
133
- { type: 'error', id: errorId },
134
- { type: 'error', id: match.errorId },
135
- 'similar_to',
136
- );
137
- }
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
-
155
- this.eventBus.emit('error:reported', { errorId, projectId: project.id, fingerprint });
156
- this.logger.info(`New error reported (id=${errorId}, type=${parsed.errorType})`);
157
-
158
- return { errorId, isNew: true, matches, crossProjectMatches };
159
- }
160
-
161
- query(input: ErrorQueryInput): ErrorRecord[] {
162
- if (input.search && input.search.trim()) {
163
- return this.errorRepo.search(input.search.trim());
164
- }
165
- if (input.resolved === false) {
166
- return this.errorRepo.findUnresolved(input.projectId);
167
- }
168
- if (input.projectId) {
169
- return this.errorRepo.findByProject(input.projectId);
170
- }
171
- // Default: return recent errors (most recent first)
172
- return this.errorRepo.findAll(input.limit ?? 100);
173
- }
174
-
175
- matchSimilar(errorId: number): MatchResult[] {
176
- const error = this.errorRepo.getById(errorId);
177
- if (!error) return [];
178
-
179
- const candidates = this.errorRepo.findByProject(error.project_id)
180
- .filter(e => e.id !== errorId);
181
-
182
- // Hybrid search: include vector scores if embedding engine is available
183
- const vectorScores = this.embeddingEngine?.isReady()
184
- ? this.embeddingEngine.computeErrorVectorScores(errorId, error.project_id)
185
- : undefined;
186
-
187
- return matchError(error, candidates, vectorScores);
188
- }
189
-
190
- resolve(errorId: number, solutionId?: number): void {
191
- this.errorRepo.update(errorId, {
192
- resolved: 1,
193
- resolved_at: new Date().toISOString(),
194
- });
195
-
196
- if (solutionId) {
197
- this.eventBus.emit('error:resolved', { errorId, solutionId });
198
- }
199
- }
200
-
201
- getById(id: number): ErrorRecord | undefined {
202
- return this.errorRepo.getById(id);
203
- }
204
-
205
- countSince(since: string, projectId?: number): number {
206
- return this.errorRepo.countSince(since, projectId);
207
- }
208
-
209
- getErrorChain(errorId: number): { parents: ErrorRecord[]; children: ErrorRecord[] } {
210
- return {
211
- parents: this.errorRepo.findChainParents(errorId),
212
- children: this.errorRepo.findChainChildren(errorId),
213
- };
214
- }
215
-
216
- private findCrossProjectMatches(error: ErrorRecord, currentProjectId: number): MatchResult[] {
217
- const allProjects = this.projectRepo.getAll();
218
- const otherProjects = allProjects.filter(p => p.id !== currentProjectId);
219
- const weight = this.matchingConfig?.crossProjectWeight ?? 0.7;
220
-
221
- const allMatches: MatchResult[] = [];
222
- for (const project of otherProjects) {
223
- const candidates = this.errorRepo.findByProject(project.id)
224
- .filter(e => e.resolved === 1); // Only look at resolved errors from other projects
225
- const matches = matchError(error, candidates);
226
- // Apply cross-project weight discount
227
- for (const match of matches) {
228
- match.score *= weight;
229
- match.isStrong = match.score >= 0.90;
230
- allMatches.push(match);
231
- }
232
- }
233
-
234
- return allMatches.sort((a, b) => b.score - a.score).slice(0, 5);
235
- }
236
-
237
- private detectErrorChain(newErrorId: number, projectId: number): void {
238
- // Look for recent unresolved errors in the same project (last 10 minutes)
239
- const tenMinutesAgo = new Date(Date.now() - 10 * 60 * 1000).toISOString();
240
- const recentErrors = this.errorRepo.findRecentByProject(projectId, tenMinutesAgo, 5);
241
-
242
- for (const recent of recentErrors) {
243
- if (recent.id === newErrorId) continue;
244
- if (recent.resolved === 0) {
245
- // This new error appeared while trying to fix a recent error → chain
246
- this.errorRepo.createChain(recent.id, newErrorId, 'caused_by_fix');
247
- this.synapseManager.strengthen(
248
- { type: 'error', id: recent.id },
249
- { type: 'error', id: newErrorId },
250
- 'causes',
251
- );
252
- this.logger.info(`Error chain: #${recent.id} → #${newErrorId} (caused_by_fix)`);
253
- break; // Link to most recent parent only
254
- }
255
- }
256
- }
257
-
258
- private buildContext(input: ReportErrorInput): string | null {
259
- const parts: string[] = [];
260
- if (input.taskContext) parts.push(`task: ${input.taskContext}`);
261
- if (input.command) parts.push(`command: ${input.command}`);
262
- if (input.workingDirectory) parts.push(`cwd: ${input.workingDirectory}`);
263
- if (input.filePath) parts.push(`file: ${input.filePath}`);
264
- return parts.length > 0 ? parts.join(' | ') : null;
265
- }
266
- }
@@ -1,132 +0,0 @@
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
- }
@@ -1,41 +0,0 @@
1
- import type { NotificationRepository, NotificationRecord } from '../db/repositories/notification.repository.js';
2
- import { getLogger } from '../utils/logger.js';
3
-
4
- export interface CreateNotificationInput {
5
- type: string;
6
- title: string;
7
- message: string;
8
- priority?: number;
9
- projectId?: number;
10
- }
11
-
12
- export class NotificationService {
13
- private logger = getLogger();
14
-
15
- constructor(private notificationRepo: NotificationRepository) {}
16
-
17
- create(input: CreateNotificationInput): number {
18
- const id = this.notificationRepo.create({
19
- type: input.type,
20
- title: input.title,
21
- message: input.message,
22
- priority: input.priority ?? 0,
23
- project_id: input.projectId ?? null,
24
- });
25
-
26
- this.logger.info(`Notification created (id=${id}, type=${input.type})`);
27
- return id;
28
- }
29
-
30
- list(projectId?: number): NotificationRecord[] {
31
- return this.notificationRepo.findUnacknowledged(projectId);
32
- }
33
-
34
- acknowledge(id: number): void {
35
- this.notificationRepo.acknowledge(id);
36
- }
37
-
38
- getById(id: number): NotificationRecord | undefined {
39
- return this.notificationRepo.getById(id);
40
- }
41
- }
@@ -1,159 +0,0 @@
1
- import type { RuleRepository } from '../db/repositories/rule.repository.js';
2
- import type { AntipatternRepository } from '../db/repositories/antipattern.repository.js';
3
- import type { SynapseManager } from '../synapses/synapse-manager.js';
4
- import { getLogger } from '../utils/logger.js';
5
-
6
- export interface RuleCheckResult {
7
- matched: boolean;
8
- ruleId: number;
9
- action: string;
10
- description: string | null;
11
- confidence: number;
12
- }
13
-
14
- export interface AntipatternCheckResult {
15
- matched: boolean;
16
- antipatternId: number;
17
- pattern: string;
18
- description: string;
19
- severity: string;
20
- suggestion: string | null;
21
- }
22
-
23
- export class PreventionService {
24
- private logger = getLogger();
25
-
26
- constructor(
27
- private ruleRepo: RuleRepository,
28
- private antipatternRepo: AntipatternRepository,
29
- private synapseManager: SynapseManager,
30
- ) {}
31
-
32
- checkRules(errorType: string, message: string, projectId?: number): RuleCheckResult[] {
33
- const rules = this.ruleRepo.findActive(projectId);
34
- const results: RuleCheckResult[] = [];
35
-
36
- for (const rule of rules) {
37
- try {
38
- const pattern = new RegExp(rule.pattern, 'i');
39
- const input = `${errorType}: ${message}`;
40
-
41
- if (pattern.test(input)) {
42
- results.push({
43
- matched: true,
44
- ruleId: rule.id,
45
- action: rule.action,
46
- description: rule.description,
47
- confidence: rule.confidence,
48
- });
49
-
50
- this.logger.debug(`Rule ${rule.id} matched: ${rule.pattern}`);
51
- }
52
- } catch {
53
- // Invalid regex in rule pattern, skip
54
- this.logger.warn(`Invalid regex in rule ${rule.id}: ${rule.pattern}`);
55
- }
56
- }
57
-
58
- return results.sort((a, b) => b.confidence - a.confidence);
59
- }
60
-
61
- checkAntipatterns(errorType: string, message: string, projectId?: number): AntipatternCheckResult[] {
62
- const antipatterns = projectId
63
- ? [...this.antipatternRepo.findByProject(projectId), ...this.antipatternRepo.findGlobal()]
64
- : this.antipatternRepo.findGlobal();
65
-
66
- const results: AntipatternCheckResult[] = [];
67
- const input = `${errorType}: ${message}`;
68
-
69
- for (const ap of antipatterns) {
70
- try {
71
- const pattern = new RegExp(ap.pattern, 'i');
72
- if (pattern.test(input)) {
73
- results.push({
74
- matched: true,
75
- antipatternId: ap.id,
76
- pattern: ap.pattern,
77
- description: ap.description,
78
- severity: ap.severity,
79
- suggestion: ap.suggestion,
80
- });
81
- }
82
- } catch {
83
- this.logger.warn(`Invalid regex in antipattern ${ap.id}: ${ap.pattern}`);
84
- }
85
- }
86
-
87
- return results;
88
- }
89
-
90
- createRule(data: {
91
- pattern: string;
92
- action: string;
93
- description?: string;
94
- confidence?: number;
95
- projectId?: number;
96
- }): number {
97
- return this.ruleRepo.create({
98
- pattern: data.pattern,
99
- action: data.action,
100
- description: data.description ?? null,
101
- confidence: data.confidence ?? 0.5,
102
- occurrences: 0,
103
- active: 1,
104
- project_id: data.projectId ?? null,
105
- });
106
- }
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
-
148
- reportPrevention(ruleId: number, errorId: number): void {
149
- const rule = this.ruleRepo.getById(ruleId);
150
- if (rule) {
151
- this.ruleRepo.update(ruleId, { occurrences: rule.occurrences + 1 });
152
- this.synapseManager.strengthen(
153
- { type: 'rule', id: ruleId },
154
- { type: 'error', id: errorId },
155
- 'prevents',
156
- );
157
- }
158
- }
159
- }
@@ -1,98 +0,0 @@
1
- import type { InsightRecord } from '../types/research.types.js';
2
- import type { InsightRepository } from '../db/repositories/insight.repository.js';
3
- import type { ErrorRepository } from '../db/repositories/error.repository.js';
4
- import type { SynapseManager } from '../synapses/synapse-manager.js';
5
- import { getLogger } from '../utils/logger.js';
6
-
7
- export interface InsightQuery {
8
- projectId?: number;
9
- type?: string;
10
- activeOnly?: boolean;
11
- limit?: number;
12
- }
13
-
14
- export interface TrendResult {
15
- errorType: string;
16
- count: number;
17
- direction: 'increasing' | 'decreasing' | 'stable';
18
- period: string;
19
- }
20
-
21
- export class ResearchService {
22
- private logger = getLogger();
23
-
24
- constructor(
25
- private insightRepo: InsightRepository,
26
- private errorRepo: ErrorRepository,
27
- private synapseManager: SynapseManager,
28
- ) {}
29
-
30
- getInsights(query: InsightQuery): InsightRecord[] {
31
- if (query.type) {
32
- return this.insightRepo.findByType(query.type);
33
- }
34
- if (query.activeOnly !== false) {
35
- return this.insightRepo.findActive(query.projectId);
36
- }
37
- return this.insightRepo.findActive(query.projectId);
38
- }
39
-
40
- createInsight(data: {
41
- type: string;
42
- title: string;
43
- description: string;
44
- evidence?: string;
45
- priority?: number;
46
- projectId?: number;
47
- expiresInDays?: number;
48
- }): number {
49
- const expiresAt = data.expiresInDays
50
- ? new Date(Date.now() + data.expiresInDays * 86400000).toISOString()
51
- : null;
52
-
53
- const id = this.insightRepo.create({
54
- type: data.type as InsightRecord['type'],
55
- title: data.title,
56
- description: data.description,
57
- evidence: data.evidence ?? '[]',
58
- priority: data.priority ?? 0,
59
- project_id: data.projectId ?? null,
60
- active: 1,
61
- expires_at: expiresAt,
62
- });
63
-
64
- this.logger.info(`Insight created (id=${id}, type=${data.type})`);
65
- return id;
66
- }
67
-
68
- getTrends(projectId?: number, windowDays: number = 7): TrendResult[] {
69
- const now = new Date();
70
- const currentStart = new Date(now.getTime() - windowDays * 86400000).toISOString();
71
- const previousStart = new Date(now.getTime() - windowDays * 2 * 86400000).toISOString();
72
-
73
- const currentCount = this.errorRepo.countSince(currentStart, projectId);
74
- const previousCount = this.errorRepo.countSince(previousStart, projectId) - currentCount;
75
-
76
- const direction = currentCount > previousCount * 1.2
77
- ? 'increasing' as const
78
- : currentCount < previousCount * 0.8
79
- ? 'decreasing' as const
80
- : 'stable' as const;
81
-
82
- return [{
83
- errorType: 'all',
84
- count: currentCount,
85
- direction,
86
- period: `${windowDays}d`,
87
- }];
88
- }
89
-
90
- expireOldInsights(): number {
91
- return this.insightRepo.expire();
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
- }
98
- }