neuronlayer 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/CONTRIBUTING.md +127 -0
  2. package/LICENSE +21 -0
  3. package/README.md +305 -0
  4. package/dist/index.js +38016 -0
  5. package/esbuild.config.js +26 -0
  6. package/package.json +63 -0
  7. package/src/cli/commands.ts +382 -0
  8. package/src/core/adr-exporter.ts +253 -0
  9. package/src/core/architecture/architecture-enforcement.ts +228 -0
  10. package/src/core/architecture/duplicate-detector.ts +288 -0
  11. package/src/core/architecture/index.ts +6 -0
  12. package/src/core/architecture/pattern-learner.ts +306 -0
  13. package/src/core/architecture/pattern-library.ts +403 -0
  14. package/src/core/architecture/pattern-validator.ts +324 -0
  15. package/src/core/change-intelligence/bug-correlator.ts +444 -0
  16. package/src/core/change-intelligence/change-intelligence.ts +221 -0
  17. package/src/core/change-intelligence/change-tracker.ts +334 -0
  18. package/src/core/change-intelligence/fix-suggester.ts +340 -0
  19. package/src/core/change-intelligence/index.ts +5 -0
  20. package/src/core/code-verifier.ts +843 -0
  21. package/src/core/confidence/confidence-scorer.ts +251 -0
  22. package/src/core/confidence/conflict-checker.ts +289 -0
  23. package/src/core/confidence/index.ts +5 -0
  24. package/src/core/confidence/source-tracker.ts +263 -0
  25. package/src/core/confidence/warning-detector.ts +241 -0
  26. package/src/core/context-rot/compaction.ts +284 -0
  27. package/src/core/context-rot/context-health.ts +243 -0
  28. package/src/core/context-rot/context-rot-prevention.ts +213 -0
  29. package/src/core/context-rot/critical-context.ts +221 -0
  30. package/src/core/context-rot/drift-detector.ts +255 -0
  31. package/src/core/context-rot/index.ts +7 -0
  32. package/src/core/context.ts +263 -0
  33. package/src/core/decision-extractor.ts +339 -0
  34. package/src/core/decisions.ts +69 -0
  35. package/src/core/deja-vu.ts +421 -0
  36. package/src/core/engine.ts +1455 -0
  37. package/src/core/feature-context.ts +726 -0
  38. package/src/core/ghost-mode.ts +412 -0
  39. package/src/core/learning.ts +485 -0
  40. package/src/core/living-docs/activity-tracker.ts +296 -0
  41. package/src/core/living-docs/architecture-generator.ts +428 -0
  42. package/src/core/living-docs/changelog-generator.ts +348 -0
  43. package/src/core/living-docs/component-generator.ts +230 -0
  44. package/src/core/living-docs/doc-engine.ts +110 -0
  45. package/src/core/living-docs/doc-validator.ts +282 -0
  46. package/src/core/living-docs/index.ts +8 -0
  47. package/src/core/project-manager.ts +297 -0
  48. package/src/core/summarizer.ts +267 -0
  49. package/src/core/test-awareness/change-validator.ts +499 -0
  50. package/src/core/test-awareness/index.ts +5 -0
  51. package/src/index.ts +49 -0
  52. package/src/indexing/ast.ts +563 -0
  53. package/src/indexing/embeddings.ts +85 -0
  54. package/src/indexing/indexer.ts +245 -0
  55. package/src/indexing/watcher.ts +78 -0
  56. package/src/server/gateways/aggregator.ts +374 -0
  57. package/src/server/gateways/index.ts +473 -0
  58. package/src/server/gateways/memory-ghost.ts +343 -0
  59. package/src/server/gateways/memory-query.ts +452 -0
  60. package/src/server/gateways/memory-record.ts +346 -0
  61. package/src/server/gateways/memory-review.ts +410 -0
  62. package/src/server/gateways/memory-status.ts +517 -0
  63. package/src/server/gateways/memory-verify.ts +392 -0
  64. package/src/server/gateways/router.ts +434 -0
  65. package/src/server/gateways/types.ts +610 -0
  66. package/src/server/mcp.ts +154 -0
  67. package/src/server/resources.ts +85 -0
  68. package/src/server/tools.ts +2261 -0
  69. package/src/storage/database.ts +262 -0
  70. package/src/storage/tier1.ts +135 -0
  71. package/src/storage/tier2.ts +764 -0
  72. package/src/storage/tier3.ts +123 -0
  73. package/src/types/documentation.ts +619 -0
  74. package/src/types/index.ts +222 -0
  75. package/src/utils/config.ts +193 -0
  76. package/src/utils/files.ts +117 -0
  77. package/src/utils/time.ts +37 -0
  78. package/src/utils/tokens.ts +52 -0
