tokenlean 0.1.0 → 0.3.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.
- package/README.md +44 -1
- package/bin/tl-cache.mjs +168 -0
- package/bin/tl-component.mjs +109 -83
- package/bin/tl-context.mjs +147 -98
- package/bin/tl-diff.mjs +95 -62
- package/bin/tl-entry.mjs +7 -1
- package/bin/tl-impact.mjs +10 -2
- package/bin/tl-related.mjs +122 -75
- package/bin/tl-search.mjs +148 -27
- package/bin/tl-structure.mjs +152 -83
- package/bin/tl-todo.mjs +8 -1
- package/bin/tl-unused.mjs +29 -19
- package/package.json +2 -1
- package/src/cache.mjs +493 -0
- package/src/config.mjs +6 -0
package/src/cache.mjs
ADDED
|
@@ -0,0 +1,493 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared caching system for tokenlean CLI tools
|
|
3
|
+
*
|
|
4
|
+
* Provides disk-based caching with git-based invalidation for expensive
|
|
5
|
+
* ripgrep operations. Falls back to TTL-based invalidation when not in a git repo.
|
|
6
|
+
*
|
|
7
|
+
* Cache storage: ~/.tokenlean/cache/<project-hash>/<key-hash>.json
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* // High-level API (preferred)
|
|
11
|
+
* const result = withCache(
|
|
12
|
+
* { op: 'rg-search', pattern: 'useState', glob: '*.tsx' },
|
|
13
|
+
* () => execSync('rg ...'),
|
|
14
|
+
* { projectRoot }
|
|
15
|
+
* );
|
|
16
|
+
*
|
|
17
|
+
* // Low-level API
|
|
18
|
+
* let data = getCached(key, projectRoot);
|
|
19
|
+
* if (!data) {
|
|
20
|
+
* data = computeExpensiveResult();
|
|
21
|
+
* setCached(key, data, projectRoot);
|
|
22
|
+
* }
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync, unlinkSync, rmSync } from 'fs';
|
|
26
|
+
import { join, dirname } from 'path';
|
|
27
|
+
import { homedir } from 'os';
|
|
28
|
+
import { execSync } from 'child_process';
|
|
29
|
+
import { createHash } from 'crypto';
|
|
30
|
+
import { loadConfig } from './config.mjs';
|
|
31
|
+
|
|
32
|
+
// ─────────────────────────────────────────────────────────────
|
|
33
|
+
// Constants
|
|
34
|
+
// ─────────────────────────────────────────────────────────────
|
|
35
|
+
|
|
36
|
+
const DEFAULT_CACHE_DIR = join(homedir(), '.tokenlean', 'cache');
|
|
37
|
+
const DEFAULT_TTL = 300; // 5 minutes fallback for non-git repos
|
|
38
|
+
const DEFAULT_MAX_SIZE = 100 * 1024 * 1024; // 100MB
|
|
39
|
+
|
|
40
|
+
// ─────────────────────────────────────────────────────────────
|
|
41
|
+
// Configuration
|
|
42
|
+
// ─────────────────────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Get cache configuration from config system
|
|
46
|
+
*/
|
|
47
|
+
export function getCacheConfig() {
|
|
48
|
+
const { config } = loadConfig();
|
|
49
|
+
const cacheConfig = config.cache || {};
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
enabled: cacheConfig.enabled !== false && process.env.TOKENLEAN_CACHE !== '0',
|
|
53
|
+
ttl: cacheConfig.ttl ?? DEFAULT_TTL,
|
|
54
|
+
maxSize: parseSize(cacheConfig.maxSize) ?? DEFAULT_MAX_SIZE,
|
|
55
|
+
location: cacheConfig.location ?? DEFAULT_CACHE_DIR
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parse size string like '100MB' to bytes
|
|
61
|
+
*/
|
|
62
|
+
function parseSize(size) {
|
|
63
|
+
if (typeof size === 'number') return size;
|
|
64
|
+
if (typeof size !== 'string') return null;
|
|
65
|
+
|
|
66
|
+
const match = size.match(/^(\d+(?:\.\d+)?)\s*(B|KB|MB|GB)?$/i);
|
|
67
|
+
if (!match) return null;
|
|
68
|
+
|
|
69
|
+
const num = parseFloat(match[1]);
|
|
70
|
+
const unit = (match[2] || 'B').toUpperCase();
|
|
71
|
+
|
|
72
|
+
const multipliers = {
|
|
73
|
+
'B': 1,
|
|
74
|
+
'KB': 1024,
|
|
75
|
+
'MB': 1024 * 1024,
|
|
76
|
+
'GB': 1024 * 1024 * 1024
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
return Math.floor(num * multipliers[unit]);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ─────────────────────────────────────────────────────────────
|
|
83
|
+
// Hashing Utilities
|
|
84
|
+
// ─────────────────────────────────────────────────────────────
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Create a short hash from any value
|
|
88
|
+
*/
|
|
89
|
+
function hash(value) {
|
|
90
|
+
const str = typeof value === 'string' ? value : JSON.stringify(value);
|
|
91
|
+
return createHash('sha256').update(str).digest('hex').slice(0, 16);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Get hash of project root path for cache directory
|
|
96
|
+
*/
|
|
97
|
+
function getProjectHash(projectRoot) {
|
|
98
|
+
return hash(projectRoot);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Get cache key hash from operation key object
|
|
103
|
+
*/
|
|
104
|
+
function getCacheKeyHash(key) {
|
|
105
|
+
return hash(key);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─────────────────────────────────────────────────────────────
|
|
109
|
+
// Git State Detection
|
|
110
|
+
// ─────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Check if directory is a git repository
|
|
114
|
+
*/
|
|
115
|
+
function isGitRepo(dir) {
|
|
116
|
+
try {
|
|
117
|
+
execSync('git rev-parse --git-dir', { cwd: dir, stdio: 'ignore' });
|
|
118
|
+
return true;
|
|
119
|
+
} catch {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Get current git state (HEAD commit + dirty files)
|
|
126
|
+
* Returns null if not in a git repo
|
|
127
|
+
*/
|
|
128
|
+
export function getGitState(projectRoot) {
|
|
129
|
+
if (!isGitRepo(projectRoot)) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
// Get HEAD commit
|
|
135
|
+
const head = execSync('git rev-parse HEAD', {
|
|
136
|
+
cwd: projectRoot,
|
|
137
|
+
encoding: 'utf-8'
|
|
138
|
+
}).trim();
|
|
139
|
+
|
|
140
|
+
// Get list of modified/untracked files (sorted for consistency)
|
|
141
|
+
const status = execSync('git status --porcelain', {
|
|
142
|
+
cwd: projectRoot,
|
|
143
|
+
encoding: 'utf-8'
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
const dirtyFiles = status
|
|
147
|
+
.split('\n')
|
|
148
|
+
.filter(line => line.trim())
|
|
149
|
+
.map(line => line.slice(3)) // Remove status prefix
|
|
150
|
+
.sort();
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
head,
|
|
154
|
+
dirtyFiles
|
|
155
|
+
};
|
|
156
|
+
} catch {
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Check if git state matches stored state
|
|
163
|
+
*/
|
|
164
|
+
function gitStateMatches(stored, current) {
|
|
165
|
+
if (!stored || !current) return false;
|
|
166
|
+
if (stored.head !== current.head) return false;
|
|
167
|
+
if (stored.dirtyFiles.length !== current.dirtyFiles.length) return false;
|
|
168
|
+
|
|
169
|
+
for (let i = 0; i < stored.dirtyFiles.length; i++) {
|
|
170
|
+
if (stored.dirtyFiles[i] !== current.dirtyFiles[i]) return false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
// ─────────────────────────────────────────────────────────────
|
|
177
|
+
// Cache Directory Management
|
|
178
|
+
// ─────────────────────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get or create cache directory for a project
|
|
182
|
+
*/
|
|
183
|
+
export function getCacheDir(projectRoot) {
|
|
184
|
+
const config = getCacheConfig();
|
|
185
|
+
const projectHash = getProjectHash(projectRoot);
|
|
186
|
+
const cacheDir = join(config.location, projectHash);
|
|
187
|
+
|
|
188
|
+
if (!existsSync(cacheDir)) {
|
|
189
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return cacheDir;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Get cache file path for a key
|
|
197
|
+
*/
|
|
198
|
+
function getCacheFilePath(key, projectRoot) {
|
|
199
|
+
const cacheDir = getCacheDir(projectRoot);
|
|
200
|
+
const keyHash = getCacheKeyHash(key);
|
|
201
|
+
return join(cacheDir, `${keyHash}.json`);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// ─────────────────────────────────────────────────────────────
|
|
205
|
+
// Cache Size Management
|
|
206
|
+
// ─────────────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Get total size of cache directory in bytes
|
|
210
|
+
*/
|
|
211
|
+
function getCacheDirSize(cacheDir) {
|
|
212
|
+
if (!existsSync(cacheDir)) return 0;
|
|
213
|
+
|
|
214
|
+
let total = 0;
|
|
215
|
+
try {
|
|
216
|
+
const files = readdirSync(cacheDir);
|
|
217
|
+
for (const file of files) {
|
|
218
|
+
try {
|
|
219
|
+
const stat = statSync(join(cacheDir, file));
|
|
220
|
+
if (stat.isFile()) {
|
|
221
|
+
total += stat.size;
|
|
222
|
+
}
|
|
223
|
+
} catch { /* skip unreadable files */ }
|
|
224
|
+
}
|
|
225
|
+
} catch { /* directory read error */ }
|
|
226
|
+
|
|
227
|
+
return total;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Get all cache entries with metadata
|
|
232
|
+
*/
|
|
233
|
+
function getCacheEntries(cacheDir) {
|
|
234
|
+
if (!existsSync(cacheDir)) return [];
|
|
235
|
+
|
|
236
|
+
const entries = [];
|
|
237
|
+
try {
|
|
238
|
+
const files = readdirSync(cacheDir).filter(f => f.endsWith('.json'));
|
|
239
|
+
for (const file of files) {
|
|
240
|
+
try {
|
|
241
|
+
const filePath = join(cacheDir, file);
|
|
242
|
+
const stat = statSync(filePath);
|
|
243
|
+
entries.push({
|
|
244
|
+
path: filePath,
|
|
245
|
+
size: stat.size,
|
|
246
|
+
mtime: stat.mtime.getTime()
|
|
247
|
+
});
|
|
248
|
+
} catch { /* skip unreadable files */ }
|
|
249
|
+
}
|
|
250
|
+
} catch { /* directory read error */ }
|
|
251
|
+
|
|
252
|
+
return entries;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/**
|
|
256
|
+
* Remove oldest cache entries until under maxSize
|
|
257
|
+
*/
|
|
258
|
+
function enforceMaxSize(projectRoot) {
|
|
259
|
+
const config = getCacheConfig();
|
|
260
|
+
const cacheDir = getCacheDir(projectRoot);
|
|
261
|
+
|
|
262
|
+
const entries = getCacheEntries(cacheDir);
|
|
263
|
+
let totalSize = entries.reduce((sum, e) => sum + e.size, 0);
|
|
264
|
+
|
|
265
|
+
if (totalSize <= config.maxSize) return;
|
|
266
|
+
|
|
267
|
+
// Sort by modification time (oldest first)
|
|
268
|
+
entries.sort((a, b) => a.mtime - b.mtime);
|
|
269
|
+
|
|
270
|
+
// Remove oldest entries until under limit
|
|
271
|
+
for (const entry of entries) {
|
|
272
|
+
if (totalSize <= config.maxSize) break;
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
unlinkSync(entry.path);
|
|
276
|
+
totalSize -= entry.size;
|
|
277
|
+
} catch { /* skip if can't delete */ }
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// ─────────────────────────────────────────────────────────────
|
|
282
|
+
// Low-Level Cache API
|
|
283
|
+
// ─────────────────────────────────────────────────────────────
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* Read from cache if valid
|
|
287
|
+
* Returns cached data or null if cache miss/invalid
|
|
288
|
+
*/
|
|
289
|
+
export function getCached(key, projectRoot) {
|
|
290
|
+
const config = getCacheConfig();
|
|
291
|
+
if (!config.enabled) return null;
|
|
292
|
+
|
|
293
|
+
const filePath = getCacheFilePath(key, projectRoot);
|
|
294
|
+
if (!existsSync(filePath)) return null;
|
|
295
|
+
|
|
296
|
+
try {
|
|
297
|
+
const cached = JSON.parse(readFileSync(filePath, 'utf-8'));
|
|
298
|
+
|
|
299
|
+
// Git-based invalidation
|
|
300
|
+
const currentGitState = getGitState(projectRoot);
|
|
301
|
+
if (currentGitState) {
|
|
302
|
+
// If we have git state, use it for validation
|
|
303
|
+
if (!gitStateMatches(cached.gitState, currentGitState)) {
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
} else {
|
|
307
|
+
// Fall back to TTL-based invalidation
|
|
308
|
+
const age = (Date.now() - cached.timestamp) / 1000;
|
|
309
|
+
if (age > config.ttl) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return cached.data;
|
|
315
|
+
} catch {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Write to cache with git state
|
|
322
|
+
*/
|
|
323
|
+
export function setCached(key, data, projectRoot) {
|
|
324
|
+
const config = getCacheConfig();
|
|
325
|
+
if (!config.enabled) return;
|
|
326
|
+
|
|
327
|
+
const filePath = getCacheFilePath(key, projectRoot);
|
|
328
|
+
const gitState = getGitState(projectRoot);
|
|
329
|
+
|
|
330
|
+
const cacheEntry = {
|
|
331
|
+
data,
|
|
332
|
+
gitState,
|
|
333
|
+
timestamp: Date.now(),
|
|
334
|
+
key: typeof key === 'string' ? key : JSON.stringify(key)
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
// Ensure directory exists
|
|
339
|
+
const dir = dirname(filePath);
|
|
340
|
+
if (!existsSync(dir)) {
|
|
341
|
+
mkdirSync(dir, { recursive: true });
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
writeFileSync(filePath, JSON.stringify(cacheEntry));
|
|
345
|
+
|
|
346
|
+
// Enforce size limit
|
|
347
|
+
enforceMaxSize(projectRoot);
|
|
348
|
+
} catch {
|
|
349
|
+
// Silently fail - caching is best-effort
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ─────────────────────────────────────────────────────────────
|
|
354
|
+
// High-Level Cache API
|
|
355
|
+
// ─────────────────────────────────────────────────────────────
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Execute function with caching
|
|
359
|
+
* Preferred API for caching expensive operations
|
|
360
|
+
*
|
|
361
|
+
* @param {Object|string} key - Cache key (operation + args)
|
|
362
|
+
* @param {Function} fn - Function to execute if cache miss
|
|
363
|
+
* @param {Object} options - Options including projectRoot
|
|
364
|
+
* @returns {*} Cached or computed result
|
|
365
|
+
*/
|
|
366
|
+
export function withCache(key, fn, options = {}) {
|
|
367
|
+
const { projectRoot = process.cwd() } = options;
|
|
368
|
+
|
|
369
|
+
// Check cache first
|
|
370
|
+
const cached = getCached(key, projectRoot);
|
|
371
|
+
if (cached !== null) {
|
|
372
|
+
return cached;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Execute function and cache result
|
|
376
|
+
const result = fn();
|
|
377
|
+
setCached(key, result, projectRoot);
|
|
378
|
+
|
|
379
|
+
return result;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// ─────────────────────────────────────────────────────────────
|
|
383
|
+
// Cache Management Utilities
|
|
384
|
+
// ─────────────────────────────────────────────────────────────
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Clear cache for a project or all projects
|
|
388
|
+
* @param {string|null} projectRoot - Project to clear, or null for all
|
|
389
|
+
*/
|
|
390
|
+
export function clearCache(projectRoot = null) {
|
|
391
|
+
const config = getCacheConfig();
|
|
392
|
+
|
|
393
|
+
if (projectRoot) {
|
|
394
|
+
// Clear single project cache
|
|
395
|
+
const cacheDir = getCacheDir(projectRoot);
|
|
396
|
+
if (existsSync(cacheDir)) {
|
|
397
|
+
try {
|
|
398
|
+
rmSync(cacheDir, { recursive: true });
|
|
399
|
+
} catch { /* ignore errors */ }
|
|
400
|
+
}
|
|
401
|
+
} else {
|
|
402
|
+
// Clear all caches
|
|
403
|
+
if (existsSync(config.location)) {
|
|
404
|
+
try {
|
|
405
|
+
rmSync(config.location, { recursive: true });
|
|
406
|
+
} catch { /* ignore errors */ }
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Get cache statistics
|
|
413
|
+
*/
|
|
414
|
+
export function getCacheStats(projectRoot = null) {
|
|
415
|
+
const config = getCacheConfig();
|
|
416
|
+
|
|
417
|
+
if (projectRoot) {
|
|
418
|
+
// Stats for single project
|
|
419
|
+
const cacheDir = getCacheDir(projectRoot);
|
|
420
|
+
const entries = getCacheEntries(cacheDir);
|
|
421
|
+
const totalSize = entries.reduce((sum, e) => sum + e.size, 0);
|
|
422
|
+
|
|
423
|
+
return {
|
|
424
|
+
enabled: config.enabled,
|
|
425
|
+
location: cacheDir,
|
|
426
|
+
entries: entries.length,
|
|
427
|
+
size: totalSize,
|
|
428
|
+
sizeFormatted: formatSize(totalSize),
|
|
429
|
+
maxSize: config.maxSize,
|
|
430
|
+
maxSizeFormatted: formatSize(config.maxSize)
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
// Stats for all projects
|
|
435
|
+
if (!existsSync(config.location)) {
|
|
436
|
+
return {
|
|
437
|
+
enabled: config.enabled,
|
|
438
|
+
location: config.location,
|
|
439
|
+
projects: 0,
|
|
440
|
+
totalEntries: 0,
|
|
441
|
+
totalSize: 0,
|
|
442
|
+
totalSizeFormatted: '0 B',
|
|
443
|
+
maxSize: config.maxSize,
|
|
444
|
+
maxSizeFormatted: formatSize(config.maxSize)
|
|
445
|
+
};
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
let totalEntries = 0;
|
|
449
|
+
let totalSize = 0;
|
|
450
|
+
let projects = 0;
|
|
451
|
+
|
|
452
|
+
try {
|
|
453
|
+
const projectDirs = readdirSync(config.location);
|
|
454
|
+
for (const dir of projectDirs) {
|
|
455
|
+
const projectDir = join(config.location, dir);
|
|
456
|
+
try {
|
|
457
|
+
if (statSync(projectDir).isDirectory()) {
|
|
458
|
+
projects++;
|
|
459
|
+
const entries = getCacheEntries(projectDir);
|
|
460
|
+
totalEntries += entries.length;
|
|
461
|
+
totalSize += entries.reduce((sum, e) => sum + e.size, 0);
|
|
462
|
+
}
|
|
463
|
+
} catch { /* skip */ }
|
|
464
|
+
}
|
|
465
|
+
} catch { /* location doesn't exist yet */ }
|
|
466
|
+
|
|
467
|
+
return {
|
|
468
|
+
enabled: config.enabled,
|
|
469
|
+
location: config.location,
|
|
470
|
+
projects,
|
|
471
|
+
totalEntries,
|
|
472
|
+
totalSize,
|
|
473
|
+
totalSizeFormatted: formatSize(totalSize),
|
|
474
|
+
maxSize: config.maxSize,
|
|
475
|
+
maxSizeFormatted: formatSize(config.maxSize)
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Format bytes to human readable
|
|
481
|
+
*/
|
|
482
|
+
function formatSize(bytes) {
|
|
483
|
+
if (bytes >= 1024 * 1024 * 1024) {
|
|
484
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)} GB`;
|
|
485
|
+
}
|
|
486
|
+
if (bytes >= 1024 * 1024) {
|
|
487
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
488
|
+
}
|
|
489
|
+
if (bytes >= 1024) {
|
|
490
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
491
|
+
}
|
|
492
|
+
return `${bytes} B`;
|
|
493
|
+
}
|
package/src/config.mjs
CHANGED
|
@@ -81,6 +81,12 @@ const DEFAULT_CONFIG = {
|
|
|
81
81
|
},
|
|
82
82
|
impact: {
|
|
83
83
|
depth: 2
|
|
84
|
+
},
|
|
85
|
+
cache: {
|
|
86
|
+
enabled: true, // Enable/disable caching
|
|
87
|
+
ttl: 300, // Max age in seconds (fallback for non-git)
|
|
88
|
+
maxSize: '100MB', // Max cache directory size
|
|
89
|
+
location: null // Override ~/.tokenlean/cache
|
|
84
90
|
}
|
|
85
91
|
};
|
|
86
92
|
|