@git.zone/tsdoc 1.11.1 → 1.11.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/dist_ts/aidocs_classes/commit.js +27 -34
  2. package/dist_ts/aidocs_classes/description.js +68 -29
  3. package/dist_ts/aidocs_classes/projectcontext.d.ts +5 -5
  4. package/dist_ts/aidocs_classes/projectcontext.js +8 -16
  5. package/dist_ts/aidocs_classes/readme.js +156 -88
  6. package/dist_ts/classes.aidoc.d.ts +10 -6
  7. package/dist_ts/classes.aidoc.js +17 -11
  8. package/dist_ts/classes.diffprocessor.js +284 -0
  9. package/dist_ts/cli.js +21 -92
  10. package/dist_ts/plugins.d.ts +1 -2
  11. package/dist_ts/plugins.js +2 -3
  12. package/package.json +2 -3
  13. package/ts/aidocs_classes/commit.ts +26 -41
  14. package/ts/aidocs_classes/description.ts +72 -34
  15. package/ts/aidocs_classes/projectcontext.ts +7 -14
  16. package/ts/aidocs_classes/readme.ts +168 -93
  17. package/ts/classes.aidoc.ts +18 -11
  18. package/ts/cli.ts +20 -100
  19. package/ts/plugins.ts +1 -2
  20. package/dist_ts/context/config-manager.d.ts +0 -83
  21. package/dist_ts/context/config-manager.js +0 -318
  22. package/dist_ts/context/context-analyzer.d.ts +0 -73
  23. package/dist_ts/context/context-analyzer.js +0 -311
  24. package/dist_ts/context/context-cache.d.ts +0 -73
  25. package/dist_ts/context/context-cache.js +0 -239
  26. package/dist_ts/context/context-trimmer.d.ts +0 -60
  27. package/dist_ts/context/context-trimmer.js +0 -258
  28. package/dist_ts/context/diff-processor.js +0 -284
  29. package/dist_ts/context/enhanced-context.d.ts +0 -73
  30. package/dist_ts/context/enhanced-context.js +0 -275
  31. package/dist_ts/context/index.d.ts +0 -11
  32. package/dist_ts/context/index.js +0 -12
  33. package/dist_ts/context/iterative-context-builder.d.ts +0 -62
  34. package/dist_ts/context/iterative-context-builder.js +0 -395
  35. package/dist_ts/context/lazy-file-loader.d.ts +0 -60
  36. package/dist_ts/context/lazy-file-loader.js +0 -182
  37. package/dist_ts/context/task-context-factory.d.ts +0 -48
  38. package/dist_ts/context/task-context-factory.js +0 -86
  39. package/dist_ts/context/types.d.ts +0 -301
  40. package/dist_ts/context/types.js +0 -2
  41. package/dist_ts/tsdoc.classes.typedoc.d.ts +0 -10
  42. package/dist_ts/tsdoc.classes.typedoc.js +0 -48
  43. package/dist_ts/tsdoc.cli.d.ts +0 -1
  44. package/dist_ts/tsdoc.cli.js +0 -32
  45. package/dist_ts/tsdoc.logging.d.ts +0 -2
  46. package/dist_ts/tsdoc.logging.js +0 -14
  47. package/dist_ts/tsdoc.paths.d.ts +0 -8
  48. package/dist_ts/tsdoc.paths.js +0 -12
  49. package/dist_ts/tsdoc.plugins.d.ts +0 -11
  50. package/dist_ts/tsdoc.plugins.js +0 -15
  51. package/ts/context/config-manager.ts +0 -369
  52. package/ts/context/context-analyzer.ts +0 -391
  53. package/ts/context/context-cache.ts +0 -286
  54. package/ts/context/context-trimmer.ts +0 -310
  55. package/ts/context/enhanced-context.ts +0 -332
  56. package/ts/context/index.ts +0 -70
  57. package/ts/context/iterative-context-builder.ts +0 -512
  58. package/ts/context/lazy-file-loader.ts +0 -207
  59. package/ts/context/task-context-factory.ts +0 -120
  60. package/ts/context/types.ts +0 -324
  61. /package/dist_ts/{context/diff-processor.d.ts → classes.diffprocessor.d.ts} +0 -0
  62. /package/ts/{context/diff-processor.ts → classes.diffprocessor.ts} +0 -0
