neuronlayer 0.1.9 → 0.2.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.
Potentially problematic release.
This version of neuronlayer might be problematic. Click here for more details.
- package/README.md +3 -2
- package/dist/index.js +172 -90
- package/dist/index.js.map +7 -0
- package/package.json +6 -1
- package/esbuild.config.js +0 -26
- package/src/cli/commands.ts +0 -573
- package/src/core/adr-exporter.ts +0 -253
- package/src/core/architecture/architecture-enforcement.ts +0 -228
- package/src/core/architecture/duplicate-detector.ts +0 -288
- package/src/core/architecture/index.ts +0 -6
- package/src/core/architecture/pattern-learner.ts +0 -306
- package/src/core/architecture/pattern-library.ts +0 -403
- package/src/core/architecture/pattern-validator.ts +0 -324
- package/src/core/change-intelligence/bug-correlator.ts +0 -544
- package/src/core/change-intelligence/change-intelligence.ts +0 -264
- package/src/core/change-intelligence/change-tracker.ts +0 -334
- package/src/core/change-intelligence/fix-suggester.ts +0 -340
- package/src/core/change-intelligence/index.ts +0 -5
- package/src/core/code-verifier.ts +0 -843
- package/src/core/confidence/confidence-scorer.ts +0 -251
- package/src/core/confidence/conflict-checker.ts +0 -289
- package/src/core/confidence/index.ts +0 -5
- package/src/core/confidence/source-tracker.ts +0 -263
- package/src/core/confidence/warning-detector.ts +0 -241
- package/src/core/context-rot/compaction.ts +0 -284
- package/src/core/context-rot/context-health.ts +0 -243
- package/src/core/context-rot/context-rot-prevention.ts +0 -213
- package/src/core/context-rot/critical-context.ts +0 -221
- package/src/core/context-rot/drift-detector.ts +0 -255
- package/src/core/context-rot/index.ts +0 -7
- package/src/core/context.ts +0 -263
- package/src/core/decision-extractor.ts +0 -339
- package/src/core/decisions.ts +0 -69
- package/src/core/deja-vu.ts +0 -421
- package/src/core/engine.ts +0 -1646
- package/src/core/feature-context.ts +0 -726
- package/src/core/ghost-mode.ts +0 -465
- package/src/core/learning.ts +0 -519
- package/src/core/living-docs/activity-tracker.ts +0 -296
- package/src/core/living-docs/architecture-generator.ts +0 -428
- package/src/core/living-docs/changelog-generator.ts +0 -348
- package/src/core/living-docs/component-generator.ts +0 -230
- package/src/core/living-docs/doc-engine.ts +0 -134
- package/src/core/living-docs/doc-validator.ts +0 -282
- package/src/core/living-docs/index.ts +0 -8
- package/src/core/project-manager.ts +0 -301
- package/src/core/refresh/activity-gate.ts +0 -256
- package/src/core/refresh/git-staleness-checker.ts +0 -108
- package/src/core/refresh/index.ts +0 -27
- package/src/core/summarizer.ts +0 -290
- package/src/core/test-awareness/change-validator.ts +0 -499
- package/src/core/test-awareness/index.ts +0 -5
- package/src/index.ts +0 -90
- package/src/indexing/ast.ts +0 -868
- package/src/indexing/embeddings.ts +0 -85
- package/src/indexing/indexer.ts +0 -270
- package/src/indexing/watcher.ts +0 -78
- package/src/server/gateways/aggregator.ts +0 -374
- package/src/server/gateways/index.ts +0 -473
- package/src/server/gateways/memory-ghost.ts +0 -343
- package/src/server/gateways/memory-query.ts +0 -452
- package/src/server/gateways/memory-record.ts +0 -346
- package/src/server/gateways/memory-review.ts +0 -410
- package/src/server/gateways/memory-status.ts +0 -517
- package/src/server/gateways/memory-verify.ts +0 -392
- package/src/server/gateways/router.ts +0 -434
- package/src/server/gateways/types.ts +0 -610
- package/src/server/http.ts +0 -228
- package/src/server/mcp.ts +0 -154
- package/src/server/resources.ts +0 -85
- package/src/server/tools.ts +0 -2460
- package/src/storage/database.ts +0 -271
- package/src/storage/tier1.ts +0 -135
- package/src/storage/tier2.ts +0 -972
- package/src/storage/tier3.ts +0 -123
- package/src/types/documentation.ts +0 -619
- package/src/types/index.ts +0 -222
- package/src/utils/config.ts +0 -194
- package/src/utils/files.ts +0 -117
- package/src/utils/time.ts +0 -37
- package/src/utils/tokens.ts +0 -52
|
@@ -1,544 +0,0 @@
|
|
|
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
|
-
/**
|
|
417
|
-
* Scan git history for fix commits and auto-record as bugs
|
|
418
|
-
* Looks for commits with "fix:", "bugfix:", "hotfix:" prefixes or "fixes #" references
|
|
419
|
-
*/
|
|
420
|
-
scanForBugFixes(): number {
|
|
421
|
-
try {
|
|
422
|
-
// Get commits from change_history that look like bug fixes
|
|
423
|
-
const fixCommits = this.db.prepare(`
|
|
424
|
-
SELECT DISTINCT commit_hash, commit_message, file, diff, timestamp
|
|
425
|
-
FROM change_history
|
|
426
|
-
WHERE (
|
|
427
|
-
LOWER(commit_message) LIKE 'fix:%' OR
|
|
428
|
-
LOWER(commit_message) LIKE 'fix(%' OR
|
|
429
|
-
LOWER(commit_message) LIKE 'bugfix:%' OR
|
|
430
|
-
LOWER(commit_message) LIKE 'hotfix:%' OR
|
|
431
|
-
LOWER(commit_message) LIKE '%fixes #%' OR
|
|
432
|
-
LOWER(commit_message) LIKE '%fixed #%' OR
|
|
433
|
-
LOWER(commit_message) LIKE '%closes #%'
|
|
434
|
-
)
|
|
435
|
-
ORDER BY timestamp DESC
|
|
436
|
-
LIMIT 50
|
|
437
|
-
`).all() as Array<{
|
|
438
|
-
commit_hash: string;
|
|
439
|
-
commit_message: string;
|
|
440
|
-
file: string;
|
|
441
|
-
diff: string | null;
|
|
442
|
-
timestamp: number;
|
|
443
|
-
}>;
|
|
444
|
-
|
|
445
|
-
let recorded = 0;
|
|
446
|
-
|
|
447
|
-
for (const commit of fixCommits) {
|
|
448
|
-
// Check if we already have this bug recorded (by commit hash in related_changes)
|
|
449
|
-
const existing = this.db.prepare(`
|
|
450
|
-
SELECT id FROM bug_history
|
|
451
|
-
WHERE fixed_by = ? OR related_changes LIKE ?
|
|
452
|
-
`).get(commit.commit_hash, `%${commit.commit_hash}%`);
|
|
453
|
-
|
|
454
|
-
if (existing) continue;
|
|
455
|
-
|
|
456
|
-
// Extract the bug description from commit message
|
|
457
|
-
const errorDescription = this.extractErrorFromCommitMessage(commit.commit_message);
|
|
458
|
-
|
|
459
|
-
// Record the bug as already fixed
|
|
460
|
-
const id = randomUUID();
|
|
461
|
-
|
|
462
|
-
this.db.prepare(`
|
|
463
|
-
INSERT INTO bug_history (id, error, file, timestamp, status, fixed_by, fixed_at, fix_diff, cause)
|
|
464
|
-
VALUES (?, ?, ?, ?, 'fixed', ?, ?, ?, ?)
|
|
465
|
-
`).run(
|
|
466
|
-
id,
|
|
467
|
-
errorDescription,
|
|
468
|
-
commit.file,
|
|
469
|
-
commit.timestamp,
|
|
470
|
-
commit.commit_hash,
|
|
471
|
-
commit.timestamp,
|
|
472
|
-
commit.diff?.slice(0, 2000) || null,
|
|
473
|
-
commit.commit_message
|
|
474
|
-
);
|
|
475
|
-
|
|
476
|
-
recorded++;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
return recorded;
|
|
480
|
-
} catch {
|
|
481
|
-
return 0;
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
/**
|
|
486
|
-
* Extract a bug/error description from a fix commit message
|
|
487
|
-
*/
|
|
488
|
-
private extractErrorFromCommitMessage(message: string): string {
|
|
489
|
-
// Remove common prefixes
|
|
490
|
-
let cleaned = message
|
|
491
|
-
.replace(/^fix\s*[:\(]/i, '')
|
|
492
|
-
.replace(/^bugfix\s*[:\(]/i, '')
|
|
493
|
-
.replace(/^hotfix\s*[:\(]/i, '')
|
|
494
|
-
.replace(/\):\s*/, ': ')
|
|
495
|
-
.trim();
|
|
496
|
-
|
|
497
|
-
// Extract issue references
|
|
498
|
-
const issueMatch = cleaned.match(/(?:fixes|fixed|closes)\s*#(\d+)/i);
|
|
499
|
-
if (issueMatch) {
|
|
500
|
-
cleaned = cleaned.replace(/(?:fixes|fixed|closes)\s*#\d+/gi, '').trim();
|
|
501
|
-
if (cleaned) {
|
|
502
|
-
cleaned = `Issue #${issueMatch[1]}: ${cleaned}`;
|
|
503
|
-
} else {
|
|
504
|
-
cleaned = `Issue #${issueMatch[1]}`;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
|
|
508
|
-
// Capitalize first letter
|
|
509
|
-
if (cleaned.length > 0) {
|
|
510
|
-
cleaned = cleaned.charAt(0).toUpperCase() + cleaned.slice(1);
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
return cleaned || message;
|
|
514
|
-
}
|
|
515
|
-
|
|
516
|
-
// Get bug statistics
|
|
517
|
-
getBugStats(): {
|
|
518
|
-
total: number;
|
|
519
|
-
open: number;
|
|
520
|
-
fixed: number;
|
|
521
|
-
avgTimeToFix: number;
|
|
522
|
-
} {
|
|
523
|
-
const stats = this.db.prepare(`
|
|
524
|
-
SELECT
|
|
525
|
-
COUNT(*) as total,
|
|
526
|
-
SUM(CASE WHEN status = 'open' THEN 1 ELSE 0 END) as open,
|
|
527
|
-
SUM(CASE WHEN status = 'fixed' THEN 1 ELSE 0 END) as fixed,
|
|
528
|
-
AVG(CASE WHEN status = 'fixed' THEN fixed_at - timestamp ELSE NULL END) as avg_fix_time
|
|
529
|
-
FROM bug_history
|
|
530
|
-
`).get() as {
|
|
531
|
-
total: number;
|
|
532
|
-
open: number;
|
|
533
|
-
fixed: number;
|
|
534
|
-
avg_fix_time: number | null;
|
|
535
|
-
};
|
|
536
|
-
|
|
537
|
-
return {
|
|
538
|
-
total: stats.total,
|
|
539
|
-
open: stats.open,
|
|
540
|
-
fixed: stats.fixed,
|
|
541
|
-
avgTimeToFix: stats.avg_fix_time ? Math.round(stats.avg_fix_time / 3600) : 0 // hours
|
|
542
|
-
};
|
|
543
|
-
}
|
|
544
|
-
}
|