@skillsmith/core 0.2.0 → 2.0.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 +233 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/analysis/__tests__/incremental.test.d.ts +13 -0
- package/dist/src/analysis/__tests__/incremental.test.d.ts.map +1 -0
- package/dist/src/analysis/__tests__/incremental.test.js +515 -0
- package/dist/src/analysis/__tests__/incremental.test.js.map +1 -0
- package/dist/src/analysis/__tests__/integration.test.d.ts +14 -0
- package/dist/src/analysis/__tests__/integration.test.d.ts.map +1 -0
- package/dist/src/analysis/__tests__/integration.test.js +1059 -0
- package/dist/src/analysis/__tests__/integration.test.js.map +1 -0
- package/dist/src/analysis/__tests__/metrics.test.d.ts +9 -0
- package/dist/src/analysis/__tests__/metrics.test.d.ts.map +1 -0
- package/dist/src/analysis/__tests__/metrics.test.js +369 -0
- package/dist/src/analysis/__tests__/metrics.test.js.map +1 -0
- package/dist/src/analysis/__tests__/performance.test.d.ts +15 -0
- package/dist/src/analysis/__tests__/performance.test.d.ts.map +1 -0
- package/dist/src/analysis/__tests__/performance.test.js +402 -0
- package/dist/src/analysis/__tests__/performance.test.js.map +1 -0
- package/dist/src/analysis/adapters/__tests__/go.test.d.ts +12 -0
- package/dist/src/analysis/adapters/__tests__/go.test.d.ts.map +1 -0
- package/dist/src/analysis/adapters/__tests__/go.test.js +561 -0
- package/dist/src/analysis/adapters/__tests__/go.test.js.map +1 -0
- package/dist/src/analysis/adapters/__tests__/python.test.d.ts +11 -0
- package/dist/src/analysis/adapters/__tests__/python.test.d.ts.map +1 -0
- package/dist/src/analysis/adapters/__tests__/python.test.js +669 -0
- package/dist/src/analysis/adapters/__tests__/python.test.js.map +1 -0
- package/dist/src/analysis/adapters/__tests__/rust.test.d.ts +12 -0
- package/dist/src/analysis/adapters/__tests__/rust.test.d.ts.map +1 -0
- package/dist/src/analysis/adapters/__tests__/rust.test.js +676 -0
- package/dist/src/analysis/adapters/__tests__/rust.test.js.map +1 -0
- package/dist/src/analysis/adapters/__tests__/typescript.test.d.ts +14 -0
- package/dist/src/analysis/adapters/__tests__/typescript.test.d.ts.map +1 -0
- package/dist/src/analysis/adapters/__tests__/typescript.test.js +381 -0
- package/dist/src/analysis/adapters/__tests__/typescript.test.js.map +1 -0
- package/dist/src/analysis/adapters/base.d.ts +83 -0
- package/dist/src/analysis/adapters/base.d.ts.map +1 -0
- package/dist/src/analysis/adapters/base.js +40 -0
- package/dist/src/analysis/adapters/base.js.map +1 -0
- package/dist/src/analysis/adapters/factory.d.ts +150 -0
- package/dist/src/analysis/adapters/factory.d.ts.map +1 -0
- package/dist/src/analysis/adapters/factory.js +244 -0
- package/dist/src/analysis/adapters/factory.js.map +1 -0
- package/dist/src/analysis/adapters/go.d.ts +131 -0
- package/dist/src/analysis/adapters/go.d.ts.map +1 -0
- package/dist/src/analysis/adapters/go.js +414 -0
- package/dist/src/analysis/adapters/go.js.map +1 -0
- package/dist/src/analysis/adapters/index.d.ts +20 -0
- package/dist/src/analysis/adapters/index.d.ts.map +1 -0
- package/dist/src/analysis/adapters/index.js +23 -0
- package/dist/src/analysis/adapters/index.js.map +1 -0
- package/dist/src/analysis/adapters/java.d.ts +154 -0
- package/dist/src/analysis/adapters/java.d.ts.map +1 -0
- package/dist/src/analysis/adapters/java.js +407 -0
- package/dist/src/analysis/adapters/java.js.map +1 -0
- package/dist/src/analysis/adapters/python.d.ts +165 -0
- package/dist/src/analysis/adapters/python.d.ts.map +1 -0
- package/dist/src/analysis/adapters/python.js +475 -0
- package/dist/src/analysis/adapters/python.js.map +1 -0
- package/dist/src/analysis/adapters/rust.d.ts +116 -0
- package/dist/src/analysis/adapters/rust.d.ts.map +1 -0
- package/dist/src/analysis/adapters/rust.js +476 -0
- package/dist/src/analysis/adapters/rust.js.map +1 -0
- package/dist/src/analysis/adapters/typescript.d.ts +68 -0
- package/dist/src/analysis/adapters/typescript.d.ts.map +1 -0
- package/dist/src/analysis/adapters/typescript.js +79 -0
- package/dist/src/analysis/adapters/typescript.js.map +1 -0
- package/dist/src/analysis/aggregator.d.ts +193 -0
- package/dist/src/analysis/aggregator.d.ts.map +1 -0
- package/dist/src/analysis/aggregator.js +283 -0
- package/dist/src/analysis/aggregator.js.map +1 -0
- package/dist/src/analysis/cache.d.ts +180 -0
- package/dist/src/analysis/cache.d.ts.map +1 -0
- package/dist/src/analysis/cache.js +279 -0
- package/dist/src/analysis/cache.js.map +1 -0
- package/dist/src/analysis/file-streamer.d.ts +136 -0
- package/dist/src/analysis/file-streamer.d.ts.map +1 -0
- package/dist/src/analysis/file-streamer.js +291 -0
- package/dist/src/analysis/file-streamer.js.map +1 -0
- package/dist/src/analysis/incremental-parser.d.ts +186 -0
- package/dist/src/analysis/incremental-parser.d.ts.map +1 -0
- package/dist/src/analysis/incremental-parser.js +291 -0
- package/dist/src/analysis/incremental-parser.js.map +1 -0
- package/dist/src/analysis/incremental.d.ts +186 -0
- package/dist/src/analysis/incremental.d.ts.map +1 -0
- package/dist/src/analysis/incremental.js +247 -0
- package/dist/src/analysis/incremental.js.map +1 -0
- package/dist/src/analysis/index.d.ts +25 -3
- package/dist/src/analysis/index.d.ts.map +1 -1
- package/dist/src/analysis/index.js +45 -3
- package/dist/src/analysis/index.js.map +1 -1
- package/dist/src/analysis/language-detector.d.ts +92 -0
- package/dist/src/analysis/language-detector.d.ts.map +1 -0
- package/dist/src/analysis/language-detector.js +602 -0
- package/dist/src/analysis/language-detector.js.map +1 -0
- package/dist/src/analysis/memory-monitor.d.ts +199 -0
- package/dist/src/analysis/memory-monitor.d.ts.map +1 -0
- package/dist/src/analysis/memory-monitor.js +271 -0
- package/dist/src/analysis/memory-monitor.js.map +1 -0
- package/dist/src/analysis/metrics.d.ts +300 -0
- package/dist/src/analysis/metrics.d.ts.map +1 -0
- package/dist/src/analysis/metrics.js +537 -0
- package/dist/src/analysis/metrics.js.map +1 -0
- package/dist/src/analysis/router.d.ts +264 -0
- package/dist/src/analysis/router.d.ts.map +1 -0
- package/dist/src/analysis/router.js +398 -0
- package/dist/src/analysis/router.js.map +1 -0
- package/dist/src/analysis/tree-cache.d.ts +208 -0
- package/dist/src/analysis/tree-cache.d.ts.map +1 -0
- package/dist/src/analysis/tree-cache.js +288 -0
- package/dist/src/analysis/tree-cache.js.map +1 -0
- package/dist/src/analysis/tree-sitter/manager.d.ts +141 -0
- package/dist/src/analysis/tree-sitter/manager.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/manager.js +239 -0
- package/dist/src/analysis/tree-sitter/manager.js.map +1 -0
- package/dist/src/analysis/types.d.ts +69 -6
- package/dist/src/analysis/types.d.ts.map +1 -1
- package/dist/src/analysis/types.js +23 -2
- package/dist/src/analysis/types.js.map +1 -1
- package/dist/src/analysis/worker-pool.d.ts +141 -0
- package/dist/src/analysis/worker-pool.d.ts.map +1 -0
- package/dist/src/analysis/worker-pool.js +418 -0
- package/dist/src/analysis/worker-pool.js.map +1 -0
- package/dist/src/analytics/schema.d.ts +1 -1
- package/dist/src/analytics/schema.d.ts.map +1 -1
- package/dist/src/analytics/schema.js +72 -0
- package/dist/src/analytics/schema.js.map +1 -1
- package/dist/src/api/cache.d.ts +24 -1
- package/dist/src/api/cache.d.ts.map +1 -1
- package/dist/src/api/cache.js +50 -2
- package/dist/src/api/cache.js.map +1 -1
- package/dist/src/api/client.d.ts +132 -2
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.js +214 -18
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/api/index.d.ts +2 -0
- package/dist/src/api/index.d.ts.map +1 -1
- package/dist/src/api/index.js +7 -0
- package/dist/src/api/index.js.map +1 -1
- package/dist/src/api/types.d.ts +251 -0
- package/dist/src/api/types.d.ts.map +1 -0
- package/dist/src/api/types.js +9 -0
- package/dist/src/api/types.js.map +1 -0
- package/dist/src/benchmarks/memory/MemoryProfiler.d.ts.map +1 -1
- package/dist/src/benchmarks/memory/MemoryProfiler.js.map +1 -1
- package/dist/src/embeddings/index.d.ts.map +1 -1
- package/dist/src/embeddings/index.js.map +1 -1
- package/dist/src/errors.d.ts +1 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +1 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/repositories/IndexerRepository.d.ts.map +1 -1
- package/dist/src/repositories/IndexerRepository.js +1 -0
- package/dist/src/repositories/IndexerRepository.js.map +1 -1
- package/dist/src/repositories/SkillRepository.d.ts.map +1 -1
- package/dist/src/repositories/SkillRepository.js +1 -0
- package/dist/src/repositories/SkillRepository.js.map +1 -1
- package/dist/src/repositories/quarantine/QuarantineRepository.d.ts.map +1 -1
- package/dist/src/repositories/quarantine/QuarantineRepository.js.map +1 -1
- package/dist/src/repositories/quarantine/query-builder.d.ts.map +1 -1
- package/dist/src/repositories/quarantine/query-builder.js +1 -1
- package/dist/src/repositories/quarantine/query-builder.js.map +1 -1
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js +3 -3
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
- package/dist/src/scripts/github-import/index.js.map +1 -1
- package/dist/src/scripts/import-github-skills.js +1 -1
- package/dist/src/scripts/import-github-skills.js.map +1 -1
- package/dist/src/scripts/skill-scanner/reporter.d.ts.map +1 -1
- package/dist/src/scripts/skill-scanner/reporter.js.map +1 -1
- package/dist/src/scripts/skill-scanner/scanner.d.ts.map +1 -1
- package/dist/src/scripts/skill-scanner/scanner.js.map +1 -1
- package/dist/src/scripts/skill-scanner/trust-scorer.d.ts.map +1 -1
- package/dist/src/scripts/skill-scanner/trust-scorer.js.map +1 -1
- package/dist/src/scripts/validation/index.js +1 -2
- package/dist/src/scripts/validation/index.js.map +1 -1
- package/dist/src/scripts/validation/pipeline.d.ts.map +1 -1
- package/dist/src/scripts/validation/pipeline.js.map +1 -1
- package/dist/src/scripts/validation/types.d.ts +2 -2
- package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
- package/dist/src/services/SearchService.d.ts.map +1 -1
- package/dist/src/services/SearchService.js +1 -0
- package/dist/src/services/SearchService.js.map +1 -1
- package/dist/src/session/SessionHealthMonitor.d.ts +1 -1
- package/dist/src/session/SessionHealthMonitor.d.ts.map +1 -1
- package/dist/src/session/SessionHealthMonitor.js +1 -1
- package/dist/src/session/SessionHealthMonitor.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +1 -1
- package/dist/src/telemetry/index.d.ts.map +1 -1
- package/dist/src/telemetry/index.js +2 -2
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/posthog.d.ts +27 -5
- package/dist/src/telemetry/posthog.d.ts.map +1 -1
- package/dist/src/telemetry/posthog.js +20 -5
- package/dist/src/telemetry/posthog.js.map +1 -1
- package/dist/src/types/skill.d.ts +3 -0
- package/dist/src/types/skill.d.ts.map +1 -1
- package/dist/src/types.d.ts +2 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +2 -2
- package/dist/src/types.js.map +1 -1
- package/dist/tests/adapters-factory.test.d.ts +13 -0
- package/dist/tests/adapters-factory.test.d.ts.map +1 -0
- package/dist/tests/adapters-factory.test.js +308 -0
- package/dist/tests/adapters-factory.test.js.map +1 -0
- package/dist/tests/adapters-java.test.d.ts +13 -0
- package/dist/tests/adapters-java.test.d.ts.map +1 -0
- package/dist/tests/adapters-java.test.js +925 -0
- package/dist/tests/adapters-java.test.js.map +1 -0
- package/dist/tests/api/client.validation.test.d.ts +7 -0
- package/dist/tests/api/client.validation.test.d.ts.map +1 -0
- package/dist/tests/api/client.validation.test.js +183 -0
- package/dist/tests/api/client.validation.test.js.map +1 -0
- package/dist/tests/language-detector.test.d.ts +13 -0
- package/dist/tests/language-detector.test.d.ts.map +1 -0
- package/dist/tests/language-detector.test.js +674 -0
- package/dist/tests/language-detector.test.js.map +1 -0
- package/dist/tests/telemetry/posthog.test.d.ts +13 -0
- package/dist/tests/telemetry/posthog.test.d.ts.map +1 -0
- package/dist/tests/telemetry/posthog.test.js +600 -0
- package/dist/tests/telemetry/posthog.test.js.map +1 -0
- package/package.json +5 -6
- package/dist/src/security/RateLimiter.d.ts +0 -337
- package/dist/src/security/RateLimiter.d.ts.map +0 -1
- package/dist/src/security/RateLimiter.js +0 -782
- package/dist/src/security/RateLimiter.js.map +0 -1
- package/dist/src/security/scanner.d.ts +0 -151
- package/dist/src/security/scanner.d.ts.map +0 -1
- package/dist/src/security/scanner.js +0 -599
- package/dist/src/security/scanner.js.map +0 -1
|
@@ -0,0 +1,279 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-1303: Parse Result Cache
|
|
3
|
+
* SMI-1337: Added metrics integration
|
|
4
|
+
*
|
|
5
|
+
* LRU cache for parse results with content hash validation.
|
|
6
|
+
* Provides memory-based eviction to prevent memory exhaustion.
|
|
7
|
+
*
|
|
8
|
+
* @see docs/architecture/multi-language-analysis.md
|
|
9
|
+
* @module analysis/cache
|
|
10
|
+
*/
|
|
11
|
+
import { createHash } from 'crypto';
|
|
12
|
+
import path from 'path';
|
|
13
|
+
import { LRUCache } from 'lru-cache';
|
|
14
|
+
import { getAnalysisMetrics } from './metrics.js';
|
|
15
|
+
/**
|
|
16
|
+
* LRU cache for parse results with memory-based eviction
|
|
17
|
+
*
|
|
18
|
+
* Caches parse results keyed by file path. Uses content hashing
|
|
19
|
+
* to detect when cached results are stale.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```typescript
|
|
23
|
+
* const cache = new ParseCache({ maxMemoryMB: 100 })
|
|
24
|
+
*
|
|
25
|
+
* // Check cache first
|
|
26
|
+
* const cached = cache.get('src/main.py', fileContent)
|
|
27
|
+
* if (cached) {
|
|
28
|
+
* return cached
|
|
29
|
+
* }
|
|
30
|
+
*
|
|
31
|
+
* // Parse and cache
|
|
32
|
+
* const result = adapter.parseFile(fileContent, 'src/main.py')
|
|
33
|
+
* cache.set('src/main.py', fileContent, result)
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
export class ParseCache {
|
|
37
|
+
cache;
|
|
38
|
+
maxMemory;
|
|
39
|
+
metrics;
|
|
40
|
+
hits = 0;
|
|
41
|
+
misses = 0;
|
|
42
|
+
constructor(options = {}) {
|
|
43
|
+
this.maxMemory = (options.maxMemoryMB ?? 200) * 1024 * 1024;
|
|
44
|
+
this.metrics = options.metrics ?? getAnalysisMetrics();
|
|
45
|
+
this.cache = new LRUCache({
|
|
46
|
+
maxSize: this.maxMemory,
|
|
47
|
+
sizeCalculation: (entry) => entry.size,
|
|
48
|
+
ttl: options.ttlMs,
|
|
49
|
+
updateAgeOnGet: true,
|
|
50
|
+
updateAgeOnHas: false,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Get cached result if content unchanged
|
|
55
|
+
*
|
|
56
|
+
* Returns null if:
|
|
57
|
+
* - No entry exists for the path
|
|
58
|
+
* - Content hash doesn't match (file was modified)
|
|
59
|
+
* - Entry was evicted due to memory pressure
|
|
60
|
+
*
|
|
61
|
+
* SMI-1337: Records cache hit/miss metrics.
|
|
62
|
+
*
|
|
63
|
+
* @param filePath - Path to the file
|
|
64
|
+
* @param content - Current file content for hash comparison
|
|
65
|
+
* @returns Cached parse result or null
|
|
66
|
+
*
|
|
67
|
+
* @example
|
|
68
|
+
* ```typescript
|
|
69
|
+
* const cached = cache.get('src/main.py', fileContent)
|
|
70
|
+
* if (cached) {
|
|
71
|
+
* console.log('Cache hit!')
|
|
72
|
+
* return cached
|
|
73
|
+
* }
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
get(filePath, content) {
|
|
77
|
+
const language = this.getLanguageFromPath(filePath);
|
|
78
|
+
const entry = this.cache.get(filePath);
|
|
79
|
+
if (!entry) {
|
|
80
|
+
this.misses++;
|
|
81
|
+
this.metrics.recordCacheMiss(language);
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
// Validate content hash
|
|
85
|
+
const contentHash = this.hashContent(content);
|
|
86
|
+
if (entry.contentHash !== contentHash) {
|
|
87
|
+
// Content changed, invalidate
|
|
88
|
+
this.cache.delete(filePath);
|
|
89
|
+
this.misses++;
|
|
90
|
+
this.metrics.recordCacheMiss(language);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
this.hits++;
|
|
94
|
+
this.metrics.recordCacheHit(language);
|
|
95
|
+
return entry.result;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Store parse result in cache
|
|
99
|
+
*
|
|
100
|
+
* The result is stored with a content hash for future validation.
|
|
101
|
+
* If the cache is at capacity, least recently used entries
|
|
102
|
+
* are evicted to make room.
|
|
103
|
+
*
|
|
104
|
+
* SMI-1337: Updates cache size metrics.
|
|
105
|
+
*
|
|
106
|
+
* @param filePath - Path to the file
|
|
107
|
+
* @param content - File content (used for hash)
|
|
108
|
+
* @param result - Parse result to cache
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* const result = adapter.parseFile(content, 'src/main.py')
|
|
113
|
+
* cache.set('src/main.py', content, result)
|
|
114
|
+
* ```
|
|
115
|
+
*/
|
|
116
|
+
set(filePath, content, result) {
|
|
117
|
+
const contentHash = this.hashContent(content);
|
|
118
|
+
const size = this.estimateSize(result);
|
|
119
|
+
this.cache.set(filePath, {
|
|
120
|
+
result,
|
|
121
|
+
contentHash,
|
|
122
|
+
timestamp: Date.now(),
|
|
123
|
+
size,
|
|
124
|
+
});
|
|
125
|
+
// SMI-1337: Update cache size metrics
|
|
126
|
+
this.metrics.updateCacheSize(this.cache.calculatedSize ?? 0, this.cache.size);
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Check if a file is cached (without counting as hit/miss)
|
|
130
|
+
*
|
|
131
|
+
* @param filePath - Path to check
|
|
132
|
+
* @returns True if entry exists (may be stale)
|
|
133
|
+
*/
|
|
134
|
+
has(filePath) {
|
|
135
|
+
return this.cache.has(filePath);
|
|
136
|
+
}
|
|
137
|
+
/**
|
|
138
|
+
* Invalidate cache entries for changed files
|
|
139
|
+
*
|
|
140
|
+
* Call this when files are known to have changed
|
|
141
|
+
* to prevent stale cache hits.
|
|
142
|
+
*
|
|
143
|
+
* @param filePaths - Paths to invalidate
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```typescript
|
|
147
|
+
* // On file system change event
|
|
148
|
+
* cache.invalidate(['src/modified.py', 'src/deleted.py'])
|
|
149
|
+
* ```
|
|
150
|
+
*/
|
|
151
|
+
invalidate(filePaths) {
|
|
152
|
+
for (const path of filePaths) {
|
|
153
|
+
this.cache.delete(path);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Invalidate entries matching a pattern
|
|
158
|
+
*
|
|
159
|
+
* @param pattern - Glob-like pattern to match
|
|
160
|
+
*
|
|
161
|
+
* @example
|
|
162
|
+
* ```typescript
|
|
163
|
+
* // Invalidate all Python files
|
|
164
|
+
* cache.invalidatePattern('*.py')
|
|
165
|
+
* ```
|
|
166
|
+
*/
|
|
167
|
+
invalidatePattern(pattern) {
|
|
168
|
+
const regex = this.patternToRegex(pattern);
|
|
169
|
+
for (const key of this.cache.keys()) {
|
|
170
|
+
if (regex.test(key)) {
|
|
171
|
+
this.cache.delete(key);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Clear entire cache
|
|
177
|
+
*
|
|
178
|
+
* Removes all entries and resets statistics.
|
|
179
|
+
*/
|
|
180
|
+
clear() {
|
|
181
|
+
this.cache.clear();
|
|
182
|
+
this.hits = 0;
|
|
183
|
+
this.misses = 0;
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Get cache statistics
|
|
187
|
+
*
|
|
188
|
+
* @returns Current cache statistics
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* ```typescript
|
|
192
|
+
* const stats = cache.getStats()
|
|
193
|
+
* console.log(`Hit rate: ${(stats.hitRate * 100).toFixed(1)}%`)
|
|
194
|
+
* console.log(`Size: ${(stats.size / 1024 / 1024).toFixed(1)} MB`)
|
|
195
|
+
* ```
|
|
196
|
+
*/
|
|
197
|
+
getStats() {
|
|
198
|
+
const total = this.hits + this.misses;
|
|
199
|
+
return {
|
|
200
|
+
size: this.cache.calculatedSize ?? 0,
|
|
201
|
+
entries: this.cache.size,
|
|
202
|
+
maxSize: this.maxMemory,
|
|
203
|
+
hitRate: total > 0 ? this.hits / total : 0,
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Get number of cached entries
|
|
208
|
+
*/
|
|
209
|
+
get size() {
|
|
210
|
+
return this.cache.size;
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Reset hit/miss counters
|
|
214
|
+
*/
|
|
215
|
+
resetStats() {
|
|
216
|
+
this.hits = 0;
|
|
217
|
+
this.misses = 0;
|
|
218
|
+
}
|
|
219
|
+
/**
|
|
220
|
+
* Hash file content for change detection
|
|
221
|
+
*
|
|
222
|
+
* Uses SHA-256 truncated to 16 characters for efficiency.
|
|
223
|
+
*/
|
|
224
|
+
hashContent(content) {
|
|
225
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get language from file path extension
|
|
229
|
+
* SMI-1337: Helper for metrics labeling
|
|
230
|
+
*/
|
|
231
|
+
getLanguageFromPath(filePath) {
|
|
232
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
233
|
+
const extensionToLanguage = {
|
|
234
|
+
'.ts': 'typescript',
|
|
235
|
+
'.tsx': 'typescript',
|
|
236
|
+
'.mts': 'typescript',
|
|
237
|
+
'.cts': 'typescript',
|
|
238
|
+
'.js': 'javascript',
|
|
239
|
+
'.jsx': 'javascript',
|
|
240
|
+
'.mjs': 'javascript',
|
|
241
|
+
'.cjs': 'javascript',
|
|
242
|
+
'.py': 'python',
|
|
243
|
+
'.pyi': 'python',
|
|
244
|
+
'.pyw': 'python',
|
|
245
|
+
'.go': 'go',
|
|
246
|
+
'.rs': 'rust',
|
|
247
|
+
'.java': 'java',
|
|
248
|
+
};
|
|
249
|
+
return extensionToLanguage[ext];
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Estimate memory size of a parse result
|
|
253
|
+
*
|
|
254
|
+
* Rough estimate based on array sizes and average item sizes.
|
|
255
|
+
* SMI-1335: Named constants for magic numbers
|
|
256
|
+
*/
|
|
257
|
+
estimateSize(result) {
|
|
258
|
+
// SMI-1335: Named constants for clarity and maintainability
|
|
259
|
+
/** Average bytes per import/export/function entry (strings + object overhead) */
|
|
260
|
+
const BYTES_PER_ITEM = 100;
|
|
261
|
+
/** Fixed overhead for result object structure and metadata */
|
|
262
|
+
const BASE_OVERHEAD_BYTES = 1000;
|
|
263
|
+
return (result.imports.length * BYTES_PER_ITEM +
|
|
264
|
+
result.exports.length * BYTES_PER_ITEM +
|
|
265
|
+
result.functions.length * BYTES_PER_ITEM +
|
|
266
|
+
BASE_OVERHEAD_BYTES);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Convert glob pattern to regex
|
|
270
|
+
*/
|
|
271
|
+
patternToRegex(pattern) {
|
|
272
|
+
const escaped = pattern
|
|
273
|
+
.replace(/[.+^${}()|[\]\\]/g, '\\$&')
|
|
274
|
+
.replace(/\*/g, '.*')
|
|
275
|
+
.replace(/\?/g, '.');
|
|
276
|
+
return new RegExp(`^${escaped}$`);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
//# sourceMappingURL=cache.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cache.js","sourceRoot":"","sources":["../../../src/analysis/cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-1308: File Streamer
|
|
3
|
+
*
|
|
4
|
+
* Memory-efficient file reading with streaming support for large files.
|
|
5
|
+
* Provides generators for processing files without loading all into memory.
|
|
6
|
+
*
|
|
7
|
+
* @see docs/architecture/multi-language-analysis.md
|
|
8
|
+
* @module analysis/file-streamer
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* File content with metadata
|
|
12
|
+
*/
|
|
13
|
+
export interface FileContent {
|
|
14
|
+
/** File path */
|
|
15
|
+
path: string;
|
|
16
|
+
/** File content (may be truncated for large files) */
|
|
17
|
+
content: string;
|
|
18
|
+
/** File size in bytes */
|
|
19
|
+
size: number;
|
|
20
|
+
/** Number of lines */
|
|
21
|
+
lineCount: number;
|
|
22
|
+
/** Whether content was truncated */
|
|
23
|
+
truncated?: boolean;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Options for file streaming
|
|
27
|
+
*/
|
|
28
|
+
export interface StreamOptions {
|
|
29
|
+
/** Maximum buffer size in bytes (default: 1MB) */
|
|
30
|
+
maxBufferSize?: number;
|
|
31
|
+
/** Skip files larger than buffer (default: true) */
|
|
32
|
+
skipLargeFiles?: boolean;
|
|
33
|
+
/** Include binary files (default: false) */
|
|
34
|
+
includeBinary?: boolean;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Options for batch file reading
|
|
38
|
+
*/
|
|
39
|
+
export interface BatchReadOptions {
|
|
40
|
+
/** Maximum concurrent reads (default: 10) */
|
|
41
|
+
concurrency?: number;
|
|
42
|
+
/** Maximum file size to read (default: 1MB) */
|
|
43
|
+
maxFileSize?: number;
|
|
44
|
+
/** Skip unreadable files (default: true) */
|
|
45
|
+
skipErrors?: boolean;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Stream files with memory-efficient chunking
|
|
49
|
+
*
|
|
50
|
+
* Uses async generators to process files one at a time,
|
|
51
|
+
* avoiding loading all file contents into memory.
|
|
52
|
+
*
|
|
53
|
+
* @param filePaths - Array of file paths to stream
|
|
54
|
+
* @param options - Streaming options
|
|
55
|
+
* @yields FileContent objects
|
|
56
|
+
*
|
|
57
|
+
* @example
|
|
58
|
+
* ```typescript
|
|
59
|
+
* for await (const file of streamFiles(paths, { maxBufferSize: 512 * 1024 })) {
|
|
60
|
+
* console.log(`${file.path}: ${file.lineCount} lines`)
|
|
61
|
+
* // Process file.content...
|
|
62
|
+
* }
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
export declare function streamFiles(filePaths: string[], options?: StreamOptions): AsyncGenerator<FileContent>;
|
|
66
|
+
/**
|
|
67
|
+
* Batch file reading with concurrency control
|
|
68
|
+
*
|
|
69
|
+
* Reads multiple files concurrently with a configurable limit
|
|
70
|
+
* to balance speed and memory usage.
|
|
71
|
+
*
|
|
72
|
+
* @param filePaths - Array of file paths to read
|
|
73
|
+
* @param options - Batch reading options
|
|
74
|
+
* @returns Array of file contents
|
|
75
|
+
*
|
|
76
|
+
* @example
|
|
77
|
+
* ```typescript
|
|
78
|
+
* const files = await batchReadFiles(paths, {
|
|
79
|
+
* concurrency: 20,
|
|
80
|
+
* maxFileSize: 512 * 1024
|
|
81
|
+
* })
|
|
82
|
+
*
|
|
83
|
+
* console.log(`Read ${files.length} files`)
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
export declare function batchReadFiles(filePaths: string[], options?: BatchReadOptions): Promise<FileContent[]>;
|
|
87
|
+
/**
|
|
88
|
+
* Read files as a map for quick lookup
|
|
89
|
+
*
|
|
90
|
+
* @param filePaths - Array of file paths to read
|
|
91
|
+
* @param options - Batch reading options
|
|
92
|
+
* @returns Map of path to file content
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const fileMap = await readFilesAsMap(paths)
|
|
97
|
+
* const content = fileMap.get('src/index.ts')
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export declare function readFilesAsMap(filePaths: string[], options?: BatchReadOptions): Promise<Map<string, FileContent>>;
|
|
101
|
+
/**
|
|
102
|
+
* Get file extension
|
|
103
|
+
*
|
|
104
|
+
* @param filePath - File path
|
|
105
|
+
* @returns Extension including dot, or empty string
|
|
106
|
+
*/
|
|
107
|
+
export declare function getFileExtension(filePath: string): string;
|
|
108
|
+
/**
|
|
109
|
+
* Filter paths by extension
|
|
110
|
+
*
|
|
111
|
+
* @param filePaths - Array of file paths
|
|
112
|
+
* @param extensions - Extensions to include (with dot)
|
|
113
|
+
* @returns Filtered paths
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* const tsPaths = filterByExtension(allPaths, ['.ts', '.tsx'])
|
|
118
|
+
* ```
|
|
119
|
+
*/
|
|
120
|
+
export declare function filterByExtension(filePaths: string[], extensions: string[]): string[];
|
|
121
|
+
/**
|
|
122
|
+
* Estimate memory usage for files
|
|
123
|
+
*
|
|
124
|
+
* Provides rough estimate based on file sizes.
|
|
125
|
+
*
|
|
126
|
+
* @param filePaths - Array of file paths
|
|
127
|
+
* @returns Estimated memory usage in bytes
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```typescript
|
|
131
|
+
* const estimate = await estimateMemoryUsage(paths)
|
|
132
|
+
* console.log(`Estimated: ${MemoryMonitor.formatBytes(estimate)}`)
|
|
133
|
+
* ```
|
|
134
|
+
*/
|
|
135
|
+
export declare function estimateMemoryUsage(filePaths: string[]): Promise<number>;
|
|
136
|
+
//# sourceMappingURL=file-streamer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"file-streamer.d.ts","sourceRoot":"","sources":["../../../src/analysis/file-streamer.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAKH;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAA;IACZ,sDAAsD;IACtD,OAAO,EAAE,MAAM,CAAA;IACf,yBAAyB;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,sBAAsB;IACtB,SAAS,EAAE,MAAM,CAAA;IACjB,oCAAoC;IACpC,SAAS,CAAC,EAAE,OAAO,CAAA;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,aAAa;IAC5B,kDAAkD;IAClD,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,oDAAoD;IACpD,cAAc,CAAC,EAAE,OAAO,CAAA;IACxB,4CAA4C;IAC5C,aAAa,CAAC,EAAE,OAAO,CAAA;CACxB;AAED;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAC/B,6CAA6C;IAC7C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,4CAA4C;IAC5C,UAAU,CAAC,EAAE,OAAO,CAAA;CACrB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAuB,WAAW,CAChC,SAAS,EAAE,MAAM,EAAE,EACnB,OAAO,GAAE,aAAkB,GAC1B,cAAc,CAAC,WAAW,CAAC,CA0C7B;AA2DD;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EAAE,EACnB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,WAAW,EAAE,CAAC,CAgDxB;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EAAE,EACnB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CASnC;AAwCD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CASzD;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,iBAAiB,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,UAAU,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAGrF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,mBAAmB,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAgB9E"}
|