@@ -0,0 +1,444 @@
1
+ import { randomUUID } from 'crypto';
2
+ import type Database from 'better-sqlite3';
3
+ import type { Tier2Storage } from '../../storage/tier2.js';
4
+ import type { EmbeddingGenerator } from '../../indexing/embeddings.js';
5
+ import type { Change, Bug, PastBug, Diagnosis } from '../../types/documentation.js';
6
+ import type { ChangeTracker } from './change-tracker.js';
7
+
8
+ // Common error patterns and their likely causes
9
+ const ERROR_PATTERNS = [
10
+ {
11
+ pattern: /cannot read (?:property |properties of )['"]?(\w+)['"]? of (?:undefined|null)/i,
12
+ keywords: ['null check', 'undefined', 'optional chaining', '?.'],
13
+ likelyCause: 'Missing null/undefined check'
14
+ },
15
+ {
16
+ pattern: /(\w+) is not defined/i,
17
+ keywords: ['import', 'require', 'declaration'],
18
+ likelyCause: 'Missing import or variable declaration'
19
+ },
20
+ {
21
+ pattern: /(\w+) is not a function/i,
22
+ keywords: ['function', 'method', 'call', '()'],
23
+ likelyCause: 'Function doesn\'t exist or wrong type'
24
+ },
25
+ {
26
+ pattern: /unexpected token/i,
27
+ keywords: ['syntax', 'parse', 'JSON'],
28
+ likelyCause: 'Syntax error or malformed data'
29
+ },
30
+ {
31
+ pattern: /timeout|timed out/i,
32
+ keywords: ['timeout', 'async', 'await', 'connection'],
33
+ likelyCause: 'Slow operation or connection issue'
34
+ },
35
+ {
36
+ pattern: /ECONNREFUSED|connection refused/i,
37
+ keywords: ['connection', 'port', 'server', 'database'],
38
+ likelyCause: 'Service not running or wrong port'
39
+ },
40
+ {
41
+ pattern: /out of memory|heap/i,
42
+ keywords: ['memory', 'leak', 'buffer', 'array'],
43
+ likelyCause: 'Memory leak or large data processing'
44
+ },
45
+ {
46
+ pattern: /permission denied|EACCES/i,
47
+ keywords: ['permission', 'access', 'chmod', 'sudo'],
48
+ likelyCause: 'Missing permissions'
49
+ },
50
+ {
51
+ pattern: /module not found|cannot find module/i,
52
+ keywords: ['import', 'require', 'package', 'node_modules'],
53
+ likelyCause: 'Missing dependency or wrong import path'
54
+ },
55
+ {
56
+ pattern: /type.*is not assignable/i,
57
+ keywords: ['type', 'interface', 'TypeScript'],
58
+ likelyCause: 'Type mismatch'
59
+ }
60
+ ];
61
+
62
+ export class BugCorrelator {
63
+ private db: Database.Database;
64
+ private changeTracker: ChangeTracker;
65
+ private tier2: Tier2Storage;
66
+ private embeddingGenerator: EmbeddingGenerator;
67
+
68
+ constructor(
69
+ db: Database.Database,
70
+ changeTracker: ChangeTracker,
71
+ tier2: Tier2Storage,
72
+ embeddingGenerator: EmbeddingGenerator
73
+ ) {
74
+ this.db = db;
75
+ this.changeTracker = changeTracker;
76
+ this.tier2 = tier2;
77
+ this.embeddingGenerator = embeddingGenerator;
78
+ this.ensureTable();
79
+ }
80
+
81
+ private ensureTable(): void {
82
+ this.db.exec(`
83
+ CREATE TABLE IF NOT EXISTS bug_history (
84
+ id TEXT PRIMARY KEY,
85
+ error TEXT NOT NULL,
86
+ stack_trace TEXT,
87
+ file TEXT,
88
+ line INTEGER,
89
+ timestamp INTEGER NOT NULL,
90
+ status TEXT DEFAULT 'open',
91
+ related_changes TEXT,
92
+ fixed_by TEXT,
93
+ fixed_at INTEGER,
94
+ fix_diff TEXT,
95
+ cause TEXT
96
+ );
97
+ CREATE INDEX IF NOT EXISTS idx_bug_timestamp ON bug_history(timestamp);
98
+ CREATE INDEX IF NOT EXISTS idx_bug_status ON bug_history(status);
99
+
100
+ CREATE VIRTUAL TABLE IF NOT EXISTS bug_fts USING fts5(
101
+ error,
102
+ stack_trace,
103
+ cause,
104
+ content='bug_history',
105
+ content_rowid='rowid'
106
+ );
107
+ `);
108
+ }
109
+
110
+ // Record a bug
111
+ recordBug(error: string, options?: {
112
+ stackTrace?: string;
113
+ file?: string;
114
+ line?: number;
115
+ relatedChanges?: string[];
116
+ }): Bug {
117
+ const id = randomUUID();
118
+ const timestamp = Math.floor(Date.now() / 1000);
119
+
120
+ this.db.prepare(`
121
+ INSERT INTO bug_history (id, error, stack_trace, file, line, timestamp, status, related_changes)
122
+ VALUES (?, ?, ?, ?, ?, ?, 'open', ?)
123
+ `).run(
124
+ id,
125
+ error,
126
+ options?.stackTrace || null,
127
+ options?.file || null,
128
+ options?.line || null,
129
+ timestamp,
130
+ JSON.stringify(options?.relatedChanges || [])
131
+ );
132
+
133
+ return {
134
+ id,
135
+ error,
136
+ stackTrace: options?.stackTrace,
137
+ file: options?.file,
138
+ line: options?.line,
139
+ timestamp: new Date(timestamp * 1000),
140
+ status: 'open',
141
+ relatedChanges: options?.relatedChanges || []
142
+ };
143
+ }
144
+
145
+ // Record a fix for a bug
146
+ recordFix(bugId: string, fixDiff: string, cause?: string): boolean {
147
+ const result = this.db.prepare(`
148
+ UPDATE bug_history
149
+ SET status = 'fixed', fixed_at = ?, fix_diff = ?, cause = ?
150
+ WHERE id = ?
151
+ `).run(
152
+ Math.floor(Date.now() / 1000),
153
+ fixDiff,
154
+ cause || null,
155
+ bugId
156
+ );
157
+
158
+ return result.changes > 0;
159
+ }
160
+
161
+ // Diagnose a bug
162
+ diagnoseBug(error: string, options?: { file?: string; line?: number }): Diagnosis {
163
+ // 1. Extract keywords from error
164
+ const keywords = this.extractKeywords(error);
165
+
166
+ // 2. Find recent changes that might be related
167
+ const recentChanges = this.changeTracker.getRecentChanges(48); // Last 48 hours
168
+ const relevantChanges = this.findRelevantChanges(recentChanges, keywords, options?.file);
169
+
170
+ // 3. Score and rank changes
171
+ const scoredChanges = this.scoreChanges(relevantChanges, error, keywords, options);
172
+
173
+ // 4. Find similar past bugs
174
+ const pastBugs = this.findSimilarBugs(error);
175
+
176
+ // 5. Generate diagnosis
177
+ const likelyCause = scoredChanges.length > 0 ? scoredChanges[0].change : null;
178
+ const confidence = scoredChanges.length > 0 ? scoredChanges[0].score : 0;
179
+
180
+ // 6. Generate reasoning
181
+ const reasoning = this.generateReasoning(error, likelyCause, pastBugs, keywords);
182
+
183
+ // 7. Suggest fix if we have past fixes
184
+ const suggestedFix = this.getSuggestedFix(pastBugs, error);
185
+
186
+ return {
187
+ likelyCause,
188
+ confidence: Math.round(confidence),
189
+ relatedChanges: scoredChanges.slice(1, 5).map(s => s.change),
190
+ pastSimilarBugs: pastBugs.slice(0, 5),
191
+ suggestedFix,
192
+ reasoning
193
+ };
194
+ }
195
+
196
+ private extractKeywords(error: string): string[] {
197
+ const keywords: string[] = [];
198
+
199
+ // Extract from error patterns
200
+ for (const { pattern, keywords: patternKeywords } of ERROR_PATTERNS) {
201
+ if (pattern.test(error)) {
202
+ keywords.push(...patternKeywords);
203
+ }
204
+ }
205
+
206
+ // Extract identifiers (variable/function names)
207
+ const identifiers = error.match(/['"`](\w+)['"`]/g);
208
+ if (identifiers) {
209
+ keywords.push(...identifiers.map(i => i.replace(/['"`]/g, '')));
210
+ }
211
+
212
+ // Extract file paths
213
+ const paths = error.match(/[\w\-./]+\.(ts|js|tsx|jsx)/g);
214
+ if (paths) {
215
+ keywords.push(...paths);
216
+ }
217
+
218
+ // Extract line numbers
219
+ const lineNums = error.match(/line\s*(\d+)/gi);
220
+ if (lineNums) {
221
+ keywords.push(...lineNums);
222
+ }
223
+
224
+ // Add common error-related words
225
+ const words = error.toLowerCase().split(/\s+/);
226
+ const significantWords = words.filter(w =>
227
+ w.length > 3 &&
228
+ !['error', 'the', 'and', 'for', 'with', 'from'].includes(w)
229
+ );
230
+ keywords.push(...significantWords.slice(0, 10));
231
+
232
+ return [...new Set(keywords)];
233
+ }
234
+
235
+ private findRelevantChanges(changes: Change[], keywords: string[], file?: string): Change[] {
236
+ return changes.filter(change => {
237
+ // Priority 1: File match
238
+ if (file && change.file.includes(file)) {
239
+ return true;
240
+ }
241
+
242
+ // Priority 2: Keyword in diff or file
243
+ const changeText = `${change.file} ${change.diff} ${change.commitMessage}`.toLowerCase();
244
+ return keywords.some(k => changeText.includes(k.toLowerCase()));
245
+ });
246
+ }
247
+
248
+ private scoreChanges(
249
+ changes: Change[],
250
+ error: string,
251
+ keywords: string[],
252
+ options?: { file?: string; line?: number }
253
+ ): Array<{ change: Change; score: number }> {
254
+ return changes.map(change => {
255
+ let score = 0;
256
+
257
+ // File match (high weight)
258
+ if (options?.file && change.file.includes(options.file)) {
259
+ score += 40;
260
+ }
261
+
262
+ // Recency (more recent = higher score)
263
+ const hoursAgo = (Date.now() - change.timestamp.getTime()) / (1000 * 60 * 60);
264
+ if (hoursAgo < 2) score += 30;
265
+ else if (hoursAgo < 6) score += 20;
266
+ else if (hoursAgo < 24) score += 10;
267
+
268
+ // Keyword matches in diff
269
+ const diffLower = change.diff.toLowerCase();
270
+ let keywordMatches = 0;
271
+ for (const keyword of keywords) {
272
+ if (diffLower.includes(keyword.toLowerCase())) {
273
+ keywordMatches++;
274
+ }
275
+ }
276
+ score += Math.min(keywordMatches * 5, 20);
277
+
278
+ // Error pattern match
279
+ for (const { pattern, likelyCause } of ERROR_PATTERNS) {
280
+ if (pattern.test(error)) {
281
+ // Check if diff contains related changes
282
+ if (diffLower.includes('null') || diffLower.includes('undefined') ||
283
+ diffLower.includes('?.') || diffLower.includes('if (')) {
284
+ score += 15;
285
+ }
286
+ }
287
+ }
288
+
289
+ // Deletion score (deletions often cause bugs)
290
+ if (change.linesRemoved > change.linesAdded) {
291
+ score += 10;
292
+ }
293
+
294
+ return { change, score };
295
+ }).sort((a, b) => b.score - a.score);
296
+ }
297
+
298
+ // Find similar bugs from history
299
+ findSimilarBugs(error: string, limit: number = 5): PastBug[] {
300
+ try {
301
+ // Simple text search
302
+ const keywords = error.split(/\s+/)
303
+ .filter(w => w.length > 3)
304
+ .slice(0, 5)
305
+ .join(' OR ');
306
+
307
+ if (!keywords) return [];
308
+
309
+ const rows = this.db.prepare(`
310
+ SELECT id, error, cause, fix_diff, file, timestamp, status
311
+ FROM bug_history
312
+ WHERE status = 'fixed' AND (
313
+ error LIKE ? OR
314
+ error LIKE ? OR
315
+ error LIKE ?
316
+ )
317
+ ORDER BY timestamp DESC
318
+ LIMIT ?
319
+ `).all(
320
+ `%${error.slice(0, 30)}%`,
321
+ `%${error.split(' ')[0]}%`,
322
+ `%${error.split(':')[0]}%`,
323
+ limit
324
+ ) as Array<{
325
+ id: string;
326
+ error: string;
327
+ cause: string | null;
328
+ fix_diff: string | null;
329
+ file: string | null;
330
+ timestamp: number;
331
+ status: string;
332
+ }>;
333
+
334
+ return rows.map(row => ({
335
+ id: row.id,
336
+ error: row.error,
337
+ cause: row.cause || undefined,
338
+ fix: row.cause || undefined,
339
+ fixDiff: row.fix_diff || undefined,
340
+ file: row.file || undefined,
341
+ date: new Date(row.timestamp * 1000),
342
+ similarity: this.calculateSimilarity(error, row.error)
343
+ })).filter(bug => bug.similarity > 30);
344
+ } catch {
345
+ return [];
346
+ }
347
+ }
348
+
349
+ private calculateSimilarity(error1: string, error2: string): number {
350
+ const words1 = new Set(error1.toLowerCase().split(/\s+/));
351
+ const words2 = new Set(error2.toLowerCase().split(/\s+/));
352
+
353
+ let matches = 0;
354
+ for (const word of words1) {
355
+ if (words2.has(word)) matches++;
356
+ }
357
+
358
+ const total = Math.max(words1.size, words2.size);
359
+ return total > 0 ? Math.round((matches / total) * 100) : 0;
360
+ }
361
+
362
+ private generateReasoning(
363
+ error: string,
364
+ likelyCause: Change | null,
365
+ pastBugs: PastBug[],
366
+ keywords: string[]
367
+ ): string {
368
+ const parts: string[] = [];
369
+
370
+ if (likelyCause) {
371
+ const hoursAgo = Math.round((Date.now() - likelyCause.timestamp.getTime()) / (1000 * 60 * 60));
372
+ parts.push(`Found likely cause in ${likelyCause.file} (changed ${hoursAgo}h ago)`);
373
+
374
+ // Check for specific patterns
375
+ const diff = likelyCause.diff.toLowerCase();
376
+ if (diff.includes('-') && (diff.includes('if') || diff.includes('null') || diff.includes('?.'))) {
377
+ parts.push('A null/undefined check may have been removed');
378
+ }
379
+ if (likelyCause.linesRemoved > likelyCause.linesAdded) {
380
+ parts.push('Code was removed which might have broken functionality');
381
+ }
382
+ } else {
383
+ parts.push('Could not identify a specific recent change as the cause');
384
+ }
385
+
386
+ if (pastBugs.length > 0) {
387
+ parts.push(`Found ${pastBugs.length} similar bug(s) in history`);
388
+ if (pastBugs[0].fix) {
389
+ parts.push(`Previous fix: ${pastBugs[0].fix}`);
390
+ }
391
+ }
392
+
393
+ // Add error pattern insight
394
+ for (const { pattern, likelyCause: cause } of ERROR_PATTERNS) {
395
+ if (pattern.test(error)) {
396
+ parts.push(`Error type suggests: ${cause}`);
397
+ break;
398
+ }
399
+ }
400
+
401
+ return parts.join('. ');
402
+ }
403
+
404
+ private getSuggestedFix(pastBugs: PastBug[], _error: string): string | null {
405
+ if (pastBugs.length === 0) return null;
406
+
407
+ const bestMatch = pastBugs.find(bug => bug.fix || bug.fixDiff);
408
+ if (bestMatch) {
409
+ if (bestMatch.fix) return bestMatch.fix;
410
+ if (bestMatch.fixDiff) return `Apply similar fix:\n${bestMatch.fixDiff.slice(0, 200)}`;
411
+ }
412
+
413
+ return null;
414
+ }
415
+
416
+ // Get bug statistics
417
+ getBugStats(): {
418
+ total: number;
419
+ open: number;
420
+ fixed: number;
421
+ avgTimeToFix: number;
422
+ } {
423
+ const stats = this.db.prepare(`
424
+ SELECT
425
+ COUNT(*) as total,
426
+ SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END) as open,
427
+ SUM(CASE WHEN status = 'fixed' THEN 1 ELSE 0 END) as fixed,
428
+ AVG(CASE WHEN status = 'fixed' THEN fixed_at - timestamp ELSE NULL END) as avg_fix_time
429
+ FROM bug_history
430
+ `).get() as {
431
+ total: number;
432
+ open: number;
433
+ fixed: number;
434
+ avg_fix_time: number | null;
435
+ };
436
+
437
+ return {
438
+ total: stats.total,
439
+ open: stats.open,
440
+ fixed: stats.fixed,
441
+ avgTimeToFix: stats.avg_fix_time ? Math.round(stats.avg_fix_time / 3600) : 0 // hours
442
+ };
443
+ }
444
+ }
@@ -0,0 +1,221 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { Tier2Storage } from '../../storage/tier2.js';
3
+ import type { EmbeddingGenerator } from '../../indexing/embeddings.js';
4
+ import type {
5
+ Change,
6
+ ChangeQueryResult,
7
+ ChangeQueryOptions,
8
+ Diagnosis,
9
+ PastBug,
10
+ FixSuggestion,
11
+ Bug
12
+ } from '../../types/documentation.js';
13
+ import { ChangeTracker } from './change-tracker.js';
14
+ import { BugCorrelator } from './bug-correlator.js';
15
+ import { FixSuggester } from './fix-suggester.js';
16
+
17
+ export class ChangeIntelligence {
18
+ private changeTracker: ChangeTracker;
19
+ private bugCorrelator: BugCorrelator;
20
+ private fixSuggester: FixSuggester;
21
+ private initialized = false;
22
+
23
+ constructor(
24
+ projectPath: string,
25
+ db: Database.Database,
26
+ tier2: Tier2Storage,
27
+ embeddingGenerator: EmbeddingGenerator
28
+ ) {
29
+ this.changeTracker = new ChangeTracker(projectPath, db);
30
+ this.bugCorrelator = new BugCorrelator(db, this.changeTracker, tier2, embeddingGenerator);
31
+ this.fixSuggester = new FixSuggester(db, this.bugCorrelator, this.changeTracker);
32
+ }
33
+
34
+ // Initialize by syncing git history
35
+ initialize(): number {
36
+ if (this.initialized) return 0;
37
+
38
+ const synced = this.changeTracker.syncFromGit(100);
39
+ this.initialized = true;
40
+ return synced;
41
+ }
42
+
43
+ // Query what changed
44
+ whatChanged(options: ChangeQueryOptions = {}): ChangeQueryResult {
45
+ return this.changeTracker.queryChanges(options);
46
+ }
47
+
48
+ // Get changes for a specific file
49
+ whatChangedIn(file: string, limit?: number): Change[] {
50
+ return this.changeTracker.getFileChanges(file, limit);
51
+ }
52
+
53
+ // Get recent changes
54
+ getRecentChanges(hours: number = 24): Change[] {
55
+ return this.changeTracker.getRecentChanges(hours);
56
+ }
57
+
58
+ // Diagnose why something broke
59
+ whyBroke(error: string, options?: { file?: string; line?: number }): Diagnosis {
60
+ return this.bugCorrelator.diagnoseBug(error, options);
61
+ }
62
+
63
+ // Find similar bugs from history
64
+ findSimilarBugs(error: string, limit?: number): PastBug[] {
65
+ return this.bugCorrelator.findSimilarBugs(error, limit);
66
+ }
67
+
68
+ // Suggest fixes for an error
69
+ suggestFix(error: string, context?: string): FixSuggestion[] {
70
+ return this.fixSuggester.suggestFix(error, context);
71
+ }
72
+
73
+ // Record a bug for future reference
74
+ recordBug(error: string, options?: {
75
+ stackTrace?: string;
76
+ file?: string;
77
+ line?: number;
78
+ relatedChanges?: string[];
79
+ }): Bug {
80
+ return this.bugCorrelator.recordBug(error, options);
81
+ }
82
+
83
+ // Record that a bug was fixed
84
+ recordFix(bugId: string, fixDiff: string, cause?: string): boolean {
85
+ return this.bugCorrelator.recordFix(bugId, fixDiff, cause);
86
+ }
87
+
88
+ // Search changes by keyword
89
+ searchChanges(keyword: string, limit?: number): Change[] {
90
+ return this.changeTracker.searchChanges(keyword, limit);
91
+ }
92
+
93
+ // Get statistics
94
+ getStats(): {
95
+ changes: {
96
+ total: number;
97
+ last24h: number;
98
+ lastWeek: number;
99
+ };
100
+ bugs: {
101
+ total: number;
102
+ open: number;
103
+ fixed: number;
104
+ avgTimeToFix: number;
105
+ };
106
+ } {
107
+ const last24h = this.changeTracker.getRecentChanges(24);
108
+ const lastWeek = this.changeTracker.queryChanges({ since: 'last week' });
109
+ const bugStats = this.bugCorrelator.getBugStats();
110
+
111
+ return {
112
+ changes: {
113
+ total: lastWeek.changes.length,
114
+ last24h: last24h.length,
115
+ lastWeek: lastWeek.changes.length
116
+ },
117
+ bugs: bugStats
118
+ };
119
+ }
120
+
121
+ // Format diagnosis for display
122
+ static formatDiagnosis(diagnosis: Diagnosis): string {
123
+ const lines: string[] = [];
124
+
125
+ lines.push('\u{1F50D} Bug Diagnosis\n');
126
+
127
+ if (diagnosis.likelyCause) {
128
+ const change = diagnosis.likelyCause;
129
+ const hoursAgo = Math.round((Date.now() - change.timestamp.getTime()) / (1000 * 60 * 60));
130
+
131
+ lines.push(`\u{1F4CD} Likely Cause (${diagnosis.confidence}% confidence)`);
132
+ lines.push(`File: ${change.file}`);
133
+ lines.push(`Changed: ${hoursAgo}h ago by ${change.author}`);
134
+ lines.push(`Commit: ${change.commitMessage.slice(0, 50)}`);
135
+
136
+ if (change.diff) {
137
+ lines.push('\nDiff:');
138
+ lines.push(change.diff.slice(0, 300));
139
+ }
140
+ } else {
141
+ lines.push('\u{2139}\u{FE0F} Could not identify a specific cause');
142
+ }
143
+
144
+ if (diagnosis.pastSimilarBugs.length > 0) {
145
+ lines.push('\n\u{1F4A1} Similar Bugs Found');
146
+ for (const bug of diagnosis.pastSimilarBugs.slice(0, 2)) {
147
+ lines.push(`- ${bug.error.slice(0, 50)} (${bug.similarity}% similar)`);
148
+ if (bug.fix) {
149
+ lines.push(` Fix: ${bug.fix}`);
150
+ }
151
+ }
152
+ }
153
+
154
+ if (diagnosis.suggestedFix) {
155
+ lines.push('\n\u{1F527} Suggested Fix');
156
+ lines.push(diagnosis.suggestedFix);
157
+ }
158
+
159
+ lines.push('\n' + diagnosis.reasoning);
160
+
161
+ return lines.join('\n');
162
+ }
163
+
164
+ // Format changes for display
165
+ static formatChanges(result: ChangeQueryResult): string {
166
+ const lines: string[] = [];
167
+
168
+ lines.push(`\u{1F4CB} Changes: ${result.period}\n`);
169
+ lines.push(`Files Changed: ${result.totalFiles}`);
170
+ lines.push(`Lines: +${result.totalLinesAdded}, -${result.totalLinesRemoved}`);
171
+ lines.push('');
172
+
173
+ if (result.changes.length === 0) {
174
+ lines.push('No changes found in this period.');
175
+ return lines.join('\n');
176
+ }
177
+
178
+ lines.push('Key Changes:');
179
+ for (const change of result.changes.slice(0, 10)) {
180
+ const time = change.timestamp.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
181
+ lines.push(`\u251C\u2500\u2500 ${change.file} (${time})`);
182
+ lines.push(`\u2502 ${change.commitMessage.slice(0, 50)}`);
183
+ lines.push(`\u2502 +${change.linesAdded} lines, -${change.linesRemoved} lines`);
184
+ lines.push(`\u2502 Author: ${change.author}`);
185
+ }
186
+
187
+ if (result.changes.length > 10) {
188
+ lines.push(`\u2514\u2500\u2500 ${result.changes.length - 10} more changes...`);
189
+ }
190
+
191
+ return lines.join('\n');
192
+ }
193
+
194
+ // Format fix suggestions for display
195
+ static formatFixSuggestions(suggestions: FixSuggestion[]): string {
196
+ if (suggestions.length === 0) {
197
+ return 'No fix suggestions available.';
198
+ }
199
+
200
+ const lines: string[] = [];
201
+ lines.push('\u{1F527} Fix Suggestions\n');
202
+
203
+ for (let i = 0; i < suggestions.length; i++) {
204
+ const s = suggestions[i];
205
+ const icon = s.confidence >= 80 ? '\u{1F7E2}' :
206
+ s.confidence >= 60 ? '\u{1F7E1}' : '\u{1F7E0}';
207
+
208
+ lines.push(`${i + 1}. ${icon} ${s.fix} (${s.confidence}% confidence)`);
209
+ lines.push(` Reason: ${s.reason}`);
210
+ if (s.diff) {
211
+ lines.push(` ${s.diff}`);
212
+ }
213
+ if (s.pastFix) {
214
+ lines.push(` Based on fix from ${s.pastFix.date.toLocaleDateString()}`);
215
+ }
216
+ lines.push('');
217
+ }
218
+
219
+ return lines.join('\n');
220
+ }
221
+ }