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,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
|
-
}
|