recker 1.0.12 → 1.0.13

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.
@@ -1,30 +1,453 @@
1
+ import zlib from 'node:zlib';
2
+ import os from 'node:os';
3
+ import { getEffectiveTotalMemoryBytes, resolveCacheMemoryLimit, formatBytes, getHeapStats, } from './memory-limits.js';
1
4
  export class MemoryStorage {
2
5
  storage = new Map();
3
- ttls = new Map();
6
+ meta = new Map();
7
+ maxSize;
8
+ maxMemoryBytes;
9
+ maxMemoryPercent;
10
+ defaultTtl;
11
+ evictionPolicy;
12
+ compressionEnabled;
13
+ compressionThreshold;
14
+ enableStats;
15
+ heapUsageThreshold;
16
+ onEvict;
17
+ onPressure;
18
+ currentMemoryBytes = 0;
19
+ evictedDueToMemory = 0;
20
+ memoryPressureEvents = 0;
21
+ accessCounter = 0;
22
+ monitorHandle = null;
23
+ cleanupHandle = null;
24
+ stats = {
25
+ hits: 0,
26
+ misses: 0,
27
+ sets: 0,
28
+ deletes: 0,
29
+ evictions: 0,
30
+ };
31
+ compressionStats = {
32
+ totalCompressed: 0,
33
+ totalOriginalSize: 0,
34
+ totalCompressedSize: 0,
35
+ };
36
+ constructor(options = {}) {
37
+ if (options.maxMemoryBytes &&
38
+ options.maxMemoryBytes > 0 &&
39
+ options.maxMemoryPercent &&
40
+ options.maxMemoryPercent > 0) {
41
+ throw new Error('[MemoryStorage] Cannot use both maxMemoryBytes and maxMemoryPercent');
42
+ }
43
+ if (options.maxMemoryPercent !== undefined &&
44
+ (options.maxMemoryPercent < 0 || options.maxMemoryPercent > 1)) {
45
+ throw new Error('[MemoryStorage] maxMemoryPercent must be between 0 and 1');
46
+ }
47
+ this.maxSize = options.maxSize ?? 1000;
48
+ this.defaultTtl = options.ttl ?? 300000;
49
+ this.evictionPolicy = options.evictionPolicy ?? 'lru';
50
+ this.enableStats = options.enableStats ?? false;
51
+ this.heapUsageThreshold = options.heapUsageThreshold ?? 0.6;
52
+ if (options.maxMemoryBytes && options.maxMemoryBytes > 0) {
53
+ this.maxMemoryBytes = options.maxMemoryBytes;
54
+ this.maxMemoryPercent = 0;
55
+ }
56
+ else if (options.maxMemoryPercent && options.maxMemoryPercent > 0) {
57
+ const effectiveTotal = getEffectiveTotalMemoryBytes();
58
+ this.maxMemoryBytes = Math.floor(effectiveTotal * options.maxMemoryPercent);
59
+ this.maxMemoryPercent = options.maxMemoryPercent;
60
+ }
61
+ else {
62
+ const resolved = resolveCacheMemoryLimit({});
63
+ this.maxMemoryBytes = resolved.maxMemoryBytes;
64
+ this.maxMemoryPercent = resolved.inferredPercent ?? 0;
65
+ }
66
+ if (options.compression === true) {
67
+ this.compressionEnabled = true;
68
+ this.compressionThreshold = 1024;
69
+ }
70
+ else if (typeof options.compression === 'object' &&
71
+ options.compression.enabled) {
72
+ this.compressionEnabled = true;
73
+ this.compressionThreshold = options.compression.threshold ?? 1024;
74
+ }
75
+ else {
76
+ this.compressionEnabled = false;
77
+ this.compressionThreshold = 1024;
78
+ }
79
+ this.onEvict = options.onEvict;
80
+ this.onPressure = options.onPressure;
81
+ const monitorInterval = options.monitorInterval ?? 15000;
82
+ if (monitorInterval > 0) {
83
+ this.monitorHandle = setInterval(() => this.memoryHealthCheck(), monitorInterval);
84
+ this.monitorHandle.unref();
85
+ }
86
+ const cleanupInterval = options.cleanupInterval ?? 60000;
87
+ if (cleanupInterval > 0) {
88
+ this.cleanupHandle = setInterval(() => this.cleanupExpired(), cleanupInterval);
89
+ this.cleanupHandle.unref();
90
+ }
91
+ }
4
92
  async get(key) {
5
- const entry = this.storage.get(key);
6
- if (!entry) {
93
+ const data = this.storage.get(key);
94
+ const metadata = this.meta.get(key);
95
+ if (!data || !metadata) {
96
+ this.recordStat('misses');
7
97
  return undefined;
8
98
  }
9
- const expiry = this.ttls.get(key);
10
- if (expiry && Date.now() > expiry) {
11
- this.delete(key);
99
+ const now = Date.now();
100
+ if (now > metadata.expiresAt) {
101
+ this.deleteInternal(key);
102
+ this.recordStat('misses');
12
103
  return undefined;
13
104
  }
14
- return entry;
105
+ if (this.evictionPolicy === 'lru') {
106
+ metadata.lastAccess = now;
107
+ metadata.accessOrder = ++this.accessCounter;
108
+ }
109
+ this.recordStat('hits');
110
+ if (this.isCompressed(data)) {
111
+ try {
112
+ const decompressed = this.decompress(data);
113
+ return JSON.parse(decompressed);
114
+ }
115
+ catch {
116
+ this.deleteInternal(key);
117
+ return undefined;
118
+ }
119
+ }
120
+ return JSON.parse(data);
15
121
  }
16
122
  async set(key, entry, ttl) {
17
- this.storage.set(key, entry);
18
- if (ttl) {
19
- this.ttls.set(key, Date.now() + ttl);
123
+ const effectiveTtl = ttl ?? this.defaultTtl;
124
+ const now = Date.now();
125
+ const serialized = JSON.stringify(entry);
126
+ const originalSize = Buffer.byteLength(serialized, 'utf8');
127
+ let finalData = serialized;
128
+ let compressedSize = originalSize;
129
+ let compressed = false;
130
+ if (this.compressionEnabled && originalSize >= this.compressionThreshold) {
131
+ try {
132
+ const result = this.compress(serialized);
133
+ finalData = result;
134
+ compressedSize = Buffer.byteLength(result.__data, 'utf8');
135
+ compressed = true;
136
+ this.compressionStats.totalCompressed++;
137
+ this.compressionStats.totalOriginalSize += originalSize;
138
+ this.compressionStats.totalCompressedSize += compressedSize;
139
+ }
140
+ catch {
141
+ }
20
142
  }
143
+ const existingMeta = this.meta.get(key);
144
+ if (existingMeta) {
145
+ this.currentMemoryBytes -= existingMeta.compressedSize;
146
+ }
147
+ if (!this.enforceMemoryLimit(compressedSize)) {
148
+ this.evictedDueToMemory++;
149
+ return;
150
+ }
151
+ if (!existingMeta && this.storage.size >= this.maxSize) {
152
+ this.evictOne('size');
153
+ }
154
+ this.storage.set(key, finalData);
155
+ this.meta.set(key, {
156
+ createdAt: now,
157
+ expiresAt: now + effectiveTtl,
158
+ lastAccess: now,
159
+ insertOrder: ++this.accessCounter,
160
+ accessOrder: this.accessCounter,
161
+ compressed,
162
+ originalSize,
163
+ compressedSize,
164
+ });
165
+ this.currentMemoryBytes += compressedSize;
166
+ this.recordStat('sets');
21
167
  }
22
168
  async delete(key) {
169
+ this.deleteInternal(key);
170
+ this.recordStat('deletes');
171
+ }
172
+ clear(prefix) {
173
+ if (!prefix) {
174
+ this.storage.clear();
175
+ this.meta.clear();
176
+ this.currentMemoryBytes = 0;
177
+ this.evictedDueToMemory = 0;
178
+ if (this.enableStats) {
179
+ this.stats = { hits: 0, misses: 0, sets: 0, deletes: 0, evictions: 0 };
180
+ }
181
+ return;
182
+ }
183
+ for (const key of this.storage.keys()) {
184
+ if (key.startsWith(prefix)) {
185
+ this.deleteInternal(key);
186
+ }
187
+ }
188
+ }
189
+ size() {
190
+ return this.storage.size;
191
+ }
192
+ keys() {
193
+ return Array.from(this.storage.keys());
194
+ }
195
+ has(key) {
196
+ const meta = this.meta.get(key);
197
+ if (!meta)
198
+ return false;
199
+ if (Date.now() > meta.expiresAt) {
200
+ this.deleteInternal(key);
201
+ return false;
202
+ }
203
+ return true;
204
+ }
205
+ getStats() {
206
+ const total = this.stats.hits + this.stats.misses;
207
+ const hitRate = total > 0 ? this.stats.hits / total : 0;
208
+ return {
209
+ enabled: this.enableStats,
210
+ ...this.stats,
211
+ hitRate,
212
+ totalItems: this.storage.size,
213
+ memoryUsageBytes: this.currentMemoryBytes,
214
+ maxMemoryBytes: this.maxMemoryBytes,
215
+ evictedDueToMemory: this.evictedDueToMemory,
216
+ };
217
+ }
218
+ getMemoryStats() {
219
+ const totalItems = this.storage.size;
220
+ const memoryUsagePercent = this.maxMemoryBytes > 0
221
+ ? (this.currentMemoryBytes / this.maxMemoryBytes) * 100
222
+ : 0;
223
+ const systemTotal = os.totalmem();
224
+ const systemFree = os.freemem();
225
+ const systemUsed = systemTotal - systemFree;
226
+ const cachePercentOfSystem = systemTotal > 0 ? (this.currentMemoryBytes / systemTotal) * 100 : 0;
227
+ return {
228
+ currentMemoryBytes: this.currentMemoryBytes,
229
+ maxMemoryBytes: this.maxMemoryBytes,
230
+ maxMemoryPercent: this.maxMemoryPercent,
231
+ memoryUsagePercent: parseFloat(memoryUsagePercent.toFixed(2)),
232
+ cachePercentOfSystemMemory: parseFloat(cachePercentOfSystem.toFixed(2)),
233
+ totalItems,
234
+ maxSize: this.maxSize,
235
+ evictedDueToMemory: this.evictedDueToMemory,
236
+ memoryPressureEvents: this.memoryPressureEvents,
237
+ averageItemSize: totalItems > 0 ? Math.round(this.currentMemoryBytes / totalItems) : 0,
238
+ memoryUsage: {
239
+ current: formatBytes(this.currentMemoryBytes),
240
+ max: formatBytes(this.maxMemoryBytes),
241
+ available: formatBytes(Math.max(0, this.maxMemoryBytes - this.currentMemoryBytes)),
242
+ },
243
+ systemMemory: {
244
+ total: formatBytes(systemTotal),
245
+ free: formatBytes(systemFree),
246
+ used: formatBytes(systemUsed),
247
+ cachePercent: `${cachePercentOfSystem.toFixed(2)}%`,
248
+ },
249
+ };
250
+ }
251
+ getCompressionStats() {
252
+ if (!this.compressionEnabled) {
253
+ return {
254
+ enabled: false,
255
+ totalItems: this.storage.size,
256
+ compressedItems: 0,
257
+ compressionThreshold: this.compressionThreshold,
258
+ totalOriginalSize: 0,
259
+ totalCompressedSize: 0,
260
+ averageCompressionRatio: '0',
261
+ spaceSavingsPercent: '0',
262
+ memoryUsage: {
263
+ uncompressed: '0 B',
264
+ compressed: '0 B',
265
+ saved: '0 B',
266
+ },
267
+ };
268
+ }
269
+ const ratio = this.compressionStats.totalOriginalSize > 0
270
+ ? (this.compressionStats.totalCompressedSize /
271
+ this.compressionStats.totalOriginalSize).toFixed(2)
272
+ : '0';
273
+ const savings = this.compressionStats.totalOriginalSize > 0
274
+ ? (((this.compressionStats.totalOriginalSize -
275
+ this.compressionStats.totalCompressedSize) /
276
+ this.compressionStats.totalOriginalSize) *
277
+ 100).toFixed(2)
278
+ : '0';
279
+ const saved = this.compressionStats.totalOriginalSize -
280
+ this.compressionStats.totalCompressedSize;
281
+ return {
282
+ enabled: true,
283
+ totalItems: this.storage.size,
284
+ compressedItems: this.compressionStats.totalCompressed,
285
+ compressionThreshold: this.compressionThreshold,
286
+ totalOriginalSize: this.compressionStats.totalOriginalSize,
287
+ totalCompressedSize: this.compressionStats.totalCompressedSize,
288
+ averageCompressionRatio: ratio,
289
+ spaceSavingsPercent: savings,
290
+ memoryUsage: {
291
+ uncompressed: formatBytes(this.compressionStats.totalOriginalSize),
292
+ compressed: formatBytes(this.compressionStats.totalCompressedSize),
293
+ saved: formatBytes(saved),
294
+ },
295
+ };
296
+ }
297
+ shutdown() {
298
+ if (this.monitorHandle) {
299
+ clearInterval(this.monitorHandle);
300
+ this.monitorHandle = null;
301
+ }
302
+ if (this.cleanupHandle) {
303
+ clearInterval(this.cleanupHandle);
304
+ this.cleanupHandle = null;
305
+ }
306
+ }
307
+ deleteInternal(key) {
308
+ const meta = this.meta.get(key);
309
+ if (meta) {
310
+ this.currentMemoryBytes -= meta.compressedSize;
311
+ }
23
312
  this.storage.delete(key);
24
- this.ttls.delete(key);
313
+ this.meta.delete(key);
25
314
  }
26
- clear() {
27
- this.storage.clear();
28
- this.ttls.clear();
315
+ recordStat(type) {
316
+ if (this.enableStats) {
317
+ this.stats[type]++;
318
+ }
319
+ }
320
+ isCompressed(data) {
321
+ return (typeof data === 'object' && data !== null && '__compressed' in data);
322
+ }
323
+ compress(data) {
324
+ const buffer = Buffer.from(data, 'utf8');
325
+ const compressed = zlib.gzipSync(buffer);
326
+ return {
327
+ __compressed: true,
328
+ __data: compressed.toString('base64'),
329
+ __originalSize: buffer.length,
330
+ };
331
+ }
332
+ decompress(data) {
333
+ const buffer = Buffer.from(data.__data, 'base64');
334
+ const decompressed = zlib.gunzipSync(buffer);
335
+ return decompressed.toString('utf8');
336
+ }
337
+ selectEvictionCandidate() {
338
+ if (this.meta.size === 0)
339
+ return null;
340
+ let candidate = null;
341
+ let candidateValue = this.evictionPolicy === 'lru' ? Infinity : Infinity;
342
+ for (const [key, meta] of this.meta) {
343
+ const value = this.evictionPolicy === 'lru' ? meta.accessOrder : meta.insertOrder;
344
+ if (value < candidateValue) {
345
+ candidateValue = value;
346
+ candidate = key;
347
+ }
348
+ }
349
+ return candidate;
350
+ }
351
+ evictOne(reason) {
352
+ const candidate = this.selectEvictionCandidate();
353
+ if (!candidate)
354
+ return null;
355
+ const meta = this.meta.get(candidate);
356
+ const freedBytes = meta?.compressedSize ?? 0;
357
+ this.deleteInternal(candidate);
358
+ this.stats.evictions++;
359
+ if (reason === 'memory' || reason === 'heap') {
360
+ this.evictedDueToMemory++;
361
+ }
362
+ this.onEvict?.({
363
+ reason,
364
+ key: candidate,
365
+ freedBytes,
366
+ currentBytes: this.currentMemoryBytes,
367
+ maxMemoryBytes: this.maxMemoryBytes,
368
+ });
369
+ return { key: candidate, freedBytes };
370
+ }
371
+ enforceMemoryLimit(incomingSize) {
372
+ if (incomingSize > this.maxMemoryBytes) {
373
+ return false;
374
+ }
375
+ while (this.currentMemoryBytes + incomingSize > this.maxMemoryBytes &&
376
+ this.storage.size > 0) {
377
+ const result = this.evictOne('memory');
378
+ if (!result)
379
+ break;
380
+ }
381
+ return this.currentMemoryBytes + incomingSize <= this.maxMemoryBytes;
382
+ }
383
+ reduceMemoryTo(targetBytes) {
384
+ targetBytes = Math.max(0, targetBytes);
385
+ let freedBytes = 0;
386
+ while (this.currentMemoryBytes > targetBytes && this.storage.size > 0) {
387
+ const result = this.evictOne('memory');
388
+ if (!result)
389
+ break;
390
+ freedBytes += result.freedBytes;
391
+ }
392
+ return freedBytes;
393
+ }
394
+ memoryHealthCheck() {
395
+ let totalFreed = 0;
396
+ if (this.currentMemoryBytes > this.maxMemoryBytes) {
397
+ const before = this.currentMemoryBytes;
398
+ this.enforceMemoryLimit(0);
399
+ const freed = before - this.currentMemoryBytes;
400
+ if (freed > 0) {
401
+ totalFreed += freed;
402
+ this.memoryPressureEvents++;
403
+ this.onPressure?.({
404
+ reason: 'limit',
405
+ heapLimit: getHeapStats().heapLimit,
406
+ heapUsed: getHeapStats().heapUsed,
407
+ currentBytes: this.currentMemoryBytes,
408
+ maxMemoryBytes: this.maxMemoryBytes,
409
+ freedBytes: freed,
410
+ });
411
+ }
412
+ }
413
+ const { heapUsed, heapLimit, heapRatio } = getHeapStats();
414
+ if (heapLimit > 0 && heapRatio >= this.heapUsageThreshold) {
415
+ const before = this.currentMemoryBytes;
416
+ const target = Math.floor(this.currentMemoryBytes * 0.5);
417
+ this.reduceMemoryTo(target);
418
+ const freed = before - this.currentMemoryBytes;
419
+ if (freed > 0) {
420
+ totalFreed += freed;
421
+ this.memoryPressureEvents++;
422
+ this.onPressure?.({
423
+ reason: 'heap',
424
+ heapLimit,
425
+ heapUsed,
426
+ heapRatio,
427
+ currentBytes: this.currentMemoryBytes,
428
+ maxMemoryBytes: this.maxMemoryBytes,
429
+ freedBytes: freed,
430
+ });
431
+ }
432
+ }
433
+ return totalFreed;
434
+ }
435
+ cleanupExpired() {
436
+ const now = Date.now();
437
+ let cleaned = 0;
438
+ for (const [key, meta] of this.meta) {
439
+ if (now > meta.expiresAt) {
440
+ this.deleteInternal(key);
441
+ cleaned++;
442
+ this.onEvict?.({
443
+ reason: 'expired',
444
+ key,
445
+ freedBytes: meta.compressedSize,
446
+ currentBytes: this.currentMemoryBytes,
447
+ maxMemoryBytes: this.maxMemoryBytes,
448
+ });
449
+ }
450
+ }
451
+ return cleaned;
29
452
  }
30
453
  }
@@ -0,0 +1,46 @@
1
+ import type { SearchResult } from '../../mcp/search/types.js';
2
+ export declare class ShellSearch {
3
+ private hybridSearch;
4
+ private docsIndex;
5
+ private codeExamples;
6
+ private typeDefinitions;
7
+ private initialized;
8
+ private idleTimer;
9
+ private docsPath;
10
+ private examplesPath;
11
+ private srcPath;
12
+ constructor();
13
+ private ensureInitialized;
14
+ private resetIdleTimer;
15
+ private unload;
16
+ search(query: string, options?: {
17
+ limit?: number;
18
+ category?: string;
19
+ }): Promise<SearchResult[]>;
20
+ suggest(useCase: string): Promise<string>;
21
+ getExamples(feature: string, options?: {
22
+ limit?: number;
23
+ complexity?: string;
24
+ }): Promise<string>;
25
+ getStats(): {
26
+ documents: number;
27
+ examples: number;
28
+ types: number;
29
+ loaded: boolean;
30
+ };
31
+ private findDocsPath;
32
+ private findExamplesPath;
33
+ private findSrcPath;
34
+ private buildIndex;
35
+ private indexDocsFromEmbeddings;
36
+ private indexDocsFromFilesystem;
37
+ private indexExamples;
38
+ private indexTypes;
39
+ private extractKeywords;
40
+ private parseExampleMeta;
41
+ private inferFeature;
42
+ private extractMainCode;
43
+ private parseTypeDefinitions;
44
+ }
45
+ export declare function getShellSearch(): ShellSearch;
46
+ //# sourceMappingURL=shell-search.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shell-search.d.ts","sourceRoot":"","sources":["../../../src/cli/tui/shell-search.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAc,YAAY,EAAE,MAAM,2BAA2B,CAAC;AAmC1E,qBAAa,WAAW;IACtB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,SAAS,CAAoB;IACrC,OAAO,CAAC,YAAY,CAAqB;IACzC,OAAO,CAAC,eAAe,CAAwB;IAC/C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,SAAS,CAA8C;IAC/D,OAAO,CAAC,QAAQ,CAAS;IACzB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,OAAO,CAAS;;YAWV,iBAAiB;IAgB/B,OAAO,CAAC,cAAc;IAUtB,OAAO,CAAC,MAAM;IAaR,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAenG,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAqGzC,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,GAAE;QAAE,KAAK,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAA;KAAO,GAAG,OAAO,CAAC,MAAM,CAAC;IAwD1G,QAAQ,IAAI;QAAE,SAAS,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,OAAO,CAAA;KAAE;IAWnF,OAAO,CAAC,YAAY;IAYpB,OAAO,CAAC,gBAAgB;IAWxB,OAAO,CAAC,WAAW;IAanB,OAAO,CAAC,UAAU;IAUlB,OAAO,CAAC,uBAAuB;IAuC/B,OAAO,CAAC,uBAAuB;IAuC/B,OAAO,CAAC,aAAa;IAoCrB,OAAO,CAAC,UAAU;IAwBlB,OAAO,CAAC,eAAe;IA8BvB,OAAO,CAAC,gBAAgB;IA8BxB,OAAO,CAAC,YAAY;IAcpB,OAAO,CAAC,eAAe;IAkBvB,OAAO,CAAC,oBAAoB;CA4C7B;AAKD,wBAAgB,cAAc,IAAI,WAAW,CAK5C"}