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.

Files changed (81) hide show
  1. package/README.md +3 -2
  2. package/dist/index.js +172 -90
  3. package/dist/index.js.map +7 -0
  4. package/package.json +6 -1
  5. package/esbuild.config.js +0 -26
  6. package/src/cli/commands.ts +0 -573
  7. package/src/core/adr-exporter.ts +0 -253
  8. package/src/core/architecture/architecture-enforcement.ts +0 -228
  9. package/src/core/architecture/duplicate-detector.ts +0 -288
  10. package/src/core/architecture/index.ts +0 -6
  11. package/src/core/architecture/pattern-learner.ts +0 -306
  12. package/src/core/architecture/pattern-library.ts +0 -403
  13. package/src/core/architecture/pattern-validator.ts +0 -324
  14. package/src/core/change-intelligence/bug-correlator.ts +0 -544
  15. package/src/core/change-intelligence/change-intelligence.ts +0 -264
  16. package/src/core/change-intelligence/change-tracker.ts +0 -334
  17. package/src/core/change-intelligence/fix-suggester.ts +0 -340
  18. package/src/core/change-intelligence/index.ts +0 -5
  19. package/src/core/code-verifier.ts +0 -843
  20. package/src/core/confidence/confidence-scorer.ts +0 -251
  21. package/src/core/confidence/conflict-checker.ts +0 -289
  22. package/src/core/confidence/index.ts +0 -5
  23. package/src/core/confidence/source-tracker.ts +0 -263
  24. package/src/core/confidence/warning-detector.ts +0 -241
  25. package/src/core/context-rot/compaction.ts +0 -284
  26. package/src/core/context-rot/context-health.ts +0 -243
  27. package/src/core/context-rot/context-rot-prevention.ts +0 -213
  28. package/src/core/context-rot/critical-context.ts +0 -221
  29. package/src/core/context-rot/drift-detector.ts +0 -255
  30. package/src/core/context-rot/index.ts +0 -7
  31. package/src/core/context.ts +0 -263
  32. package/src/core/decision-extractor.ts +0 -339
  33. package/src/core/decisions.ts +0 -69
  34. package/src/core/deja-vu.ts +0 -421
  35. package/src/core/engine.ts +0 -1646
  36. package/src/core/feature-context.ts +0 -726
  37. package/src/core/ghost-mode.ts +0 -465
  38. package/src/core/learning.ts +0 -519
  39. package/src/core/living-docs/activity-tracker.ts +0 -296
  40. package/src/core/living-docs/architecture-generator.ts +0 -428
  41. package/src/core/living-docs/changelog-generator.ts +0 -348
  42. package/src/core/living-docs/component-generator.ts +0 -230
  43. package/src/core/living-docs/doc-engine.ts +0 -134
  44. package/src/core/living-docs/doc-validator.ts +0 -282
  45. package/src/core/living-docs/index.ts +0 -8
  46. package/src/core/project-manager.ts +0 -301
  47. package/src/core/refresh/activity-gate.ts +0 -256
  48. package/src/core/refresh/git-staleness-checker.ts +0 -108
  49. package/src/core/refresh/index.ts +0 -27
  50. package/src/core/summarizer.ts +0 -290
  51. package/src/core/test-awareness/change-validator.ts +0 -499
  52. package/src/core/test-awareness/index.ts +0 -5
  53. package/src/index.ts +0 -90
  54. package/src/indexing/ast.ts +0 -868
  55. package/src/indexing/embeddings.ts +0 -85
  56. package/src/indexing/indexer.ts +0 -270
  57. package/src/indexing/watcher.ts +0 -78
  58. package/src/server/gateways/aggregator.ts +0 -374
  59. package/src/server/gateways/index.ts +0 -473
  60. package/src/server/gateways/memory-ghost.ts +0 -343
  61. package/src/server/gateways/memory-query.ts +0 -452
  62. package/src/server/gateways/memory-record.ts +0 -346
  63. package/src/server/gateways/memory-review.ts +0 -410
  64. package/src/server/gateways/memory-status.ts +0 -517
  65. package/src/server/gateways/memory-verify.ts +0 -392
  66. package/src/server/gateways/router.ts +0 -434
  67. package/src/server/gateways/types.ts +0 -610
  68. package/src/server/http.ts +0 -228
  69. package/src/server/mcp.ts +0 -154
  70. package/src/server/resources.ts +0 -85
  71. package/src/server/tools.ts +0 -2460
  72. package/src/storage/database.ts +0 -271
  73. package/src/storage/tier1.ts +0 -135
  74. package/src/storage/tier2.ts +0 -972
  75. package/src/storage/tier3.ts +0 -123
  76. package/src/types/documentation.ts +0 -619
  77. package/src/types/index.ts +0 -222
  78. package/src/utils/config.ts +0 -194
  79. package/src/utils/files.ts +0 -117
  80. package/src/utils/time.ts +0 -37
  81. package/src/utils/tokens.ts +0 -52
