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,334 @@
1
+ import { execSync } from 'child_process';
2
+ import { randomUUID } from 'crypto';
3
+ import type Database from 'better-sqlite3';
4
+ import type { Change, ChangeQueryResult, ChangeQueryOptions } from '../../types/documentation.js';
5
+
6
+ export class ChangeTracker {
7
+ private projectPath: string;
8
+ private db: Database.Database;
9
+
10
+ constructor(projectPath: string, db: Database.Database) {
11
+ this.projectPath = projectPath;
12
+ this.db = db;
13
+ this.ensureTable();
14
+ }
15
+
16
+ private ensureTable(): void {
17
+ this.db.exec(`
18
+ CREATE TABLE IF NOT EXISTS change_history (
19
+ id TEXT PRIMARY KEY,
20
+ file TEXT NOT NULL,
21
+ diff TEXT,
22
+ timestamp INTEGER NOT NULL,
23
+ author TEXT,
24
+ commit_hash TEXT,
25
+ commit_message TEXT,
26
+ lines_added INTEGER DEFAULT 0,
27
+ lines_removed INTEGER DEFAULT 0,
28
+ change_type TEXT DEFAULT 'modify'
29
+ );
30
+ CREATE INDEX IF NOT EXISTS idx_change_timestamp ON change_history(timestamp);
31
+ CREATE INDEX IF NOT EXISTS idx_change_file ON change_history(file);
32
+ CREATE INDEX IF NOT EXISTS idx_change_commit ON change_history(commit_hash);
33
+ `);
34
+ }
35
+
36
+ // Sync changes from git history
37
+ syncFromGit(limit: number = 100): number {
38
+ try {
39
+ // Get recent commits
40
+ const logOutput = execSync(
41
+ `git log --oneline -${limit} --format="%H|%an|%ad|%s" --date=unix`,
42
+ { cwd: this.projectPath, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }
43
+ ).trim();
44
+
45
+ if (!logOutput) return 0;
46
+
47
+ const commits = logOutput.split('\n').filter(Boolean);
48
+ let synced = 0;
49
+
50
+ for (const commitLine of commits) {
51
+ const [hash, author, dateStr, ...messageParts] = commitLine.split('|');
52
+ const message = messageParts.join('|');
53
+ const timestamp = parseInt(dateStr, 10);
54
+
55
+ // Check if already synced
56
+ const existing = this.db.prepare(
57
+ 'SELECT id FROM change_history WHERE commit_hash = ?'
58
+ ).get(hash);
59
+
60
+ if (existing) continue;
61
+
62
+ // Get files changed in this commit
63
+ try {
64
+ const filesOutput = execSync(
65
+ `git show --numstat --format="" ${hash}`,
66
+ { cwd: this.projectPath, encoding: 'utf-8', maxBuffer: 10 * 1024 * 1024 }
67
+ ).trim();
68
+
69
+ if (!filesOutput) continue;
70
+
71
+ const files = filesOutput.split('\n').filter(Boolean);
72
+
73
+ for (const fileLine of files) {
74
+ const [added, removed, filePath] = fileLine.split('\t');
75
+ if (!filePath) continue;
76
+
77
+ // Get diff for this file (cross-platform, no pipe to head)
78
+ let diff = '';
79
+ try {
80
+ const fullDiff = execSync(
81
+ `git show ${hash} -- "${filePath}"`,
82
+ { cwd: this.projectPath, encoding: 'utf-8', maxBuffer: 1024 * 1024 }
83
+ );
84
+ // Limit to first 100 lines and 2000 chars
85
+ diff = fullDiff.split('\n').slice(0, 100).join('\n').slice(0, 2000);
86
+ } catch {
87
+ // Ignore diff errors
88
+ }
89
+
90
+ const linesAdded = added === '-' ? 0 : parseInt(added, 10) || 0;
91
+ const linesRemoved = removed === '-' ? 0 : parseInt(removed, 10) || 0;
92
+ const changeType = this.inferChangeType(linesAdded, linesRemoved, filePath);
93
+
94
+ this.db.prepare(`
95
+ INSERT INTO change_history (id, file, diff, timestamp, author, commit_hash, commit_message, lines_added, lines_removed, change_type)
96
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
97
+ `).run(
98
+ randomUUID(),
99
+ filePath,
100
+ diff,
101
+ timestamp,
102
+ author,
103
+ hash,
104
+ message,
105
+ linesAdded,
106
+ linesRemoved,
107
+ changeType
108
+ );
109
+
110
+ synced++;
111
+ }
112
+ } catch {
113
+ // Skip commits that fail
114
+ }
115
+ }
116
+
117
+ return synced;
118
+ } catch (error) {
119
+ console.error('Error syncing git history:', error);
120
+ return 0;
121
+ }
122
+ }
123
+
124
+ private inferChangeType(added: number, removed: number, _filePath: string): Change['type'] {
125
+ if (removed === 0 && added > 0) return 'add';
126
+ if (added === 0 && removed > 0) return 'delete';
127
+ return 'modify';
128
+ }
129
+
130
+ // Query changes
131
+ queryChanges(options: ChangeQueryOptions = {}): ChangeQueryResult {
132
+ const since = this.parseSince(options.since || 'this week');
133
+ const until = options.until || new Date();
134
+
135
+ let query = `
136
+ SELECT id, file, diff, timestamp, author, commit_hash, commit_message, lines_added, lines_removed, change_type
137
+ FROM change_history
138
+ WHERE timestamp >= ? AND timestamp <= ?
139
+ `;
140
+ const params: (string | number)[] = [
141
+ Math.floor(since.getTime() / 1000),
142
+ Math.floor(until.getTime() / 1000)
143
+ ];
144
+
145
+ if (options.file) {
146
+ query += ' AND file LIKE ?';
147
+ params.push(`%${options.file}%`);
148
+ }
149
+
150
+ if (options.author) {
151
+ query += ' AND author LIKE ?';
152
+ params.push(`%${options.author}%`);
153
+ }
154
+
155
+ if (options.type) {
156
+ query += ' AND change_type = ?';
157
+ params.push(options.type);
158
+ }
159
+
160
+ query += ' ORDER BY timestamp DESC';
161
+
162
+ if (options.limit) {
163
+ query += ` LIMIT ${options.limit}`;
164
+ }
165
+
166
+ const rows = this.db.prepare(query).all(...params) as Array<{
167
+ id: string;
168
+ file: string;
169
+ diff: string | null;
170
+ timestamp: number;
171
+ author: string;
172
+ commit_hash: string;
173
+ commit_message: string;
174
+ lines_added: number;
175
+ lines_removed: number;
176
+ change_type: string;
177
+ }>;
178
+
179
+ const changes: Change[] = rows.map(row => ({
180
+ id: row.id,
181
+ file: row.file,
182
+ diff: row.diff || '',
183
+ timestamp: new Date(row.timestamp * 1000),
184
+ author: row.author,
185
+ commitHash: row.commit_hash,
186
+ commitMessage: row.commit_message,
187
+ linesAdded: row.lines_added,
188
+ linesRemoved: row.lines_removed,
189
+ type: row.change_type as Change['type']
190
+ }));
191
+
192
+ // Calculate aggregates
193
+ const byAuthor: Record<string, number> = {};
194
+ const byType: Record<string, number> = {};
195
+ let totalLinesAdded = 0;
196
+ let totalLinesRemoved = 0;
197
+ const uniqueFiles = new Set<string>();
198
+
199
+ for (const change of changes) {
200
+ byAuthor[change.author] = (byAuthor[change.author] || 0) + 1;
201
+ byType[change.type] = (byType[change.type] || 0) + 1;
202
+ totalLinesAdded += change.linesAdded;
203
+ totalLinesRemoved += change.linesRemoved;
204
+ uniqueFiles.add(change.file);
205
+ }
206
+
207
+ return {
208
+ period: this.formatPeriod(since, until),
209
+ since,
210
+ until,
211
+ changes,
212
+ totalFiles: uniqueFiles.size,
213
+ totalLinesAdded,
214
+ totalLinesRemoved,
215
+ byAuthor,
216
+ byType
217
+ };
218
+ }
219
+
220
+ // Get changes for a specific file
221
+ getFileChanges(filePath: string, limit: number = 20): Change[] {
222
+ const rows = this.db.prepare(`
223
+ SELECT id, file, diff, timestamp, author, commit_hash, commit_message, lines_added, lines_removed, change_type
224
+ FROM change_history
225
+ WHERE file LIKE ?
226
+ ORDER BY timestamp DESC
227
+ LIMIT ?
228
+ `).all(`%${filePath}%`, limit) as Array<{
229
+ id: string;
230
+ file: string;
231
+ diff: string | null;
232
+ timestamp: number;
233
+ author: string;
234
+ commit_hash: string;
235
+ commit_message: string;
236
+ lines_added: number;
237
+ lines_removed: number;
238
+ change_type: string;
239
+ }>;
240
+
241
+ return rows.map(row => ({
242
+ id: row.id,
243
+ file: row.file,
244
+ diff: row.diff || '',
245
+ timestamp: new Date(row.timestamp * 1000),
246
+ author: row.author,
247
+ commitHash: row.commit_hash,
248
+ commitMessage: row.commit_message,
249
+ linesAdded: row.lines_added,
250
+ linesRemoved: row.lines_removed,
251
+ type: row.change_type as Change['type']
252
+ }));
253
+ }
254
+
255
+ // Get recent changes
256
+ getRecentChanges(hours: number = 24): Change[] {
257
+ const since = new Date(Date.now() - hours * 60 * 60 * 1000);
258
+ return this.queryChanges({ since }).changes;
259
+ }
260
+
261
+ private parseSince(since: string | Date): Date {
262
+ if (since instanceof Date) return since;
263
+
264
+ const now = new Date();
265
+ const lower = since.toLowerCase();
266
+
267
+ if (lower === 'today') {
268
+ return new Date(now.getFullYear(), now.getMonth(), now.getDate());
269
+ }
270
+ if (lower === 'yesterday') {
271
+ return new Date(now.getTime() - 24 * 60 * 60 * 1000);
272
+ }
273
+ if (lower === 'this week') {
274
+ const dayOfWeek = now.getDay();
275
+ return new Date(now.getTime() - dayOfWeek * 24 * 60 * 60 * 1000);
276
+ }
277
+ if (lower === 'this month') {
278
+ return new Date(now.getFullYear(), now.getMonth(), 1);
279
+ }
280
+ if (lower === 'last week') {
281
+ return new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
282
+ }
283
+
284
+ // Try to parse as date
285
+ const parsed = new Date(since);
286
+ return isNaN(parsed.getTime()) ? new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000) : parsed;
287
+ }
288
+
289
+ private formatPeriod(since: Date, until: Date): string {
290
+ const options: Intl.DateTimeFormatOptions = { month: 'short', day: 'numeric' };
291
+ const sinceStr = since.toLocaleDateString('en-US', options);
292
+ const untilStr = until.toLocaleDateString('en-US', options);
293
+
294
+ if (sinceStr === untilStr) {
295
+ return sinceStr;
296
+ }
297
+ return `${sinceStr} - ${untilStr}`;
298
+ }
299
+
300
+ // Search changes by keyword
301
+ searchChanges(keyword: string, limit: number = 20): Change[] {
302
+ const rows = this.db.prepare(`
303
+ SELECT id, file, diff, timestamp, author, commit_hash, commit_message, lines_added, lines_removed, change_type
304
+ FROM change_history
305
+ WHERE file LIKE ? OR diff LIKE ? OR commit_message LIKE ?
306
+ ORDER BY timestamp DESC
307
+ LIMIT ?
308
+ `).all(`%${keyword}%`, `%${keyword}%`, `%${keyword}%`, limit) as Array<{
309
+ id: string;
310
+ file: string;
311
+ diff: string | null;
312
+ timestamp: number;
313
+ author: string;
314
+ commit_hash: string;
315
+ commit_message: string;
316
+ lines_added: number;
317
+ lines_removed: number;
318
+ change_type: string;
319
+ }>;
320
+
321
+ return rows.map(row => ({
322
+ id: row.id,
323
+ file: row.file,
324
+ diff: row.diff || '',
325
+ timestamp: new Date(row.timestamp * 1000),
326
+ author: row.author,
327
+ commitHash: row.commit_hash,
328
+ commitMessage: row.commit_message,
329
+ linesAdded: row.lines_added,
330
+ linesRemoved: row.lines_removed,
331
+ type: row.change_type as Change['type']
332
+ }));
333
+ }
334
+ }
@@ -0,0 +1,340 @@
1
+ import type Database from 'better-sqlite3';
2
+ import type { FixSuggestion, PastBug, Change } from '../../types/documentation.js';
3
+ import type { BugCorrelator } from './bug-correlator.js';
4
+ import type { ChangeTracker } from './change-tracker.js';
5
+
6
+ // Common fix patterns
7
+ const FIX_PATTERNS = [
8
+ {
9
+ errorPattern: /cannot read (?:property |properties of )['"]?(\w+)['"]? of (?:undefined|null)/i,
10
+ suggestions: [
11
+ {
12
+ fix: 'Add optional chaining (?.) before accessing the property',
13
+ diff: '+ obj?.property instead of obj.property',
14
+ confidence: 85
15
+ },
16
+ {
17
+ fix: 'Add null/undefined check before accessing',
18
+ diff: '+ if (obj && obj.property) { ... }',
19
+ confidence: 80
20
+ }
21
+ ]
22
+ },
23
+ {
24
+ errorPattern: /(\w+) is not defined/i,
25
+ suggestions: [
26
+ {
27
+ fix: 'Add missing import statement',
28
+ diff: '+ import { $1 } from "module"',
29
+ confidence: 75
30
+ },
31
+ {
32
+ fix: 'Declare the variable before use',
33
+ diff: '+ const $1 = ...',
34
+ confidence: 70
35
+ }
36
+ ]
37
+ },
38
+ {
39
+ errorPattern: /(\w+) is not a function/i,
40
+ suggestions: [
41
+ {
42
+ fix: 'Check if the method exists on the object',
43
+ diff: '+ if (typeof obj.method === "function") { obj.method() }',
44
+ confidence: 70
45
+ },
46
+ {
47
+ fix: 'Verify the import is correct',
48
+ diff: 'Check that the function is exported from the source module',
49
+ confidence: 65
50
+ }
51
+ ]
52
+ },
53
+ {
54
+ errorPattern: /unexpected token/i,
55
+ suggestions: [
56
+ {
57
+ fix: 'Check for JSON.parse on invalid JSON',
58
+ diff: '+ try { JSON.parse(data) } catch { ... }',
59
+ confidence: 70
60
+ },
61
+ {
62
+ fix: 'Validate data before parsing',
63
+ diff: '+ if (typeof data === "string" && data.startsWith("{")) { ... }',
64
+ confidence: 65
65
+ }
66
+ ]
67
+ },
68
+ {
69
+ errorPattern: /timeout|timed out/i,
70
+ suggestions: [
71
+ {
72
+ fix: 'Increase timeout value',
73
+ diff: '+ timeout: 30000 // Increase from default',
74
+ confidence: 60
75
+ },
76
+ {
77
+ fix: 'Add retry logic',
78
+ diff: '+ for (let i = 0; i < 3; i++) { try { ... } catch { await delay(1000) } }',
79
+ confidence: 55
80
+ }
81
+ ]
82
+ },
83
+ {
84
+ errorPattern: /ECONNREFUSED|connection refused/i,
85
+ suggestions: [
86
+ {
87
+ fix: 'Verify the service is running',
88
+ diff: 'Check that the database/API server is started',
89
+ confidence: 80
90
+ },
91
+ {
92
+ fix: 'Check port and host configuration',
93
+ diff: 'Verify PORT and HOST environment variables',
94
+ confidence: 75
95
+ }
96
+ ]
97
+ },
98
+ {
99
+ errorPattern: /out of memory|heap/i,
100
+ suggestions: [
101
+ {
102
+ fix: 'Process data in chunks',
103
+ diff: '+ for (const chunk of chunks(data, 1000)) { process(chunk) }',
104
+ confidence: 70
105
+ },
106
+ {
107
+ fix: 'Increase Node.js memory limit',
108
+ diff: '+ node --max-old-space-size=4096 app.js',
109
+ confidence: 60
110
+ }
111
+ ]
112
+ },
113
+ {
114
+ errorPattern: /module not found|cannot find module/i,
115
+ suggestions: [
116
+ {
117
+ fix: 'Install missing package',
118
+ diff: '+ npm install <package-name>',
119
+ confidence: 85
120
+ },
121
+ {
122
+ fix: 'Check import path is correct',
123
+ diff: 'Verify relative path (./module vs ../module)',
124
+ confidence: 75
125
+ }
126
+ ]
127
+ },
128
+ {
129
+ errorPattern: /type.*is not assignable/i,
130
+ suggestions: [
131
+ {
132
+ fix: 'Add type assertion',
133
+ diff: '+ value as ExpectedType',
134
+ confidence: 60
135
+ },
136
+ {
137
+ fix: 'Fix the type definition',
138
+ diff: 'Update interface/type to match actual data',
139
+ confidence: 70
140
+ }
141
+ ]
142
+ },
143
+ {
144
+ errorPattern: /async|await|promise/i,
145
+ suggestions: [
146
+ {
147
+ fix: 'Add await keyword',
148
+ diff: '+ const result = await asyncFunction()',
149
+ confidence: 75
150
+ },
151
+ {
152
+ fix: 'Handle promise rejection',
153
+ diff: '+ .catch(err => console.error(err))',
154
+ confidence: 70
155
+ }
156
+ ]
157
+ }
158
+ ];
159
+
160
+ export class FixSuggester {
161
+ private db: Database.Database;
162
+ private bugCorrelator: BugCorrelator;
163
+ private changeTracker: ChangeTracker;
164
+
165
+ constructor(
166
+ db: Database.Database,
167
+ bugCorrelator: BugCorrelator,
168
+ changeTracker: ChangeTracker
169
+ ) {
170
+ this.db = db;
171
+ this.bugCorrelator = bugCorrelator;
172
+ this.changeTracker = changeTracker;
173
+ }
174
+
175
+ suggestFix(error: string, context?: string): FixSuggestion[] {
176
+ const suggestions: FixSuggestion[] = [];
177
+
178
+ // 1. Check for similar bugs with fixes
179
+ const pastBugs = this.bugCorrelator.findSimilarBugs(error, 5);
180
+ for (const bug of pastBugs) {
181
+ if (bug.fix || bug.fixDiff) {
182
+ suggestions.push({
183
+ confidence: bug.similarity,
184
+ fix: bug.fix || 'Apply similar fix',
185
+ reason: `Fixed similar issue on ${bug.date.toLocaleDateString()}`,
186
+ diff: bug.fixDiff,
187
+ pastFix: {
188
+ date: bug.date,
189
+ file: bug.file || 'unknown',
190
+ bugId: bug.id
191
+ },
192
+ source: 'history'
193
+ });
194
+ }
195
+ }
196
+
197
+ // 2. Check for pattern-based suggestions
198
+ for (const { errorPattern, suggestions: patternSuggestions } of FIX_PATTERNS) {
199
+ const match = error.match(errorPattern);
200
+ if (match) {
201
+ for (const suggestion of patternSuggestions) {
202
+ // Replace $1 with captured group if present
203
+ let fix = suggestion.fix;
204
+ let diff = suggestion.diff;
205
+ if (match[1]) {
206
+ fix = fix.replace('$1', match[1]);
207
+ diff = diff.replace('$1', match[1]);
208
+ }
209
+
210
+ suggestions.push({
211
+ confidence: suggestion.confidence,
212
+ fix,
213
+ reason: 'Common fix for this error type',
214
+ diff,
215
+ source: 'pattern'
216
+ });
217
+ }
218
+ break; // Only use first matching pattern
219
+ }
220
+ }
221
+
222
+ // 3. Check if recent changes can be reverted
223
+ if (suggestions.length < 3) {
224
+ const revertSuggestion = this.checkRevertOption(error);
225
+ if (revertSuggestion) {
226
+ suggestions.push(revertSuggestion);
227
+ }
228
+ }
229
+
230
+ // 4. Add general suggestions based on context
231
+ if (context) {
232
+ const contextSuggestion = this.getContextBasedSuggestion(error, context);
233
+ if (contextSuggestion) {
234
+ suggestions.push(contextSuggestion);
235
+ }
236
+ }
237
+
238
+ // Sort by confidence
239
+ suggestions.sort((a, b) => b.confidence - a.confidence);
240
+
241
+ // Return top 5
242
+ return suggestions.slice(0, 5);
243
+ }
244
+
245
+ private checkRevertOption(error: string): FixSuggestion | null {
246
+ // Check if a recent change might have caused this
247
+ const keywords = error.split(/\s+/)
248
+ .filter(w => w.length > 3)
249
+ .slice(0, 5);
250
+
251
+ const recentChanges = this.changeTracker.getRecentChanges(24);
252
+
253
+ for (const change of recentChanges) {
254
+ const changeText = `${change.file} ${change.diff}`.toLowerCase();
255
+ const matchCount = keywords.filter(k => changeText.includes(k.toLowerCase())).length;
256
+
257
+ if (matchCount >= 2 && change.linesRemoved > 0) {
258
+ return {
259
+ confidence: 50,
260
+ fix: `Consider reverting recent change in ${change.file}`,
261
+ reason: `This file was modified ${Math.round((Date.now() - change.timestamp.getTime()) / (1000 * 60 * 60))}h ago and may have introduced the issue`,
262
+ diff: `git revert ${change.commitHash}`,
263
+ pastFix: {
264
+ date: change.timestamp,
265
+ file: change.file
266
+ },
267
+ source: 'history'
268
+ };
269
+ }
270
+ }
271
+
272
+ return null;
273
+ }
274
+
275
+ private getContextBasedSuggestion(error: string, context: string): FixSuggestion | null {
276
+ const contextLower = context.toLowerCase();
277
+ const errorLower = error.toLowerCase();
278
+
279
+ // Database-related
280
+ if (contextLower.includes('database') || contextLower.includes('query')) {
281
+ if (errorLower.includes('timeout')) {
282
+ return {
283
+ confidence: 55,
284
+ fix: 'Add database index or optimize query',
285
+ reason: 'Database timeouts often indicate missing indexes',
286
+ diff: 'CREATE INDEX idx_column ON table(column)',
287
+ source: 'general'
288
+ };
289
+ }
290
+ }
291
+
292
+ // API-related
293
+ if (contextLower.includes('api') || contextLower.includes('fetch')) {
294
+ if (errorLower.includes('undefined') || errorLower.includes('null')) {
295
+ return {
296
+ confidence: 55,
297
+ fix: 'Add response validation before accessing data',
298
+ reason: 'API responses should be validated before use',
299
+ diff: 'if (response?.data) { ... }',
300
+ source: 'general'
301
+ };
302
+ }
303
+ }
304
+
305
+ // React/Component-related
306
+ if (contextLower.includes('component') || contextLower.includes('react')) {
307
+ if (errorLower.includes('undefined') || errorLower.includes('null')) {
308
+ return {
309
+ confidence: 55,
310
+ fix: 'Add null check or default value for props',
311
+ reason: 'Component props may be undefined initially',
312
+ diff: 'const value = props.value ?? defaultValue',
313
+ source: 'general'
314
+ };
315
+ }
316
+ }
317
+
318
+ return null;
319
+ }
320
+
321
+ // Get fix statistics
322
+ getFixStats(): {
323
+ totalFixes: number;
324
+ successRate: number;
325
+ avgConfidence: number;
326
+ topPatterns: Array<{ pattern: string; count: number }>;
327
+ } {
328
+ // This would ideally track actual fix success over time
329
+ // For now, return pattern-based stats
330
+ return {
331
+ totalFixes: FIX_PATTERNS.length * 2, // Each pattern has ~2 suggestions
332
+ successRate: 75, // Estimated
333
+ avgConfidence: 70,
334
+ topPatterns: FIX_PATTERNS.slice(0, 5).map((p, i) => ({
335
+ pattern: p.errorPattern.source.slice(0, 30),
336
+ count: 10 - i // Placeholder counts
337
+ }))
338
+ };
339
+ }
340
+ }
@@ -0,0 +1,5 @@
1
+ // Change Intelligence Module - Phase 9
2
+ export { ChangeIntelligence } from './change-intelligence.js';
3
+ export { ChangeTracker } from './change-tracker.js';
4
+ export { BugCorrelator } from './bug-correlator.js';
5
+ export { FixSuggester } from './fix-suggester.js';