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,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';
@@ -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
- }