@@ -1,391 +0,0 @@
1
- import * as plugins from '../plugins.js';
2
- import type {
3
- IFileMetadata,
4
- IFileDependencies,
5
- IFileAnalysis,
6
- IAnalysisResult,
7
- TaskType,
8
- IPrioritizationWeights,
9
- ITierConfig,
10
- } from './types.js';
11
-
12
- /**
13
- * ContextAnalyzer provides intelligent file selection and prioritization
14
- * based on dependency analysis, task relevance, and configurable weights
15
- */
16
- export class ContextAnalyzer {
17
- private projectRoot: string;
18
- private weights: Required<IPrioritizationWeights>;
19
- private tiers: Required<ITierConfig>;
20
-
21
- /**
22
- * Creates a new ContextAnalyzer
23
- * @param projectRoot - Root directory of the project
24
- * @param weights - Prioritization weights
25
- * @param tiers - Tier configuration
26
- */
27
- constructor(
28
- projectRoot: string,
29
- weights: Partial<IPrioritizationWeights> = {},
30
- tiers: Partial<ITierConfig> = {}
31
- ) {
32
- this.projectRoot = projectRoot;
33
-
34
- // Default weights
35
- this.weights = {
36
- dependencyWeight: weights.dependencyWeight ?? 0.3,
37
- relevanceWeight: weights.relevanceWeight ?? 0.4,
38
- efficiencyWeight: weights.efficiencyWeight ?? 0.2,
39
- recencyWeight: weights.recencyWeight ?? 0.1,
40
- };
41
-
42
- // Default tiers
43
- this.tiers = {
44
- essential: tiers.essential ?? { minScore: 0.8, trimLevel: 'none' },
45
- important: tiers.important ?? { minScore: 0.5, trimLevel: 'light' },
46
- optional: tiers.optional ?? { minScore: 0.2, trimLevel: 'aggressive' },
47
- };
48
- }
49
-
50
- /**
51
- * Analyzes files for a specific task type
52
- * @param metadata - Array of file metadata to analyze
53
- * @param taskType - Type of task being performed
54
- * @param changedFiles - Optional list of recently changed files (for commits)
55
- * @returns Analysis result with scored files
56
- */
57
- public async analyze(
58
- metadata: IFileMetadata[],
59
- taskType: TaskType,
60
- changedFiles: string[] = []
61
- ): Promise<IAnalysisResult> {
62
- const startTime = Date.now();
63
-
64
- // Build dependency graph
65
- const dependencyGraph = await this.buildDependencyGraph(metadata);
66
-
67
- // Calculate centrality scores
68
- this.calculateCentrality(dependencyGraph);
69
-
70
- // Analyze each file
71
- const files: IFileAnalysis[] = [];
72
- for (const meta of metadata) {
73
- const analysis = await this.analyzeFile(
74
- meta,
75
- taskType,
76
- dependencyGraph,
77
- changedFiles
78
- );
79
- files.push(analysis);
80
- }
81
-
82
- // Sort by importance score (highest first)
83
- files.sort((a, b) => b.importanceScore - a.importanceScore);
84
-
85
- const analysisDuration = Date.now() - startTime;
86
-
87
- return {
88
- taskType,
89
- files,
90
- dependencyGraph,
91
- totalFiles: metadata.length,
92
- analysisDuration,
93
- };
94
- }
95
-
96
- /**
97
- * Builds a dependency graph from file metadata
98
- * @param metadata - Array of file metadata
99
- * @returns Dependency graph as a map
100
- */
101
- private async buildDependencyGraph(
102
- metadata: IFileMetadata[]
103
- ): Promise<Map<string, IFileDependencies>> {
104
- const graph = new Map<string, IFileDependencies>();
105
-
106
- // Initialize graph entries
107
- for (const meta of metadata) {
108
- graph.set(meta.path, {
109
- path: meta.path,
110
- imports: [],
111
- importedBy: [],
112
- centrality: 0,
113
- });
114
- }
115
-
116
- // Parse imports from each file
117
- for (const meta of metadata) {
118
- try {
119
- const contents = await plugins.fsInstance.file(meta.path).encoding('utf8').read() as string;
120
- const imports = this.extractImports(contents, meta.path);
121
-
122
- const deps = graph.get(meta.path)!;
123
- deps.imports = imports;
124
-
125
- // Update importedBy for imported files
126
- for (const importPath of imports) {
127
- const importedDeps = graph.get(importPath);
128
- if (importedDeps) {
129
- importedDeps.importedBy.push(meta.path);
130
- }
131
- }
132
- } catch (error) {
133
- console.warn(`Failed to parse imports from ${meta.path}:`, error.message);
134
- }
135
- }
136
-
137
- return graph;
138
- }
139
-
140
- /**
141
- * Extracts import statements from file contents
142
- * @param contents - File contents
143
- * @param filePath - Path of the file being analyzed
144
- * @returns Array of absolute paths to imported files
145
- */
146
- private extractImports(contents: string, filePath: string): string[] {
147
- const imports: string[] = [];
148
- const fileDir = plugins.path.dirname(filePath);
149
-
150
- // Match various import patterns
151
- const importRegex = /(?:import|export).*?from\s+['"](.+?)['"]/g;
152
- let match;
153
-
154
- while ((match = importRegex.exec(contents)) !== null) {
155
- const importPath = match[1];
156
-
157
- // Skip external modules
158
- if (!importPath.startsWith('.')) {
159
- continue;
160
- }
161
-
162
- // Resolve relative import to absolute path
163
- let resolvedPath = plugins.path.resolve(fileDir, importPath);
164
-
165
- // Handle various file extensions
166
- const extensions = ['.ts', '.js', '.tsx', '.jsx', '/index.ts', '/index.js'];
167
- let found = false;
168
-
169
- for (const ext of extensions) {
170
- const testPath = resolvedPath.endsWith(ext) ? resolvedPath : resolvedPath + ext;
171
- try {
172
- // Use synchronous file check to avoid async in this context
173
- const fs = require('fs');
174
- const exists = fs.existsSync(testPath);
175
- if (exists) {
176
- imports.push(testPath);
177
- found = true;
178
- break;
179
- }
180
- } catch (error) {
181
- // Continue trying other extensions
182
- }
183
- }
184
-
185
- if (!found && !resolvedPath.includes('.')) {
186
- // Try with .ts extension as default
187
- imports.push(resolvedPath + '.ts');
188
- }
189
- }
190
-
191
- return imports;
192
- }
193
-
194
- /**
195
- * Calculates centrality scores for all nodes in the dependency graph
196
- * Uses a simplified PageRank-like algorithm
197
- * @param graph - Dependency graph
198
- */
199
- private calculateCentrality(graph: Map<string, IFileDependencies>): void {
200
- const damping = 0.85;
201
- const iterations = 10;
202
- const nodeCount = graph.size;
203
-
204
- // Initialize scores
205
- const scores = new Map<string, number>();
206
- for (const path of graph.keys()) {
207
- scores.set(path, 1.0 / nodeCount);
208
- }
209
-
210
- // Iterative calculation
211
- for (let i = 0; i < iterations; i++) {
212
- const newScores = new Map<string, number>();
213
-
214
- for (const [path, deps] of graph.entries()) {
215
- let score = (1 - damping) / nodeCount;
216
-
217
- // Add contributions from nodes that import this file
218
- for (const importerPath of deps.importedBy) {
219
- const importerDeps = graph.get(importerPath);
220
- if (importerDeps) {
221
- const importerScore = scores.get(importerPath) ?? 0;
222
- const outgoingCount = importerDeps.imports.length || 1;
223
- score += damping * (importerScore / outgoingCount);
224
- }
225
- }
226
-
227
- newScores.set(path, score);
228
- }
229
-
230
- // Update scores
231
- for (const [path, score] of newScores) {
232
- scores.set(path, score);
233
- }
234
- }
235
-
236
- // Normalize scores to 0-1 range
237
- const maxScore = Math.max(...scores.values());
238
- if (maxScore > 0) {
239
- for (const deps of graph.values()) {
240
- const score = scores.get(deps.path) ?? 0;
241
- deps.centrality = score / maxScore;
242
- }
243
- }
244
- }
245
-
246
- /**
247
- * Analyzes a single file
248
- * @param meta - File metadata
249
- * @param taskType - Task being performed
250
- * @param graph - Dependency graph
251
- * @param changedFiles - Recently changed files
252
- * @returns File analysis
253
- */
254
- private async analyzeFile(
255
- meta: IFileMetadata,
256
- taskType: TaskType,
257
- graph: Map<string, IFileDependencies>,
258
- changedFiles: string[]
259
- ): Promise<IFileAnalysis> {
260
- const deps = graph.get(meta.path);
261
- const centralityScore = deps?.centrality ?? 0;
262
-
263
- // Calculate task-specific relevance
264
- const relevanceScore = this.calculateRelevance(meta, taskType);
265
-
266
- // Calculate efficiency (information per token)
267
- const efficiencyScore = this.calculateEfficiency(meta);
268
-
269
- // Calculate recency (for commit tasks)
270
- const recencyScore = this.calculateRecency(meta, changedFiles);
271
-
272
- // Calculate combined importance score
273
- const importanceScore =
274
- relevanceScore * this.weights.relevanceWeight +
275
- centralityScore * this.weights.dependencyWeight +
276
- efficiencyScore * this.weights.efficiencyWeight +
277
- recencyScore * this.weights.recencyWeight;
278
-
279
- // Assign tier
280
- const tier = this.assignTier(importanceScore);
281
-
282
- return {
283
- path: meta.path,
284
- relevanceScore,
285
- centralityScore,
286
- efficiencyScore,
287
- recencyScore,
288
- importanceScore,
289
- tier,
290
- reason: this.generateReason(meta, taskType, importanceScore, tier),
291
- };
292
- }
293
-
294
- /**
295
- * Calculates task-specific relevance score
296
- */
297
- private calculateRelevance(meta: IFileMetadata, taskType: TaskType): number {
298
- const relativePath = meta.relativePath.toLowerCase();
299
- let score = 0.5; // Base score
300
-
301
- // README generation - prioritize public APIs and main exports
302
- if (taskType === 'readme') {
303
- if (relativePath.includes('index.ts')) score += 0.3;
304
- if (relativePath.match(/^ts\/[^\/]+\.ts$/)) score += 0.2; // Root level exports
305
- if (relativePath.includes('test/')) score -= 0.3;
306
- if (relativePath.includes('classes/')) score += 0.1;
307
- if (relativePath.includes('interfaces/')) score += 0.1;
308
- }
309
-
310
- // Commit messages - prioritize changed files and their dependencies
311
- if (taskType === 'commit') {
312
- if (relativePath.includes('test/')) score -= 0.2;
313
- // Recency will handle changed files
314
- }
315
-
316
- // Description generation - prioritize main exports and core interfaces
317
- if (taskType === 'description') {
318
- if (relativePath.includes('index.ts')) score += 0.4;
319
- if (relativePath.match(/^ts\/[^\/]+\.ts$/)) score += 0.3;
320
- if (relativePath.includes('test/')) score -= 0.4;
321
- if (relativePath.includes('interfaces/')) score += 0.2;
322
- }
323
-
324
- return Math.max(0, Math.min(1, score));
325
- }
326
-
327
- /**
328
- * Calculates efficiency score (information density)
329
- */
330
- private calculateEfficiency(meta: IFileMetadata): number {
331
- // Prefer files that are not too large (good signal-to-noise ratio)
332
- const optimalSize = 5000; // ~1250 tokens
333
- const distance = Math.abs(meta.estimatedTokens - optimalSize);
334
- const normalized = Math.max(0, 1 - distance / optimalSize);
335
-
336
- return normalized;
337
- }
338
-
339
- /**
340
- * Calculates recency score for changed files
341
- */
342
- private calculateRecency(meta: IFileMetadata, changedFiles: string[]): number {
343
- if (changedFiles.length === 0) {
344
- return 0;
345
- }
346
-
347
- // Check if this file was changed
348
- const isChanged = changedFiles.some((changed) => changed === meta.path);
349
-
350
- return isChanged ? 1.0 : 0.0;
351
- }
352
-
353
- /**
354
- * Assigns a tier based on importance score
355
- */
356
- private assignTier(score: number): 'essential' | 'important' | 'optional' | 'excluded' {
357
- if (score >= this.tiers.essential.minScore) return 'essential';
358
- if (score >= this.tiers.important.minScore) return 'important';
359
- if (score >= this.tiers.optional.minScore) return 'optional';
360
- return 'excluded';
361
- }
362
-
363
- /**
364
- * Generates a human-readable reason for the score
365
- */
366
- private generateReason(
367
- meta: IFileMetadata,
368
- taskType: TaskType,
369
- score: number,
370
- tier: string
371
- ): string {
372
- const reasons: string[] = [];
373
-
374
- if (meta.relativePath.includes('index.ts')) {
375
- reasons.push('main export file');
376
- }
377
-
378
- if (meta.relativePath.includes('test/')) {
379
- reasons.push('test file (lower priority)');
380
- }
381
-
382
- if (taskType === 'readme' && meta.relativePath.match(/^ts\/[^\/]+\.ts$/)) {
383
- reasons.push('root-level module');
384
- }
385
-
386
- reasons.push(`score: ${score.toFixed(2)}`);
387
- reasons.push(`tier: ${tier}`);
388
-
389
- return reasons.join(', ');
390
- }
391
- }
@@ -1,286 +0,0 @@
1
- import * as plugins from '../plugins.js';
2
- import * as fs from 'fs';
3
- import type { ICacheEntry, ICacheConfig } from './types.js';
4
- import { logger } from '../logging.js';
5
-
6
- /**
7
- * ContextCache provides persistent caching of file contents and token counts
8
- * with automatic invalidation on file changes
9
- */
10
- export class ContextCache {
11
- private cacheDir: string;
12
- private cache: Map<string, ICacheEntry> = new Map();
13
- private config: Required<ICacheConfig>;
14
- private cacheIndexPath: string;
15
-
16
- /**
17
- * Creates a new ContextCache
18
- * @param projectRoot - Root directory of the project
19
- * @param config - Cache configuration
20
- */
21
- constructor(projectRoot: string, config: Partial<ICacheConfig> = {}) {
22
- this.config = {
23
- enabled: config.enabled ?? true,
24
- ttl: config.ttl ?? 3600, // 1 hour default
25
- maxSize: config.maxSize ?? 100, // 100MB default
26
- directory: config.directory ?? plugins.path.join(projectRoot, '.nogit', 'context-cache'),
27
- };
28
-
29
- this.cacheDir = this.config.directory;
30
- this.cacheIndexPath = plugins.path.join(this.cacheDir, 'index.json');
31
- }
32
-
33
- /**
34
- * Initializes the cache by loading from disk
35
- */
36
- public async init(): Promise<void> {
37
- if (!this.config.enabled) {
38
- return;
39
- }
40
-
41
- // Ensure cache directory exists
42
- await plugins.fsInstance.directory(this.cacheDir).recursive().create();
43
-
44
- // Load cache index if it exists
45
- try {
46
- const indexExists = await plugins.fsInstance.file(this.cacheIndexPath).exists();
47
- if (indexExists) {
48
- const indexContent = await plugins.fsInstance.file(this.cacheIndexPath).encoding('utf8').read() as string;
49
- const indexData = JSON.parse(indexContent) as ICacheEntry[];
50
- if (Array.isArray(indexData)) {
51
- for (const entry of indexData) {
52
- this.cache.set(entry.path, entry);
53
- }
54
- }
55
- }
56
- } catch (error) {
57
- console.warn('Failed to load cache index:', error.message);
58
- // Start with empty cache if loading fails
59
- }
60
-
61
- // Clean up expired and invalid entries
62
- await this.cleanup();
63
- }
64
-
65
- /**
66
- * Gets a cached entry if it's still valid
67
- * @param filePath - Absolute path to the file
68
- * @returns Cache entry if valid, null otherwise
69
- */
70
- public async get(filePath: string): Promise<ICacheEntry | null> {
71
- if (!this.config.enabled) {
72
- return null;
73
- }
74
-
75
- const entry = this.cache.get(filePath);
76
- if (!entry) {
77
- return null;
78
- }
79
-
80
- // Check if entry is expired
81
- const now = Date.now();
82
- if (now - entry.cachedAt > this.config.ttl * 1000) {
83
- this.cache.delete(filePath);
84
- return null;
85
- }
86
-
87
- // Check if file has been modified
88
- try {
89
- const stats = await fs.promises.stat(filePath);
90
- const currentMtime = Math.floor(stats.mtimeMs);
91
-
92
- if (currentMtime !== entry.mtime) {
93
- // File has changed, invalidate cache
94
- this.cache.delete(filePath);
95
- return null;
96
- }
97
-
98
- return entry;
99
- } catch (error) {
100
- // File doesn't exist anymore
101
- this.cache.delete(filePath);
102
- return null;
103
- }
104
- }
105
-
106
- /**
107
- * Stores a cache entry
108
- * @param entry - Cache entry to store
109
- */
110
- public async set(entry: ICacheEntry): Promise<void> {
111
- if (!this.config.enabled) {
112
- return;
113
- }
114
-
115
- this.cache.set(entry.path, entry);
116
-
117
- // Check cache size and evict old entries if needed
118
- await this.enforceMaxSize();
119
-
120
- // Persist to disk (async, don't await)
121
- this.persist().catch((error) => {
122
- console.warn('Failed to persist cache:', error.message);
123
- });
124
- }
125
-
126
- /**
127
- * Stores multiple cache entries
128
- * @param entries - Array of cache entries
129
- */
130
- public async setMany(entries: ICacheEntry[]): Promise<void> {
131
- if (!this.config.enabled) {
132
- return;
133
- }
134
-
135
- for (const entry of entries) {
136
- this.cache.set(entry.path, entry);
137
- }
138
-
139
- await this.enforceMaxSize();
140
- await this.persist();
141
- }
142
-
143
- /**
144
- * Checks if a file is cached and valid
145
- * @param filePath - Absolute path to the file
146
- * @returns True if cached and valid
147
- */
148
- public async has(filePath: string): Promise<boolean> {
149
- const entry = await this.get(filePath);
150
- return entry !== null;
151
- }
152
-
153
- /**
154
- * Gets cache statistics
155
- */
156
- public getStats(): {
157
- entries: number;
158
- totalSize: number;
159
- oldestEntry: number | null;
160
- newestEntry: number | null;
161
- } {
162
- let totalSize = 0;
163
- let oldestEntry: number | null = null;
164
- let newestEntry: number | null = null;
165
-
166
- for (const entry of this.cache.values()) {
167
- totalSize += entry.contents.length;
168
-
169
- if (oldestEntry === null || entry.cachedAt < oldestEntry) {
170
- oldestEntry = entry.cachedAt;
171
- }
172
-
173
- if (newestEntry === null || entry.cachedAt > newestEntry) {
174
- newestEntry = entry.cachedAt;
175
- }
176
- }
177
-
178
- return {
179
- entries: this.cache.size,
180
- totalSize,
181
- oldestEntry,
182
- newestEntry,
183
- };
184
- }
185
-
186
- /**
187
- * Clears all cache entries
188
- */
189
- public async clear(): Promise<void> {
190
- this.cache.clear();
191
- await this.persist();
192
- }
193
-
194
- /**
195
- * Clears specific cache entries
196
- * @param filePaths - Array of file paths to clear
197
- */
198
- public async clearPaths(filePaths: string[]): Promise<void> {
199
- for (const path of filePaths) {
200
- this.cache.delete(path);
201
- }
202
- await this.persist();
203
- }
204
-
205
- /**
206
- * Cleans up expired and invalid cache entries
207
- */
208
- private async cleanup(): Promise<void> {
209
- const now = Date.now();
210
- const toDelete: string[] = [];
211
-
212
- for (const [path, entry] of this.cache.entries()) {
213
- // Check expiration
214
- if (now - entry.cachedAt > this.config.ttl * 1000) {
215
- toDelete.push(path);
216
- continue;
217
- }
218
-
219
- // Check if file still exists and hasn't changed
220
- try {
221
- const stats = await fs.promises.stat(path);
222
- const currentMtime = Math.floor(stats.mtimeMs);
223
-
224
- if (currentMtime !== entry.mtime) {
225
- toDelete.push(path);
226
- }
227
- } catch (error) {
228
- // File doesn't exist
229
- toDelete.push(path);
230
- }
231
- }
232
-
233
- for (const path of toDelete) {
234
- this.cache.delete(path);
235
- }
236
-
237
- if (toDelete.length > 0) {
238
- await this.persist();
239
- }
240
- }
241
-
242
- /**
243
- * Enforces maximum cache size by evicting oldest entries
244
- */
245
- private async enforceMaxSize(): Promise<void> {
246
- const stats = this.getStats();
247
- const maxSizeBytes = this.config.maxSize * 1024 * 1024; // Convert MB to bytes
248
-
249
- if (stats.totalSize <= maxSizeBytes) {
250
- return;
251
- }
252
-
253
- // Sort entries by age (oldest first)
254
- const entries = Array.from(this.cache.entries()).sort(
255
- (a, b) => a[1].cachedAt - b[1].cachedAt
256
- );
257
-
258
- // Remove oldest entries until we're under the limit
259
- let currentSize = stats.totalSize;
260
- for (const [path, entry] of entries) {
261
- if (currentSize <= maxSizeBytes) {
262
- break;
263
- }
264
-
265
- currentSize -= entry.contents.length;
266
- this.cache.delete(path);
267
- }
268
- }
269
-
270
- /**
271
- * Persists cache index to disk
272
- */
273
- private async persist(): Promise<void> {
274
- if (!this.config.enabled) {
275
- return;
276
- }
277
-
278
- try {
279
- const entries = Array.from(this.cache.values());
280
- const content = JSON.stringify(entries, null, 2);
281
- await plugins.fsInstance.file(this.cacheIndexPath).encoding('utf8').write(content);
282
- } catch (error) {
283
- console.warn('Failed to persist cache index:', error.message);
284
- }
285
- }
286
- }