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,256 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ActivityGate - Track user activity and manage idle-time maintenance tasks
|
|
3
|
-
*
|
|
4
|
-
* This component enables intelligent refresh by:
|
|
5
|
-
* 1. Tracking when the user was last active
|
|
6
|
-
* 2. Detecting idle periods
|
|
7
|
-
* 3. Executing maintenance tasks during idle time
|
|
8
|
-
* 4. Ensuring only one idle task runs at a time
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
export interface IdleTask {
|
|
12
|
-
name: string;
|
|
13
|
-
callback: () => void | Promise<void>;
|
|
14
|
-
minIdleMs: number; // Minimum idle time before this task can run
|
|
15
|
-
lastRun: number;
|
|
16
|
-
intervalMs: number; // Minimum time between runs
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export class ActivityGate {
|
|
20
|
-
private lastActivity: number = Date.now();
|
|
21
|
-
private idleTasks: IdleTask[] = [];
|
|
22
|
-
private monitoringInterval: ReturnType<typeof setInterval> | null = null;
|
|
23
|
-
private isExecutingTask: boolean = false;
|
|
24
|
-
private currentTaskIndex: number = 0;
|
|
25
|
-
|
|
26
|
-
constructor() {
|
|
27
|
-
// Activity is recorded at construction time
|
|
28
|
-
this.recordActivity();
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* Record user activity (call this on API method invocations)
|
|
33
|
-
*/
|
|
34
|
-
recordActivity(): void {
|
|
35
|
-
this.lastActivity = Date.now();
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* Get the timestamp of the last recorded activity
|
|
40
|
-
*/
|
|
41
|
-
getLastActivity(): number {
|
|
42
|
-
return this.lastActivity;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Check if the user has been idle for at least the given threshold
|
|
47
|
-
*/
|
|
48
|
-
isIdle(thresholdMs: number = 30_000): boolean {
|
|
49
|
-
return Date.now() - this.lastActivity > thresholdMs;
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* Get the duration of the current idle period in milliseconds
|
|
54
|
-
*/
|
|
55
|
-
getIdleDuration(): number {
|
|
56
|
-
return Date.now() - this.lastActivity;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Register an idle-time task
|
|
61
|
-
* Tasks are executed during idle periods, one at a time
|
|
62
|
-
*/
|
|
63
|
-
registerIdleTask(
|
|
64
|
-
name: string,
|
|
65
|
-
callback: () => void | Promise<void>,
|
|
66
|
-
options: {
|
|
67
|
-
minIdleMs?: number;
|
|
68
|
-
intervalMs?: number;
|
|
69
|
-
} = {}
|
|
70
|
-
): void {
|
|
71
|
-
const { minIdleMs = 30_000, intervalMs = 300_000 } = options;
|
|
72
|
-
|
|
73
|
-
// Remove existing task with same name
|
|
74
|
-
this.idleTasks = this.idleTasks.filter(t => t.name !== name);
|
|
75
|
-
|
|
76
|
-
this.idleTasks.push({
|
|
77
|
-
name,
|
|
78
|
-
callback,
|
|
79
|
-
minIdleMs,
|
|
80
|
-
lastRun: 0,
|
|
81
|
-
intervalMs
|
|
82
|
-
});
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Unregister an idle-time task
|
|
87
|
-
*/
|
|
88
|
-
unregisterIdleTask(name: string): boolean {
|
|
89
|
-
const initialLength = this.idleTasks.length;
|
|
90
|
-
this.idleTasks = this.idleTasks.filter(t => t.name !== name);
|
|
91
|
-
return this.idleTasks.length < initialLength;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
/**
|
|
95
|
-
* Get all registered idle tasks
|
|
96
|
-
*/
|
|
97
|
-
getIdleTasks(): readonly IdleTask[] {
|
|
98
|
-
return this.idleTasks;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Start monitoring for idle periods
|
|
103
|
-
* Will check every checkIntervalMs and execute tasks when idle
|
|
104
|
-
*/
|
|
105
|
-
startIdleMonitoring(checkIntervalMs: number = 10_000): void {
|
|
106
|
-
if (this.monitoringInterval) {
|
|
107
|
-
return; // Already monitoring
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
this.monitoringInterval = setInterval(() => {
|
|
111
|
-
this.executeNextIdleTaskIfReady();
|
|
112
|
-
}, checkIntervalMs);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Stop idle monitoring
|
|
117
|
-
*/
|
|
118
|
-
stopIdleMonitoring(): void {
|
|
119
|
-
if (this.monitoringInterval) {
|
|
120
|
-
clearInterval(this.monitoringInterval);
|
|
121
|
-
this.monitoringInterval = null;
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Execute the next idle task if conditions are met
|
|
127
|
-
* - User must be idle
|
|
128
|
-
* - No other task is currently executing
|
|
129
|
-
* - Task's minimum idle time must be met
|
|
130
|
-
* - Task's minimum interval since last run must be met
|
|
131
|
-
*/
|
|
132
|
-
private async executeNextIdleTaskIfReady(): Promise<void> {
|
|
133
|
-
if (this.isExecutingTask) {
|
|
134
|
-
return; // Already executing a task
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (this.idleTasks.length === 0) {
|
|
138
|
-
return; // No tasks registered
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
const now = Date.now();
|
|
142
|
-
const idleDuration = this.getIdleDuration();
|
|
143
|
-
|
|
144
|
-
// Find the next task that's ready to run
|
|
145
|
-
for (let i = 0; i < this.idleTasks.length; i++) {
|
|
146
|
-
const taskIndex = (this.currentTaskIndex + i) % this.idleTasks.length;
|
|
147
|
-
const task = this.idleTasks[taskIndex];
|
|
148
|
-
|
|
149
|
-
// Safety check (TypeScript strictness)
|
|
150
|
-
if (!task) continue;
|
|
151
|
-
|
|
152
|
-
// Check if task is ready
|
|
153
|
-
if (idleDuration < task.minIdleMs) {
|
|
154
|
-
continue; // Not idle long enough
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
if (now - task.lastRun < task.intervalMs) {
|
|
158
|
-
continue; // Too soon since last run
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// Execute this task
|
|
162
|
-
this.currentTaskIndex = (taskIndex + 1) % this.idleTasks.length;
|
|
163
|
-
await this.executeTask(task);
|
|
164
|
-
return; // Only execute one task per check
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/**
|
|
169
|
-
* Execute a specific task
|
|
170
|
-
*/
|
|
171
|
-
private async executeTask(task: IdleTask): Promise<void> {
|
|
172
|
-
this.isExecutingTask = true;
|
|
173
|
-
|
|
174
|
-
try {
|
|
175
|
-
await Promise.resolve(task.callback());
|
|
176
|
-
task.lastRun = Date.now();
|
|
177
|
-
} catch (error) {
|
|
178
|
-
console.error(`Idle task '${task.name}' failed:`, error);
|
|
179
|
-
// Still update lastRun to prevent rapid retries
|
|
180
|
-
task.lastRun = Date.now();
|
|
181
|
-
} finally {
|
|
182
|
-
this.isExecutingTask = false;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
/**
|
|
187
|
-
* Manually trigger execution of all ready idle tasks
|
|
188
|
-
* Useful for testing or manual refresh
|
|
189
|
-
*/
|
|
190
|
-
async executeReadyTasks(): Promise<string[]> {
|
|
191
|
-
const executed: string[] = [];
|
|
192
|
-
const now = Date.now();
|
|
193
|
-
const idleDuration = this.getIdleDuration();
|
|
194
|
-
|
|
195
|
-
for (const task of this.idleTasks) {
|
|
196
|
-
if (idleDuration >= task.minIdleMs && now - task.lastRun >= task.intervalMs) {
|
|
197
|
-
await this.executeTask(task);
|
|
198
|
-
executed.push(task.name);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
return executed;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
/**
|
|
206
|
-
* Force immediate execution of a specific task by name
|
|
207
|
-
* Ignores idle and interval requirements
|
|
208
|
-
*/
|
|
209
|
-
async forceExecuteTask(name: string): Promise<boolean> {
|
|
210
|
-
const task = this.idleTasks.find(t => t.name === name);
|
|
211
|
-
if (!task) {
|
|
212
|
-
return false;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
await this.executeTask(task);
|
|
216
|
-
return true;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Get status of all idle tasks
|
|
221
|
-
*/
|
|
222
|
-
getStatus(): {
|
|
223
|
-
isIdle: boolean;
|
|
224
|
-
idleDuration: number;
|
|
225
|
-
isExecutingTask: boolean;
|
|
226
|
-
tasks: Array<{
|
|
227
|
-
name: string;
|
|
228
|
-
lastRun: number;
|
|
229
|
-
timeSinceRun: number;
|
|
230
|
-
readyToRun: boolean;
|
|
231
|
-
}>;
|
|
232
|
-
} {
|
|
233
|
-
const now = Date.now();
|
|
234
|
-
const idleDuration = this.getIdleDuration();
|
|
235
|
-
|
|
236
|
-
return {
|
|
237
|
-
isIdle: this.isIdle(),
|
|
238
|
-
idleDuration,
|
|
239
|
-
isExecutingTask: this.isExecutingTask,
|
|
240
|
-
tasks: this.idleTasks.map(task => ({
|
|
241
|
-
name: task.name,
|
|
242
|
-
lastRun: task.lastRun,
|
|
243
|
-
timeSinceRun: now - task.lastRun,
|
|
244
|
-
readyToRun: idleDuration >= task.minIdleMs && now - task.lastRun >= task.intervalMs
|
|
245
|
-
}))
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
/**
|
|
250
|
-
* Shutdown the activity gate
|
|
251
|
-
*/
|
|
252
|
-
shutdown(): void {
|
|
253
|
-
this.stopIdleMonitoring();
|
|
254
|
-
this.idleTasks = [];
|
|
255
|
-
}
|
|
256
|
-
}
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
import { execSync } from 'child_process';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* GitStalenessChecker - Cheap HEAD caching and change detection
|
|
5
|
-
*
|
|
6
|
-
* Instead of running expensive `git log` operations, we cache the HEAD commit
|
|
7
|
-
* and only run full syncs when HEAD has changed. This reduces polling overhead
|
|
8
|
-
* from ~100ms+ to ~5ms per check.
|
|
9
|
-
*/
|
|
10
|
-
export class GitStalenessChecker {
|
|
11
|
-
private cachedHead: string | null = null;
|
|
12
|
-
private lastCheckTime: number = 0;
|
|
13
|
-
private projectPath: string;
|
|
14
|
-
|
|
15
|
-
constructor(projectPath: string) {
|
|
16
|
-
this.projectPath = projectPath;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
/**
|
|
20
|
-
* Get the current HEAD commit hash
|
|
21
|
-
* This is a cheap operation (~5ms)
|
|
22
|
-
*/
|
|
23
|
-
getCurrentHead(): string | null {
|
|
24
|
-
try {
|
|
25
|
-
const head = execSync('git rev-parse HEAD', {
|
|
26
|
-
cwd: this.projectPath,
|
|
27
|
-
encoding: 'utf-8',
|
|
28
|
-
timeout: 5000,
|
|
29
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
30
|
-
}).trim();
|
|
31
|
-
return head;
|
|
32
|
-
} catch {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Check if there are new commits since the last check
|
|
39
|
-
* Returns true if HEAD has changed (meaning we need to sync)
|
|
40
|
-
*/
|
|
41
|
-
hasNewCommits(): boolean {
|
|
42
|
-
const current = this.getCurrentHead();
|
|
43
|
-
this.lastCheckTime = Date.now();
|
|
44
|
-
|
|
45
|
-
if (current === null) {
|
|
46
|
-
return false; // Not a git repo or git command failed
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (this.cachedHead === null) {
|
|
50
|
-
// First check - cache the current HEAD
|
|
51
|
-
this.cachedHead = current;
|
|
52
|
-
return false; // No need to sync on first check (assume we synced on init)
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (current !== this.cachedHead) {
|
|
56
|
-
// HEAD has changed - update cache and signal sync needed
|
|
57
|
-
this.cachedHead = current;
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return false;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Force update the cached HEAD (call after successful sync)
|
|
66
|
-
*/
|
|
67
|
-
updateCachedHead(): void {
|
|
68
|
-
this.cachedHead = this.getCurrentHead();
|
|
69
|
-
this.lastCheckTime = Date.now();
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Get the cached HEAD without checking git
|
|
74
|
-
*/
|
|
75
|
-
getCachedHead(): string | null {
|
|
76
|
-
return this.cachedHead;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Get the time of the last check
|
|
81
|
-
*/
|
|
82
|
-
getLastCheckTime(): number {
|
|
83
|
-
return this.lastCheckTime;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/**
|
|
87
|
-
* Get time since last check in milliseconds
|
|
88
|
-
*/
|
|
89
|
-
getTimeSinceLastCheck(): number {
|
|
90
|
-
return Date.now() - this.lastCheckTime;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Check if enough time has passed since last check
|
|
95
|
-
* Default threshold is 30 seconds
|
|
96
|
-
*/
|
|
97
|
-
shouldCheck(thresholdMs: number = 30_000): boolean {
|
|
98
|
-
return this.getTimeSinceLastCheck() > thresholdMs;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Reset the checker state (useful for testing)
|
|
103
|
-
*/
|
|
104
|
-
reset(): void {
|
|
105
|
-
this.cachedHead = null;
|
|
106
|
-
this.lastCheckTime = 0;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Intelligent Refresh System for NeuronLayer
|
|
3
|
-
*
|
|
4
|
-
* This module provides a tiered refresh architecture:
|
|
5
|
-
*
|
|
6
|
-
* TIER 1: REAL-TIME (via file watcher)
|
|
7
|
-
* - File changes → Chokidar watcher → immediate invalidation
|
|
8
|
-
* - User queries → immediate tracking
|
|
9
|
-
* - File access → immediate hot cache update
|
|
10
|
-
*
|
|
11
|
-
* TIER 2: ON-DEMAND WITH CHEAP PRE-CHECK
|
|
12
|
-
* - Git sync → check HEAD first (5ms), only sync if changed
|
|
13
|
-
* - Summaries → check lastModified before regenerating
|
|
14
|
-
* - Bug diagnosis → sync git first if HEAD changed
|
|
15
|
-
*
|
|
16
|
-
* TIER 3: IDLE-TIME MAINTENANCE
|
|
17
|
-
* - When user idle > 30s AND git changed → sync git
|
|
18
|
-
* - When idle > 5min since last update → update importance scores
|
|
19
|
-
* - One task at a time, non-blocking
|
|
20
|
-
*
|
|
21
|
-
* TIER 4: SESSION-BASED
|
|
22
|
-
* - Engine init → full git sync
|
|
23
|
-
* - Shutdown → persist state
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
export { GitStalenessChecker } from './git-staleness-checker.js';
|
|
27
|
-
export { ActivityGate, type IdleTask } from './activity-gate.js';
|
package/src/core/summarizer.ts
DELETED
|
@@ -1,290 +0,0 @@
|
|
|
1
|
-
import type Database from 'better-sqlite3';
|
|
2
|
-
import type { CodeSymbol } from '../types/index.js';
|
|
3
|
-
import { estimateTokens } from '../utils/tokens.js';
|
|
4
|
-
|
|
5
|
-
interface FileSummary {
|
|
6
|
-
fileId: number;
|
|
7
|
-
summary: string;
|
|
8
|
-
tokens: number;
|
|
9
|
-
generatedAt: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class FileSummarizer {
|
|
13
|
-
private db: Database.Database;
|
|
14
|
-
|
|
15
|
-
constructor(db: Database.Database) {
|
|
16
|
-
this.db = db;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Generate a compressed summary of a file
|
|
20
|
-
generateSummary(
|
|
21
|
-
filePath: string,
|
|
22
|
-
content: string,
|
|
23
|
-
symbols: CodeSymbol[],
|
|
24
|
-
imports: Array<{ importedFrom: string; importedSymbols: string[] }>,
|
|
25
|
-
exports: Array<{ exportedName: string }>
|
|
26
|
-
): string {
|
|
27
|
-
const lines: string[] = [];
|
|
28
|
-
|
|
29
|
-
// File name and type
|
|
30
|
-
const fileName = filePath.split(/[/\\]/).pop() || filePath;
|
|
31
|
-
const extension = fileName.split('.').pop() || '';
|
|
32
|
-
lines.push(`**${fileName}** (${this.getFileType(extension)})`);
|
|
33
|
-
|
|
34
|
-
// Purpose (inferred from file name and content)
|
|
35
|
-
const purpose = this.inferPurpose(fileName, content, symbols);
|
|
36
|
-
if (purpose) {
|
|
37
|
-
lines.push(`Purpose: ${purpose}`);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// Exports summary
|
|
41
|
-
if (exports.length > 0) {
|
|
42
|
-
const exportNames = exports.map(e => e.exportedName).slice(0, 10);
|
|
43
|
-
lines.push(`Exports: ${exportNames.join(', ')}${exports.length > 10 ? ` (+${exports.length - 10} more)` : ''}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// Key symbols
|
|
47
|
-
const functions = symbols.filter(s => s.kind === 'function' && s.exported);
|
|
48
|
-
const classes = symbols.filter(s => s.kind === 'class');
|
|
49
|
-
const interfaces = symbols.filter(s => s.kind === 'interface');
|
|
50
|
-
|
|
51
|
-
if (classes.length > 0) {
|
|
52
|
-
lines.push(`Classes: ${classes.map(c => c.name).join(', ')}`);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
if (interfaces.length > 0) {
|
|
56
|
-
lines.push(`Interfaces: ${interfaces.map(i => i.name).slice(0, 5).join(', ')}`);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (functions.length > 0) {
|
|
60
|
-
const funcNames = functions.map(f => f.signature || f.name).slice(0, 5);
|
|
61
|
-
lines.push(`Functions: ${funcNames.join(', ')}${functions.length > 5 ? ` (+${functions.length - 5} more)` : ''}`);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
// Dependencies
|
|
65
|
-
if (imports.length > 0) {
|
|
66
|
-
const deps = imports
|
|
67
|
-
.filter(i => !i.importedFrom.startsWith('.'))
|
|
68
|
-
.map(i => i.importedFrom.split('/')[0])
|
|
69
|
-
.filter((v, i, a) => a.indexOf(v) === i)
|
|
70
|
-
.slice(0, 5);
|
|
71
|
-
|
|
72
|
-
if (deps.length > 0) {
|
|
73
|
-
lines.push(`Uses: ${deps.join(', ')}`);
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
// Local imports (internal dependencies)
|
|
78
|
-
const localImports = imports
|
|
79
|
-
.filter(i => i.importedFrom.startsWith('.'))
|
|
80
|
-
.map(i => i.importedFrom.replace(/^\.\//, '').replace(/\.[^.]+$/, ''))
|
|
81
|
-
.slice(0, 5);
|
|
82
|
-
|
|
83
|
-
if (localImports.length > 0) {
|
|
84
|
-
lines.push(`Imports from: ${localImports.join(', ')}`);
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// Key patterns detected
|
|
88
|
-
const patterns = this.detectPatterns(content);
|
|
89
|
-
if (patterns.length > 0) {
|
|
90
|
-
lines.push(`Patterns: ${patterns.join(', ')}`);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return lines.join('\n');
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
private getFileType(extension: string): string {
|
|
97
|
-
const types: Record<string, string> = {
|
|
98
|
-
'ts': 'TypeScript',
|
|
99
|
-
'tsx': 'React Component',
|
|
100
|
-
'js': 'JavaScript',
|
|
101
|
-
'jsx': 'React Component',
|
|
102
|
-
'py': 'Python',
|
|
103
|
-
'go': 'Go',
|
|
104
|
-
'rs': 'Rust',
|
|
105
|
-
'java': 'Java',
|
|
106
|
-
'rb': 'Ruby',
|
|
107
|
-
'vue': 'Vue Component',
|
|
108
|
-
'svelte': 'Svelte Component',
|
|
109
|
-
};
|
|
110
|
-
return types[extension] || extension.toUpperCase();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
private inferPurpose(fileName: string, content: string, symbols: CodeSymbol[]): string {
|
|
114
|
-
const lowerName = fileName.toLowerCase();
|
|
115
|
-
const lowerContent = content.toLowerCase();
|
|
116
|
-
|
|
117
|
-
// Common file patterns
|
|
118
|
-
if (lowerName.includes('middleware')) return 'Request middleware/interceptor';
|
|
119
|
-
if (lowerName.includes('route') || lowerName.includes('router')) return 'API route definitions';
|
|
120
|
-
if (lowerName.includes('controller')) return 'Request handler/controller';
|
|
121
|
-
if (lowerName.includes('service')) return 'Business logic service';
|
|
122
|
-
if (lowerName.includes('model')) return 'Data model definitions';
|
|
123
|
-
if (lowerName.includes('schema')) return 'Schema/validation definitions';
|
|
124
|
-
if (lowerName.includes('util') || lowerName.includes('helper')) return 'Utility functions';
|
|
125
|
-
if (lowerName.includes('config')) return 'Configuration';
|
|
126
|
-
if (lowerName.includes('constant')) return 'Constants/enums';
|
|
127
|
-
if (lowerName.includes('type') || lowerName.includes('interface')) return 'Type definitions';
|
|
128
|
-
if (lowerName.includes('test') || lowerName.includes('spec')) return 'Tests';
|
|
129
|
-
if (lowerName.includes('hook')) return 'React hooks';
|
|
130
|
-
if (lowerName.includes('context')) return 'React context provider';
|
|
131
|
-
if (lowerName.includes('store')) return 'State management';
|
|
132
|
-
if (lowerName.includes('api')) return 'API client/definitions';
|
|
133
|
-
if (lowerName.includes('auth')) return 'Authentication';
|
|
134
|
-
if (lowerName.includes('db') || lowerName.includes('database')) return 'Database operations';
|
|
135
|
-
|
|
136
|
-
// Content-based inference
|
|
137
|
-
if (lowerContent.includes('express') && lowerContent.includes('router')) return 'Express router';
|
|
138
|
-
if (lowerContent.includes('mongoose') || lowerContent.includes('prisma')) return 'Database model';
|
|
139
|
-
if (lowerContent.includes('usestate') || lowerContent.includes('useeffect')) return 'React component';
|
|
140
|
-
if (lowerContent.includes('describe(') && lowerContent.includes('it(')) return 'Test suite';
|
|
141
|
-
|
|
142
|
-
// Symbol-based inference
|
|
143
|
-
const hasClasses = symbols.some(s => s.kind === 'class');
|
|
144
|
-
const hasInterfaces = symbols.some(s => s.kind === 'interface');
|
|
145
|
-
const hasFunctions = symbols.some(s => s.kind === 'function');
|
|
146
|
-
|
|
147
|
-
if (hasInterfaces && !hasClasses && !hasFunctions) return 'Type definitions';
|
|
148
|
-
if (hasClasses && symbols.filter(s => s.kind === 'class').length === 1) {
|
|
149
|
-
const className = symbols.find(s => s.kind === 'class')?.name;
|
|
150
|
-
return `${className} implementation`;
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
return '';
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
private detectPatterns(content: string): string[] {
|
|
157
|
-
const patterns: string[] = [];
|
|
158
|
-
const lower = content.toLowerCase();
|
|
159
|
-
|
|
160
|
-
// Architectural patterns
|
|
161
|
-
if (lower.includes('singleton') || /private\s+static\s+instance/i.test(content)) {
|
|
162
|
-
patterns.push('Singleton');
|
|
163
|
-
}
|
|
164
|
-
if (lower.includes('factory') || /create\w+\s*\(/i.test(content)) {
|
|
165
|
-
patterns.push('Factory');
|
|
166
|
-
}
|
|
167
|
-
if (lower.includes('observer') || lower.includes('subscriber') || lower.includes('eventemitter')) {
|
|
168
|
-
patterns.push('Observer');
|
|
169
|
-
}
|
|
170
|
-
if (/async\s+\*|yield\s+/i.test(content)) {
|
|
171
|
-
patterns.push('Generator');
|
|
172
|
-
}
|
|
173
|
-
if (lower.includes('decorator') || /@\w+\s*\(/i.test(content)) {
|
|
174
|
-
patterns.push('Decorator');
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
// Code patterns
|
|
178
|
-
if (lower.includes('try') && lower.includes('catch')) {
|
|
179
|
-
patterns.push('Error handling');
|
|
180
|
-
}
|
|
181
|
-
if (lower.includes('async') && lower.includes('await')) {
|
|
182
|
-
patterns.push('Async/await');
|
|
183
|
-
}
|
|
184
|
-
if (/\.then\s*\(/i.test(content)) {
|
|
185
|
-
patterns.push('Promise chain');
|
|
186
|
-
}
|
|
187
|
-
if (lower.includes('middleware')) {
|
|
188
|
-
patterns.push('Middleware');
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
return patterns.slice(0, 4);
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// Store summary in database
|
|
195
|
-
storeSummary(fileId: number, summary: string): void {
|
|
196
|
-
const tokens = estimateTokens(summary);
|
|
197
|
-
|
|
198
|
-
const stmt = this.db.prepare(`
|
|
199
|
-
INSERT INTO file_summaries (file_id, summary, summary_tokens, generated_at)
|
|
200
|
-
VALUES (?, ?, ?, unixepoch())
|
|
201
|
-
ON CONFLICT(file_id) DO UPDATE SET
|
|
202
|
-
summary = excluded.summary,
|
|
203
|
-
summary_tokens = excluded.summary_tokens,
|
|
204
|
-
generated_at = unixepoch()
|
|
205
|
-
`);
|
|
206
|
-
stmt.run(fileId, summary, tokens);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
// Get summary from database
|
|
210
|
-
getSummary(fileId: number): FileSummary | null {
|
|
211
|
-
const stmt = this.db.prepare(`
|
|
212
|
-
SELECT file_id as fileId, summary, summary_tokens as tokens, generated_at as generatedAt
|
|
213
|
-
FROM file_summaries
|
|
214
|
-
WHERE file_id = ?
|
|
215
|
-
`);
|
|
216
|
-
return stmt.get(fileId) as FileSummary | null;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// Get summaries for multiple files
|
|
220
|
-
getSummaries(fileIds: number[]): Map<number, string> {
|
|
221
|
-
const result = new Map<number, string>();
|
|
222
|
-
|
|
223
|
-
if (fileIds.length === 0) return result;
|
|
224
|
-
|
|
225
|
-
const placeholders = fileIds.map(() => '?').join(',');
|
|
226
|
-
const stmt = this.db.prepare(`
|
|
227
|
-
SELECT file_id, summary
|
|
228
|
-
FROM file_summaries
|
|
229
|
-
WHERE file_id IN (${placeholders})
|
|
230
|
-
`);
|
|
231
|
-
const rows = stmt.all(...fileIds) as Array<{ file_id: number; summary: string }>;
|
|
232
|
-
|
|
233
|
-
for (const row of rows) {
|
|
234
|
-
result.set(row.file_id, row.summary);
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return result;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// Check if summary needs regeneration
|
|
241
|
-
needsRegeneration(fileId: number, fileLastModified: number): boolean {
|
|
242
|
-
const summary = this.getSummary(fileId);
|
|
243
|
-
if (!summary) return true;
|
|
244
|
-
|
|
245
|
-
// Regenerate if file was modified after summary was generated
|
|
246
|
-
return fileLastModified > summary.generatedAt;
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// Invalidate summary when file changes (event-driven)
|
|
250
|
-
invalidateSummary(fileId: number): boolean {
|
|
251
|
-
try {
|
|
252
|
-
const result = this.db.prepare('DELETE FROM file_summaries WHERE file_id = ?').run(fileId);
|
|
253
|
-
return result.changes > 0;
|
|
254
|
-
} catch {
|
|
255
|
-
return false;
|
|
256
|
-
}
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
// Invalidate summary by file path
|
|
260
|
-
invalidateSummaryByPath(filePath: string): boolean {
|
|
261
|
-
try {
|
|
262
|
-
const file = this.db.prepare('SELECT id FROM files WHERE path = ?').get(filePath) as { id: number } | undefined;
|
|
263
|
-
if (file) {
|
|
264
|
-
return this.invalidateSummary(file.id);
|
|
265
|
-
}
|
|
266
|
-
return false;
|
|
267
|
-
} catch {
|
|
268
|
-
return false;
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
// Get compression ratio stats
|
|
273
|
-
getCompressionStats(): { totalFiles: number; avgCompression: number; totalTokensSaved: number } {
|
|
274
|
-
const stmt = this.db.prepare(`
|
|
275
|
-
SELECT
|
|
276
|
-
COUNT(*) as total_files,
|
|
277
|
-
AVG(f.size_bytes / 4.0 / NULLIF(fs.summary_tokens, 0)) as avg_compression,
|
|
278
|
-
SUM(f.size_bytes / 4.0 - fs.summary_tokens) as tokens_saved
|
|
279
|
-
FROM file_summaries fs
|
|
280
|
-
JOIN files f ON fs.file_id = f.id
|
|
281
|
-
`);
|
|
282
|
-
const result = stmt.get() as { total_files: number; avg_compression: number; tokens_saved: number };
|
|
283
|
-
|
|
284
|
-
return {
|
|
285
|
-
totalFiles: result.total_files || 0,
|
|
286
|
-
avgCompression: result.avg_compression || 1,
|
|
287
|
-
totalTokensSaved: result.tokens_saved || 0
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
}
|