@git.zone/tsdoc 1.5.2 → 1.6.1
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.
- package/dist_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/context/config-manager.d.ts +22 -1
- package/dist_ts/context/config-manager.js +113 -4
- package/dist_ts/context/context-analyzer.d.ts +73 -0
- package/dist_ts/context/context-analyzer.js +311 -0
- package/dist_ts/context/context-cache.d.ts +73 -0
- package/dist_ts/context/context-cache.js +238 -0
- package/dist_ts/context/context-trimmer.d.ts +8 -0
- package/dist_ts/context/context-trimmer.js +60 -1
- package/dist_ts/context/enhanced-context.d.ts +12 -14
- package/dist_ts/context/enhanced-context.js +130 -127
- package/dist_ts/context/index.d.ts +6 -3
- package/dist_ts/context/index.js +5 -2
- package/dist_ts/context/lazy-file-loader.d.ts +60 -0
- package/dist_ts/context/lazy-file-loader.js +164 -0
- package/dist_ts/context/types.d.ts +142 -0
- package/package.json +1 -1
- package/readme.md +348 -53
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/context/config-manager.ts +146 -15
- package/ts/context/context-analyzer.ts +391 -0
- package/ts/context/context-cache.ts +285 -0
- package/ts/context/context-trimmer.ts +64 -0
- package/ts/context/enhanced-context.ts +169 -180
- package/ts/context/index.ts +31 -5
- package/ts/context/lazy-file-loader.ts +191 -0
- package/ts/context/types.ts +152 -0
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import * as plugins from '../plugins.js';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import type { ICacheEntry, ICacheConfig } from './types.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* ContextCache provides persistent caching of file contents and token counts
|
|
7
|
+
* with automatic invalidation on file changes
|
|
8
|
+
*/
|
|
9
|
+
export class ContextCache {
|
|
10
|
+
private cacheDir: string;
|
|
11
|
+
private cache: Map<string, ICacheEntry> = new Map();
|
|
12
|
+
private config: Required<ICacheConfig>;
|
|
13
|
+
private cacheIndexPath: string;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates a new ContextCache
|
|
17
|
+
* @param projectRoot - Root directory of the project
|
|
18
|
+
* @param config - Cache configuration
|
|
19
|
+
*/
|
|
20
|
+
constructor(projectRoot: string, config: Partial<ICacheConfig> = {}) {
|
|
21
|
+
this.config = {
|
|
22
|
+
enabled: config.enabled ?? true,
|
|
23
|
+
ttl: config.ttl ?? 3600, // 1 hour default
|
|
24
|
+
maxSize: config.maxSize ?? 100, // 100MB default
|
|
25
|
+
directory: config.directory ?? plugins.path.join(projectRoot, '.nogit', 'context-cache'),
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
this.cacheDir = this.config.directory;
|
|
29
|
+
this.cacheIndexPath = plugins.path.join(this.cacheDir, 'index.json');
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Initializes the cache by loading from disk
|
|
34
|
+
*/
|
|
35
|
+
public async init(): Promise<void> {
|
|
36
|
+
if (!this.config.enabled) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Ensure cache directory exists
|
|
41
|
+
await plugins.smartfile.fs.ensureDir(this.cacheDir);
|
|
42
|
+
|
|
43
|
+
// Load cache index if it exists
|
|
44
|
+
try {
|
|
45
|
+
const indexExists = await plugins.smartfile.fs.fileExists(this.cacheIndexPath);
|
|
46
|
+
if (indexExists) {
|
|
47
|
+
const indexContent = await plugins.smartfile.fs.toStringSync(this.cacheIndexPath);
|
|
48
|
+
const indexData = JSON.parse(indexContent) as ICacheEntry[];
|
|
49
|
+
if (Array.isArray(indexData)) {
|
|
50
|
+
for (const entry of indexData) {
|
|
51
|
+
this.cache.set(entry.path, entry);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (error) {
|
|
56
|
+
console.warn('Failed to load cache index:', error.message);
|
|
57
|
+
// Start with empty cache if loading fails
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Clean up expired and invalid entries
|
|
61
|
+
await this.cleanup();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Gets a cached entry if it's still valid
|
|
66
|
+
* @param filePath - Absolute path to the file
|
|
67
|
+
* @returns Cache entry if valid, null otherwise
|
|
68
|
+
*/
|
|
69
|
+
public async get(filePath: string): Promise<ICacheEntry | null> {
|
|
70
|
+
if (!this.config.enabled) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const entry = this.cache.get(filePath);
|
|
75
|
+
if (!entry) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Check if entry is expired
|
|
80
|
+
const now = Date.now();
|
|
81
|
+
if (now - entry.cachedAt > this.config.ttl * 1000) {
|
|
82
|
+
this.cache.delete(filePath);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Check if file has been modified
|
|
87
|
+
try {
|
|
88
|
+
const stats = await fs.promises.stat(filePath);
|
|
89
|
+
const currentMtime = Math.floor(stats.mtimeMs);
|
|
90
|
+
|
|
91
|
+
if (currentMtime !== entry.mtime) {
|
|
92
|
+
// File has changed, invalidate cache
|
|
93
|
+
this.cache.delete(filePath);
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return entry;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
// File doesn't exist anymore
|
|
100
|
+
this.cache.delete(filePath);
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Stores a cache entry
|
|
107
|
+
* @param entry - Cache entry to store
|
|
108
|
+
*/
|
|
109
|
+
public async set(entry: ICacheEntry): Promise<void> {
|
|
110
|
+
if (!this.config.enabled) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
this.cache.set(entry.path, entry);
|
|
115
|
+
|
|
116
|
+
// Check cache size and evict old entries if needed
|
|
117
|
+
await this.enforceMaxSize();
|
|
118
|
+
|
|
119
|
+
// Persist to disk (async, don't await)
|
|
120
|
+
this.persist().catch((error) => {
|
|
121
|
+
console.warn('Failed to persist cache:', error.message);
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Stores multiple cache entries
|
|
127
|
+
* @param entries - Array of cache entries
|
|
128
|
+
*/
|
|
129
|
+
public async setMany(entries: ICacheEntry[]): Promise<void> {
|
|
130
|
+
if (!this.config.enabled) {
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const entry of entries) {
|
|
135
|
+
this.cache.set(entry.path, entry);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
await this.enforceMaxSize();
|
|
139
|
+
await this.persist();
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Checks if a file is cached and valid
|
|
144
|
+
* @param filePath - Absolute path to the file
|
|
145
|
+
* @returns True if cached and valid
|
|
146
|
+
*/
|
|
147
|
+
public async has(filePath: string): Promise<boolean> {
|
|
148
|
+
const entry = await this.get(filePath);
|
|
149
|
+
return entry !== null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Gets cache statistics
|
|
154
|
+
*/
|
|
155
|
+
public getStats(): {
|
|
156
|
+
entries: number;
|
|
157
|
+
totalSize: number;
|
|
158
|
+
oldestEntry: number | null;
|
|
159
|
+
newestEntry: number | null;
|
|
160
|
+
} {
|
|
161
|
+
let totalSize = 0;
|
|
162
|
+
let oldestEntry: number | null = null;
|
|
163
|
+
let newestEntry: number | null = null;
|
|
164
|
+
|
|
165
|
+
for (const entry of this.cache.values()) {
|
|
166
|
+
totalSize += entry.contents.length;
|
|
167
|
+
|
|
168
|
+
if (oldestEntry === null || entry.cachedAt < oldestEntry) {
|
|
169
|
+
oldestEntry = entry.cachedAt;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (newestEntry === null || entry.cachedAt > newestEntry) {
|
|
173
|
+
newestEntry = entry.cachedAt;
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return {
|
|
178
|
+
entries: this.cache.size,
|
|
179
|
+
totalSize,
|
|
180
|
+
oldestEntry,
|
|
181
|
+
newestEntry,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Clears all cache entries
|
|
187
|
+
*/
|
|
188
|
+
public async clear(): Promise<void> {
|
|
189
|
+
this.cache.clear();
|
|
190
|
+
await this.persist();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Clears specific cache entries
|
|
195
|
+
* @param filePaths - Array of file paths to clear
|
|
196
|
+
*/
|
|
197
|
+
public async clearPaths(filePaths: string[]): Promise<void> {
|
|
198
|
+
for (const path of filePaths) {
|
|
199
|
+
this.cache.delete(path);
|
|
200
|
+
}
|
|
201
|
+
await this.persist();
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Cleans up expired and invalid cache entries
|
|
206
|
+
*/
|
|
207
|
+
private async cleanup(): Promise<void> {
|
|
208
|
+
const now = Date.now();
|
|
209
|
+
const toDelete: string[] = [];
|
|
210
|
+
|
|
211
|
+
for (const [path, entry] of this.cache.entries()) {
|
|
212
|
+
// Check expiration
|
|
213
|
+
if (now - entry.cachedAt > this.config.ttl * 1000) {
|
|
214
|
+
toDelete.push(path);
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Check if file still exists and hasn't changed
|
|
219
|
+
try {
|
|
220
|
+
const stats = await fs.promises.stat(path);
|
|
221
|
+
const currentMtime = Math.floor(stats.mtimeMs);
|
|
222
|
+
|
|
223
|
+
if (currentMtime !== entry.mtime) {
|
|
224
|
+
toDelete.push(path);
|
|
225
|
+
}
|
|
226
|
+
} catch (error) {
|
|
227
|
+
// File doesn't exist
|
|
228
|
+
toDelete.push(path);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
for (const path of toDelete) {
|
|
233
|
+
this.cache.delete(path);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
if (toDelete.length > 0) {
|
|
237
|
+
await this.persist();
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Enforces maximum cache size by evicting oldest entries
|
|
243
|
+
*/
|
|
244
|
+
private async enforceMaxSize(): Promise<void> {
|
|
245
|
+
const stats = this.getStats();
|
|
246
|
+
const maxSizeBytes = this.config.maxSize * 1024 * 1024; // Convert MB to bytes
|
|
247
|
+
|
|
248
|
+
if (stats.totalSize <= maxSizeBytes) {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Sort entries by age (oldest first)
|
|
253
|
+
const entries = Array.from(this.cache.entries()).sort(
|
|
254
|
+
(a, b) => a[1].cachedAt - b[1].cachedAt
|
|
255
|
+
);
|
|
256
|
+
|
|
257
|
+
// Remove oldest entries until we're under the limit
|
|
258
|
+
let currentSize = stats.totalSize;
|
|
259
|
+
for (const [path, entry] of entries) {
|
|
260
|
+
if (currentSize <= maxSizeBytes) {
|
|
261
|
+
break;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
currentSize -= entry.contents.length;
|
|
265
|
+
this.cache.delete(path);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Persists cache index to disk
|
|
271
|
+
*/
|
|
272
|
+
private async persist(): Promise<void> {
|
|
273
|
+
if (!this.config.enabled) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
try {
|
|
278
|
+
const entries = Array.from(this.cache.values());
|
|
279
|
+
const content = JSON.stringify(entries, null, 2);
|
|
280
|
+
await plugins.smartfile.memory.toFs(content, this.cacheIndexPath);
|
|
281
|
+
} catch (error) {
|
|
282
|
+
console.warn('Failed to persist cache index:', error.message);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
@@ -243,4 +243,68 @@ export class ContextTrimmer {
|
|
|
243
243
|
...config
|
|
244
244
|
};
|
|
245
245
|
}
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Trim a file based on its importance tier
|
|
249
|
+
* @param filePath The path to the file
|
|
250
|
+
* @param content The file's contents
|
|
251
|
+
* @param level The trimming level to apply ('none', 'light', 'aggressive')
|
|
252
|
+
* @returns The trimmed file contents
|
|
253
|
+
*/
|
|
254
|
+
public trimFileWithLevel(
|
|
255
|
+
filePath: string,
|
|
256
|
+
content: string,
|
|
257
|
+
level: 'none' | 'light' | 'aggressive'
|
|
258
|
+
): string {
|
|
259
|
+
// No trimming for essential files
|
|
260
|
+
if (level === 'none') {
|
|
261
|
+
return content;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Create a temporary config based on level
|
|
265
|
+
const originalConfig = { ...this.config };
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
if (level === 'light') {
|
|
269
|
+
// Light trimming: preserve signatures, remove only complex implementations
|
|
270
|
+
this.config = {
|
|
271
|
+
...this.config,
|
|
272
|
+
removeImplementations: false,
|
|
273
|
+
preserveInterfaces: true,
|
|
274
|
+
preserveTypeDefs: true,
|
|
275
|
+
preserveJSDoc: true,
|
|
276
|
+
maxFunctionLines: 10,
|
|
277
|
+
removeComments: false,
|
|
278
|
+
removeBlankLines: true
|
|
279
|
+
};
|
|
280
|
+
} else if (level === 'aggressive') {
|
|
281
|
+
// Aggressive trimming: remove all implementations, keep only signatures
|
|
282
|
+
this.config = {
|
|
283
|
+
...this.config,
|
|
284
|
+
removeImplementations: true,
|
|
285
|
+
preserveInterfaces: true,
|
|
286
|
+
preserveTypeDefs: true,
|
|
287
|
+
preserveJSDoc: true,
|
|
288
|
+
maxFunctionLines: 3,
|
|
289
|
+
removeComments: true,
|
|
290
|
+
removeBlankLines: true
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Process based on file type
|
|
295
|
+
let result = content;
|
|
296
|
+
if (filePath.endsWith('.ts') || filePath.endsWith('.tsx')) {
|
|
297
|
+
result = this.trimTypeScriptFile(content);
|
|
298
|
+
} else if (filePath.endsWith('.md')) {
|
|
299
|
+
result = this.trimMarkdownFile(content);
|
|
300
|
+
} else if (filePath.endsWith('.json')) {
|
|
301
|
+
result = this.trimJsonFile(content);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return result;
|
|
305
|
+
} finally {
|
|
306
|
+
// Restore original config
|
|
307
|
+
this.config = originalConfig;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
246
310
|
}
|