@@ -1,264 +0,0 @@
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
- private lastKnownHead: string | null = null;
23
- private lastBugScanHead: string | null = null;
24
-
25
- constructor(
26
- projectPath: string,
27
- db: Database.Database,
28
- tier2: Tier2Storage,
29
- embeddingGenerator: EmbeddingGenerator
30
- ) {
31
- this.changeTracker = new ChangeTracker(projectPath, db);
32
- this.bugCorrelator = new BugCorrelator(db, this.changeTracker, tier2, embeddingGenerator);
33
- this.fixSuggester = new FixSuggester(db, this.bugCorrelator, this.changeTracker);
34
- }
35
-
36
- // Initialize by syncing git history
37
- initialize(): number {
38
- if (this.initialized) return 0;
39
-
40
- const synced = this.changeTracker.syncFromGit(100);
41
- this.initialized = true;
42
- return synced;
43
- }
44
-
45
- // Sync recent git changes on-demand (event-driven alternative to polling)
46
- syncFromGit(limit: number = 20): number {
47
- return this.changeTracker.syncFromGit(limit);
48
- }
49
-
50
- /**
51
- * Get the last known HEAD commit
52
- */
53
- getLastKnownHead(): string | null {
54
- return this.lastKnownHead;
55
- }
56
-
57
- /**
58
- * Update the last known HEAD (call this after syncing)
59
- */
60
- setLastKnownHead(head: string): void {
61
- this.lastKnownHead = head;
62
- }
63
-
64
- /**
65
- * Get the last HEAD where we scanned for bug fixes
66
- */
67
- getLastBugScanHead(): string | null {
68
- return this.lastBugScanHead;
69
- }
70
-
71
- /**
72
- * Update the last bug scan HEAD (call this after scanning)
73
- */
74
- setLastBugScanHead(head: string): void {
75
- this.lastBugScanHead = head;
76
- }
77
-
78
- // Query what changed
79
- whatChanged(options: ChangeQueryOptions = {}): ChangeQueryResult {
80
- return this.changeTracker.queryChanges(options);
81
- }
82
-
83
- // Get changes for a specific file
84
- whatChangedIn(file: string, limit?: number): Change[] {
85
- return this.changeTracker.getFileChanges(file, limit);
86
- }
87
-
88
- // Get recent changes
89
- getRecentChanges(hours: number = 24): Change[] {
90
- return this.changeTracker.getRecentChanges(hours);
91
- }
92
-
93
- // Diagnose why something broke
94
- whyBroke(error: string, options?: { file?: string; line?: number }): Diagnosis {
95
- return this.bugCorrelator.diagnoseBug(error, options);
96
- }
97
-
98
- // Find similar bugs from history
99
- findSimilarBugs(error: string, limit?: number): PastBug[] {
100
- return this.bugCorrelator.findSimilarBugs(error, limit);
101
- }
102
-
103
- // Suggest fixes for an error
104
- suggestFix(error: string, context?: string): FixSuggestion[] {
105
- return this.fixSuggester.suggestFix(error, context);
106
- }
107
-
108
- // Record a bug for future reference
109
- recordBug(error: string, options?: {
110
- stackTrace?: string;
111
- file?: string;
112
- line?: number;
113
- relatedChanges?: string[];
114
- }): Bug {
115
- return this.bugCorrelator.recordBug(error, options);
116
- }
117
-
118
- // Record that a bug was fixed
119
- recordFix(bugId: string, fixDiff: string, cause?: string): boolean {
120
- return this.bugCorrelator.recordFix(bugId, fixDiff, cause);
121
- }
122
-
123
- /**
124
- * Scan git history for fix commits and auto-record as bugs
125
- * Called by background intelligence loop
126
- */
127
- scanForBugFixes(): number {
128
- return this.bugCorrelator.scanForBugFixes();
129
- }
130
-
131
- // Search changes by keyword
132
- searchChanges(keyword: string, limit?: number): Change[] {
133
- return this.changeTracker.searchChanges(keyword, limit);
134
- }
135
-
136
- // Get statistics
137
- getStats(): {
138
- changes: {
139
- total: number;
140
- last24h: number;
141
- lastWeek: number;
142
- };
143
- bugs: {
144
- total: number;
145
- open: number;
146
- fixed: number;
147
- avgTimeToFix: number;
148
- };
149
- } {
150
- const last24h = this.changeTracker.getRecentChanges(24);
151
- const lastWeek = this.changeTracker.queryChanges({ since: 'last week' });
152
- const bugStats = this.bugCorrelator.getBugStats();
153
-
154
- return {
155
- changes: {
156
- total: lastWeek.changes.length,
157
- last24h: last24h.length,
158
- lastWeek: lastWeek.changes.length
159
- },
160
- bugs: bugStats
161
- };
162
- }
163
-
164
- // Format diagnosis for display
165
- static formatDiagnosis(diagnosis: Diagnosis): string {
166
- const lines: string[] = [];
167
-
168
- lines.push('\u{1F50D} Bug Diagnosis\n');
169
-
170
- if (diagnosis.likelyCause) {
171
- const change = diagnosis.likelyCause;
172
- const hoursAgo = Math.round((Date.now() - change.timestamp.getTime()) / (1000 * 60 * 60));
173
-
174
- lines.push(`\u{1F4CD} Likely Cause (${diagnosis.confidence}% confidence)`);
175
- lines.push(`File: ${change.file}`);
176
- lines.push(`Changed: ${hoursAgo}h ago by ${change.author}`);
177
- lines.push(`Commit: ${change.commitMessage.slice(0, 50)}`);
178
-
179
- if (change.diff) {
180
- lines.push('\nDiff:');
181
- lines.push(change.diff.slice(0, 300));
182
- }
183
- } else {
184
- lines.push('\u{2139}\u{FE0F} Could not identify a specific cause');
185
- }
186
-
187
- if (diagnosis.pastSimilarBugs.length > 0) {
188
- lines.push('\n\u{1F4A1} Similar Bugs Found');
189
- for (const bug of diagnosis.pastSimilarBugs.slice(0, 2)) {
190
- lines.push(`- ${bug.error.slice(0, 50)} (${bug.similarity}% similar)`);
191
- if (bug.fix) {
192
- lines.push(` Fix: ${bug.fix}`);
193
- }
194
- }
195
- }
196
-
197
- if (diagnosis.suggestedFix) {
198
- lines.push('\n\u{1F527} Suggested Fix');
199
- lines.push(diagnosis.suggestedFix);
200
- }
201
-
202
- lines.push('\n' + diagnosis.reasoning);
203
-
204
- return lines.join('\n');
205
- }
206
-
207
- // Format changes for display
208
- static formatChanges(result: ChangeQueryResult): string {
209
- const lines: string[] = [];
210
-
211
- lines.push(`\u{1F4CB} Changes: ${result.period}\n`);
212
- lines.push(`Files Changed: ${result.totalFiles}`);
213
- lines.push(`Lines: +${result.totalLinesAdded}, -${result.totalLinesRemoved}`);
214
- lines.push('');
215
-
216
- if (result.changes.length === 0) {
217
- lines.push('No changes found in this period.');
218
- return lines.join('\n');
219
- }
220
-
221
- lines.push('Key Changes:');
222
- for (const change of result.changes.slice(0, 10)) {
223
- const time = change.timestamp.toLocaleTimeString('en-US', { hour: '2-digit', minute: '2-digit' });
224
- lines.push(`\u251C\u2500\u2500 ${change.file} (${time})`);
225
- lines.push(`\u2502 ${change.commitMessage.slice(0, 50)}`);
226
- lines.push(`\u2502 +${change.linesAdded} lines, -${change.linesRemoved} lines`);
227
- lines.push(`\u2502 Author: ${change.author}`);
228
- }
229
-
230
- if (result.changes.length > 10) {
231
- lines.push(`\u2514\u2500\u2500 ${result.changes.length - 10} more changes...`);
232
- }
233
-
234
- return lines.join('\n');
235
- }
236
-
237
- // Format fix suggestions for display
238
- static formatFixSuggestions(suggestions: FixSuggestion[]): string {
239
- if (suggestions.length === 0) {
240
- return 'No fix suggestions available.';
241
- }
242
-
243
- const lines: string[] = [];
244
- lines.push('\u{1F527} Fix Suggestions\n');
245
-
246
- for (let i = 0; i < suggestions.length; i++) {
247
- const s = suggestions[i];
248
- const icon = s.confidence >= 80 ? '\u{1F7E2}' :
249
- s.confidence >= 60 ? '\u{1F7E1}' : '\u{1F7E0}';
250
-
251
- lines.push(`${i + 1}. ${icon} ${s.fix} (${s.confidence}% confidence)`);
252
- lines.push(` Reason: ${s.reason}`);
253
- if (s.diff) {
254
- lines.push(` ${s.diff}`);
255
- }
256
- if (s.pastFix) {
257
- lines.push(` Based on fix from ${s.pastFix.date.toLocaleDateString()}`);
258
- }
259
- lines.push('');
260
- }
261
-
262
- return lines.join('\n');
263
- }
264
- }
@@ -1,334 +0,0 @@
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
- }