ctxman 3.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.
Files changed (190) hide show
  1. package/.ctxman/custom-profiles.example.json +20 -0
  2. package/.ctxman/llm-profiles.json +131 -0
  3. package/.ctxman/logs/ctxman-2026-07-02.log +156 -0
  4. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  5. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  6. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  7. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  8. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  9. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  10. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  11. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  12. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  13. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  14. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  15. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  16. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  17. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  18. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  19. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  20. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  21. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  22. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  23. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  24. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  25. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  26. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  27. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  28. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  29. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  30. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  31. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  32. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  33. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  34. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  35. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  36. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  37. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  38. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  39. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  40. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  41. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  42. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  43. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  44. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  45. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  46. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  47. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  48. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  49. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  50. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  51. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  52. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  53. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  54. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  55. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  56. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  57. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  58. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  59. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  60. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  61. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  62. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  63. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  64. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  65. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  66. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
  67. package/.ctxman/wizard-profiles/code-review/.contextignore +41 -0
  68. package/.ctxman/wizard-profiles/code-review/.contextinclude +30 -0
  69. package/.ctxman/wizard-profiles/code-review/.methodignore +26 -0
  70. package/.ctxman/wizard-profiles/code-review/.methodinclude +47 -0
  71. package/.ctxman/wizard-profiles/code-review/profile.json +26 -0
  72. package/.ctxman/wizard-profiles/documentation/.contextignore +21 -0
  73. package/.ctxman/wizard-profiles/documentation/.contextinclude +24 -0
  74. package/.ctxman/wizard-profiles/documentation/.methodignore +19 -0
  75. package/.ctxman/wizard-profiles/documentation/.methodinclude +44 -0
  76. package/.ctxman/wizard-profiles/documentation/profile.json +27 -0
  77. package/.ctxman/wizard-profiles/full/.contextignore +23 -0
  78. package/.ctxman/wizard-profiles/full/.contextinclude +38 -0
  79. package/.ctxman/wizard-profiles/full/.methodignore +5 -0
  80. package/.ctxman/wizard-profiles/full/.methodinclude +5 -0
  81. package/.ctxman/wizard-profiles/full/profile.json +28 -0
  82. package/.ctxman/wizard-profiles/llm-explain/.contextignore +25 -0
  83. package/.ctxman/wizard-profiles/llm-explain/.contextinclude +33 -0
  84. package/.ctxman/wizard-profiles/llm-explain/.methodignore +19 -0
  85. package/.ctxman/wizard-profiles/llm-explain/.methodinclude +48 -0
  86. package/.ctxman/wizard-profiles/llm-explain/profile.json +27 -0
  87. package/.ctxman/wizard-profiles/minimal/.contextignore +27 -0
  88. package/.ctxman/wizard-profiles/minimal/.contextinclude +14 -0
  89. package/.ctxman/wizard-profiles/minimal/.methodignore +22 -0
  90. package/.ctxman/wizard-profiles/minimal/.methodinclude +18 -0
  91. package/.ctxman/wizard-profiles/minimal/profile.json +26 -0
  92. package/.ctxman/wizard-profiles/security-audit/.contextignore +36 -0
  93. package/.ctxman/wizard-profiles/security-audit/.contextinclude +37 -0
  94. package/.ctxman/wizard-profiles/security-audit/.methodignore +29 -0
  95. package/.ctxman/wizard-profiles/security-audit/.methodinclude +60 -0
  96. package/.ctxman/wizard-profiles/security-audit/profile.json +34 -0
  97. package/README.md +1167 -0
  98. package/bin/cli.js +650 -0
  99. package/bin/cm-ask.js +95 -0
  100. package/bin/cm-gitingest.js +134 -0
  101. package/bin/cm-update.js +175 -0
  102. package/bin/mcp-server.js +31 -0
  103. package/ctxman.js +236 -0
  104. package/examples/README.md +298 -0
  105. package/examples/custom-llm-profiles.example.json +19 -0
  106. package/examples/wizard-profiles/code-review/.contextignore +41 -0
  107. package/examples/wizard-profiles/code-review/.contextinclude +30 -0
  108. package/examples/wizard-profiles/code-review/.methodignore +26 -0
  109. package/examples/wizard-profiles/code-review/.methodinclude +47 -0
  110. package/examples/wizard-profiles/code-review/profile.json +26 -0
  111. package/examples/wizard-profiles/documentation/.contextignore +21 -0
  112. package/examples/wizard-profiles/documentation/.contextinclude +24 -0
  113. package/examples/wizard-profiles/documentation/.methodignore +19 -0
  114. package/examples/wizard-profiles/documentation/.methodinclude +44 -0
  115. package/examples/wizard-profiles/documentation/profile.json +27 -0
  116. package/examples/wizard-profiles/full/.contextignore +23 -0
  117. package/examples/wizard-profiles/full/.contextinclude +38 -0
  118. package/examples/wizard-profiles/full/.methodignore +5 -0
  119. package/examples/wizard-profiles/full/.methodinclude +5 -0
  120. package/examples/wizard-profiles/full/profile.json +28 -0
  121. package/examples/wizard-profiles/llm-explain/.contextignore +25 -0
  122. package/examples/wizard-profiles/llm-explain/.contextinclude +33 -0
  123. package/examples/wizard-profiles/llm-explain/.methodignore +19 -0
  124. package/examples/wizard-profiles/llm-explain/.methodinclude +48 -0
  125. package/examples/wizard-profiles/llm-explain/profile.json +27 -0
  126. package/examples/wizard-profiles/minimal/.contextignore +27 -0
  127. package/examples/wizard-profiles/minimal/.contextinclude +14 -0
  128. package/examples/wizard-profiles/minimal/.methodignore +22 -0
  129. package/examples/wizard-profiles/minimal/.methodinclude +18 -0
  130. package/examples/wizard-profiles/minimal/profile.json +26 -0
  131. package/examples/wizard-profiles/security-audit/.contextignore +36 -0
  132. package/examples/wizard-profiles/security-audit/.contextinclude +37 -0
  133. package/examples/wizard-profiles/security-audit/.methodignore +29 -0
  134. package/examples/wizard-profiles/security-audit/.methodinclude +60 -0
  135. package/examples/wizard-profiles/security-audit/profile.json +34 -0
  136. package/index.js +68 -0
  137. package/lib/agents/pr-failure-agent.js +201 -0
  138. package/lib/analyzers/go-method-analyzer.js +126 -0
  139. package/lib/analyzers/method-analyzer.js +457 -0
  140. package/lib/analyzers/token-calculator.js +577 -0
  141. package/lib/api/mcp/prompts.js +319 -0
  142. package/lib/api/mcp/resources.js +269 -0
  143. package/lib/api/mcp/server.js +339 -0
  144. package/lib/api/mcp/transports/HttpSseTransport.js +63 -0
  145. package/lib/api/rest/server.js +405 -0
  146. package/lib/cache/CacheManager.js +388 -0
  147. package/lib/core/Analyzer.js +180 -0
  148. package/lib/core/ContextBuilder.js +325 -0
  149. package/lib/core/Reporter.js +270 -0
  150. package/lib/core/Scanner.js +129 -0
  151. package/lib/formatters/format-registry.js +419 -0
  152. package/lib/formatters/gitingest-formatter.js +746 -0
  153. package/lib/formatters/toon-formatter-v1.3.js +731 -0
  154. package/lib/formatters/toon-formatter.js +291 -0
  155. package/lib/integrations/git/BlameTracker.js +142 -0
  156. package/lib/integrations/git/DiffAnalyzer.js +260 -0
  157. package/lib/integrations/git/GitClient.js +336 -0
  158. package/lib/parsers/gitignore-parser.js +127 -0
  159. package/lib/parsers/method-filter-parser.js +50 -0
  160. package/lib/plugins/ExporterPlugin.js +102 -0
  161. package/lib/plugins/LanguagePlugin.js +100 -0
  162. package/lib/plugins/PluginManager.js +302 -0
  163. package/lib/rag/DataSourcePlugin.js +31 -0
  164. package/lib/rag/EmbeddingProvider.js +75 -0
  165. package/lib/rag/Indexer.js +83 -0
  166. package/lib/rag/VectorStoreFactory.js +24 -0
  167. package/lib/rag/sources/FileDataSource.js +70 -0
  168. package/lib/rag/stores/BaseVectorStore.js +43 -0
  169. package/lib/rag/stores/ChromaDBStore.js +85 -0
  170. package/lib/rag/stores/LanceDBStore.js +109 -0
  171. package/lib/rag/stores/PineconeStore.js +74 -0
  172. package/lib/ui/dashboard.js +109 -0
  173. package/lib/ui/index.js +15 -0
  174. package/lib/ui/progress-bar.js +110 -0
  175. package/lib/ui/select-input.js +51 -0
  176. package/lib/ui/wizard.js +359 -0
  177. package/lib/utils/clipboard-utils.js +66 -0
  178. package/lib/utils/config-utils.js +94 -0
  179. package/lib/utils/error-handler.js +145 -0
  180. package/lib/utils/file-utils.js +89 -0
  181. package/lib/utils/format-converter.js +325 -0
  182. package/lib/utils/git-utils.js +337 -0
  183. package/lib/utils/llm-detector.js +409 -0
  184. package/lib/utils/logger.js +278 -0
  185. package/lib/utils/token-utils.js +200 -0
  186. package/lib/utils/tokenizer-adapter.js +454 -0
  187. package/lib/utils/updater.js +565 -0
  188. package/lib/watch/FileWatcher.js +208 -0
  189. package/lib/watch/IncrementalAnalyzer.js +206 -0
  190. package/package.json +118 -0
@@ -0,0 +1,388 @@
1
+ /**
2
+ * CacheManager - Caching System Module
3
+ * v3.0.0 - Performance optimizations
4
+ *
5
+ * Responsibilities:
6
+ * - Cache analysis results
7
+ * - Invalidate on file changes
8
+ * - Support multiple storage backends
9
+ * - TTL-based expiration
10
+ */
11
+
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import crypto from 'crypto';
15
+ import { getLogger } from '../utils/logger.js';
16
+
17
+ const logger = getLogger('CacheManager');
18
+
19
+ export class CacheManager {
20
+ constructor(options = {}) {
21
+ this.options = {
22
+ enabled: true,
23
+ strategy: 'disk', // disk, memory
24
+ ttl: 3600, // 1 hour in seconds
25
+ path: '.context-cache',
26
+ maxSize: 100 * 1024 * 1024, // 100MB
27
+ ...options
28
+ };
29
+
30
+ this.memoryCache = new Map();
31
+ this.stats = {
32
+ hits: 0,
33
+ misses: 0,
34
+ writes: 0,
35
+ evictions: 0,
36
+ errors: 0
37
+ };
38
+
39
+ // Initialize cache directory
40
+ if (this.options.strategy === 'disk') {
41
+ this.initCacheDirectory();
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Initialize cache directory
47
+ */
48
+ initCacheDirectory() {
49
+ const cachePath = path.resolve(this.options.path);
50
+
51
+ if (!fs.existsSync(cachePath)) {
52
+ try {
53
+ fs.mkdirSync(cachePath, { recursive: true });
54
+ logger.info(`Cache directory created: ${cachePath}`);
55
+ } catch (error) {
56
+ logger.error(`Failed to create cache directory: ${error.message}`);
57
+ this.options.enabled = false;
58
+ }
59
+ }
60
+ }
61
+
62
+ /**
63
+ * Get cached analysis for file
64
+ * @param {string} filePath
65
+ * @param {number} modifiedTime - File modification timestamp
66
+ * @returns {FileAnalysis|null}
67
+ */
68
+ get(filePath, modifiedTime) {
69
+ if (!this.options.enabled) {
70
+ return null;
71
+ }
72
+
73
+ const key = this.getCacheKey(filePath);
74
+
75
+ try {
76
+ if (this.options.strategy === 'memory') {
77
+ return this.getFromMemory(key, modifiedTime);
78
+ } else {
79
+ return this.getFromDisk(key, modifiedTime);
80
+ }
81
+ } catch (error) {
82
+ logger.error(`Cache get error: ${error.message}`);
83
+ this.stats.errors++;
84
+ return null;
85
+ }
86
+ }
87
+
88
+ /**
89
+ * Get from memory cache
90
+ * @param {string} key
91
+ * @param {number} modifiedTime
92
+ * @returns {FileAnalysis|null}
93
+ */
94
+ getFromMemory(key, modifiedTime) {
95
+ const cached = this.memoryCache.get(key);
96
+
97
+ if (!cached) {
98
+ this.stats.misses++;
99
+ return null;
100
+ }
101
+
102
+ // Check if expired
103
+ if (this.isExpired(cached.timestamp, cached.modifiedTime, modifiedTime)) {
104
+ this.memoryCache.delete(key);
105
+ this.stats.misses++;
106
+ return null;
107
+ }
108
+
109
+ this.stats.hits++;
110
+ logger.debug(`Cache hit (memory): ${key}`);
111
+ return cached.data;
112
+ }
113
+
114
+ /**
115
+ * Get from disk cache
116
+ * @param {string} key
117
+ * @param {number} modifiedTime
118
+ * @returns {FileAnalysis|null}
119
+ */
120
+ getFromDisk(key, modifiedTime) {
121
+ const cacheFile = path.join(this.options.path, `${key}.json`);
122
+
123
+ if (!fs.existsSync(cacheFile)) {
124
+ this.stats.misses++;
125
+ return null;
126
+ }
127
+
128
+ try {
129
+ const content = fs.readFileSync(cacheFile, 'utf-8');
130
+ const cached = JSON.parse(content);
131
+
132
+ // Check if expired
133
+ if (this.isExpired(cached.timestamp, cached.modifiedTime, modifiedTime)) {
134
+ fs.unlinkSync(cacheFile);
135
+ this.stats.misses++;
136
+ return null;
137
+ }
138
+
139
+ this.stats.hits++;
140
+ logger.debug(`Cache hit (disk): ${key}`);
141
+ return cached.data;
142
+
143
+ } catch (error) {
144
+ this.stats.misses++;
145
+ return null;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Set cache entry
151
+ * @param {string} filePath
152
+ * @param {FileAnalysis} analysis
153
+ * @param {number} modifiedTime
154
+ */
155
+ set(filePath, analysis, modifiedTime) {
156
+ if (!this.options.enabled) {
157
+ return;
158
+ }
159
+
160
+ const key = this.getCacheKey(filePath);
161
+ const cacheEntry = {
162
+ data: analysis,
163
+ timestamp: Date.now(),
164
+ modifiedTime: modifiedTime
165
+ };
166
+
167
+ try {
168
+ if (this.options.strategy === 'memory') {
169
+ this.setInMemory(key, cacheEntry);
170
+ } else {
171
+ this.setOnDisk(key, cacheEntry);
172
+ }
173
+
174
+ this.stats.writes++;
175
+ logger.debug(`Cache set: ${key}`);
176
+
177
+ } catch (error) {
178
+ logger.error(`Cache set error: ${error.message}`);
179
+ this.stats.errors++;
180
+ }
181
+ }
182
+
183
+ /**
184
+ * Set in memory cache
185
+ * @param {string} key
186
+ * @param {object} cacheEntry
187
+ */
188
+ setInMemory(key, cacheEntry) {
189
+ this.memoryCache.set(key, cacheEntry);
190
+
191
+ // Check memory limit
192
+ if (this.memoryCache.size > 1000) {
193
+ this.evictOldest();
194
+ }
195
+ }
196
+
197
+ /**
198
+ * Set on disk cache
199
+ * @param {string} key
200
+ * @param {object} cacheEntry
201
+ */
202
+ setOnDisk(key, cacheEntry) {
203
+ const cacheFile = path.join(this.options.path, `${key}.json`);
204
+ fs.writeFileSync(cacheFile, JSON.stringify(cacheEntry), 'utf-8');
205
+ }
206
+
207
+ /**
208
+ * Check if cache entry is expired
209
+ * @param {number} cacheTimestamp - When cache was created
210
+ * @param {number} cacheModifiedTime - File modified time when cached
211
+ * @param {number} currentModifiedTime - Current file modified time
212
+ * @returns {boolean}
213
+ */
214
+ isExpired(cacheTimestamp, cacheModifiedTime, currentModifiedTime) {
215
+ // Check TTL
216
+ const age = (Date.now() - cacheTimestamp) / 1000; // seconds
217
+ if (age > this.options.ttl) {
218
+ return true;
219
+ }
220
+
221
+ // Check if file was modified
222
+ if (currentModifiedTime > cacheModifiedTime) {
223
+ return true;
224
+ }
225
+
226
+ return false;
227
+ }
228
+
229
+ /**
230
+ * Generate cache key from file path
231
+ * @param {string} filePath
232
+ * @returns {string}
233
+ */
234
+ getCacheKey(filePath) {
235
+ return crypto
236
+ .createHash('md5')
237
+ .update(filePath)
238
+ .digest('hex');
239
+ }
240
+
241
+ /**
242
+ * Clear all cache
243
+ */
244
+ clear() {
245
+ if (this.options.strategy === 'memory') {
246
+ this.memoryCache.clear();
247
+ } else {
248
+ this.clearDiskCache();
249
+ }
250
+
251
+ logger.info('Cache cleared');
252
+ }
253
+
254
+ /**
255
+ * Clear disk cache
256
+ */
257
+ clearDiskCache() {
258
+ try {
259
+ const cachePath = path.resolve(this.options.path);
260
+
261
+ if (fs.existsSync(cachePath)) {
262
+ const files = fs.readdirSync(cachePath);
263
+ for (const file of files) {
264
+ fs.unlinkSync(path.join(cachePath, file));
265
+ }
266
+ }
267
+ } catch (error) {
268
+ logger.error(`Failed to clear disk cache: ${error.message}`);
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Evict oldest entry (LRU)
274
+ */
275
+ evictOldest() {
276
+ let oldestKey = null;
277
+ let oldestTime = Infinity;
278
+
279
+ for (const [key, entry] of this.memoryCache) {
280
+ if (entry.timestamp < oldestTime) {
281
+ oldestTime = entry.timestamp;
282
+ oldestKey = key;
283
+ }
284
+ }
285
+
286
+ if (oldestKey) {
287
+ this.memoryCache.delete(oldestKey);
288
+ this.stats.evictions++;
289
+ logger.debug(`Evicted oldest entry: ${oldestKey}`);
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Get cache statistics
295
+ * @returns {object}
296
+ */
297
+ getStats() {
298
+ const totalRequests = this.stats.hits + this.stats.misses;
299
+ const hitRate = totalRequests > 0
300
+ ? (this.stats.hits / totalRequests * 100).toFixed(1)
301
+ : 0;
302
+
303
+ return {
304
+ ...this.stats,
305
+ totalRequests,
306
+ hitRate: parseFloat(hitRate),
307
+ cacheSize: this.options.strategy === 'memory'
308
+ ? this.memoryCache.size
309
+ : this.getDiskCacheSize()
310
+ };
311
+ }
312
+
313
+ /**
314
+ * Get disk cache size
315
+ * @returns {number}
316
+ */
317
+ getDiskCacheSize() {
318
+ try {
319
+ const cachePath = path.resolve(this.options.path);
320
+ if (!fs.existsSync(cachePath)) return 0;
321
+
322
+ const files = fs.readdirSync(cachePath);
323
+ return files.length;
324
+ } catch (error) {
325
+ return 0;
326
+ }
327
+ }
328
+
329
+ /**
330
+ * Prune expired entries
331
+ */
332
+ prune() {
333
+ logger.info('Pruning expired cache entries...');
334
+
335
+ if (this.options.strategy === 'memory') {
336
+ this.pruneMemoryCache();
337
+ } else {
338
+ this.pruneDiskCache();
339
+ }
340
+ }
341
+
342
+ /**
343
+ * Prune memory cache
344
+ */
345
+ pruneMemoryCache() {
346
+ const now = Date.now();
347
+ const ttlMs = this.options.ttl * 1000;
348
+ let pruned = 0;
349
+
350
+ for (const [key, entry] of this.memoryCache) {
351
+ if (now - entry.timestamp > ttlMs) {
352
+ this.memoryCache.delete(key);
353
+ pruned++;
354
+ }
355
+ }
356
+
357
+ logger.info(`Pruned ${pruned} expired entries from memory cache`);
358
+ }
359
+
360
+ /**
361
+ * Prune disk cache
362
+ */
363
+ pruneDiskCache() {
364
+ try {
365
+ const cachePath = path.resolve(this.options.path);
366
+ const files = fs.readdirSync(cachePath);
367
+ const now = Date.now();
368
+ const ttlMs = this.options.ttl * 1000;
369
+ let pruned = 0;
370
+
371
+ for (const file of files) {
372
+ const filePath = path.join(cachePath, file);
373
+ const stats = fs.statSync(filePath);
374
+
375
+ if (now - stats.mtimeMs > ttlMs) {
376
+ fs.unlinkSync(filePath);
377
+ pruned++;
378
+ }
379
+ }
380
+
381
+ logger.info(`Pruned ${pruned} expired entries from disk cache`);
382
+ } catch (error) {
383
+ logger.error(`Failed to prune disk cache: ${error.message}`);
384
+ }
385
+ }
386
+ }
387
+
388
+ export default CacheManager;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Analyzer - Token & Method Analysis Module
3
+ * v3.0.0 - Modular architecture
4
+ */
5
+
6
+ import fs from 'fs';
7
+ import TokenUtils from '../utils/token-utils.js';
8
+ import FileUtils from '../utils/file-utils.js';
9
+ import MethodAnalyzer from '../analyzers/method-analyzer.js';
10
+ import { getLogger } from '../utils/logger.js';
11
+
12
+ const logger = getLogger('Analyzer');
13
+
14
+ export class Analyzer {
15
+ constructor(options = {}) {
16
+ this.options = {
17
+ methodLevel: false,
18
+ parallel: false,
19
+ maxWorkers: 4,
20
+ ...options
21
+ };
22
+
23
+ this.methodAnalyzer = new MethodAnalyzer();
24
+ this.stats = this.initStats();
25
+ }
26
+
27
+ initStats() {
28
+ return {
29
+ totalFiles: 0,
30
+ totalTokens: 0,
31
+ totalSize: 0,
32
+ totalMethods: 0,
33
+ byLanguage: {},
34
+ largestFiles: [],
35
+ analysisTime: 0
36
+ };
37
+ }
38
+
39
+ async analyze(files) {
40
+ logger.info(`Analyzing ${files.length} files...`);
41
+ const startTime = Date.now();
42
+
43
+ this.reset();
44
+
45
+ const results = await this.analyzeSequential(files);
46
+
47
+ this.stats.analysisTime = Date.now() - startTime;
48
+ logger.info(`Analysis complete in ${this.stats.analysisTime}ms`);
49
+
50
+ return {
51
+ files: results,
52
+ stats: this.getStats()
53
+ };
54
+ }
55
+
56
+ async analyzeSequential(files) {
57
+ const results = [];
58
+
59
+ for (const file of files) {
60
+ const analysis = await this.analyzeFile(file);
61
+ if (analysis) {
62
+ results.push(analysis);
63
+ this.updateStats(analysis);
64
+ }
65
+ }
66
+
67
+ return results;
68
+ }
69
+
70
+ async analyzeFile(fileInfo) {
71
+ try {
72
+ // Skip if not a text file
73
+ if (!FileUtils.isText(fileInfo.path)) {
74
+ return null;
75
+ }
76
+
77
+ const content = fs.readFileSync(fileInfo.path, 'utf-8');
78
+ const tokens = TokenUtils.calculate(content, fileInfo.path);
79
+
80
+ const analysis = {
81
+ path: fileInfo.path,
82
+ relativePath: fileInfo.relativePath,
83
+ name: fileInfo.name,
84
+ extension: fileInfo.extension,
85
+ size: fileInfo.size,
86
+ tokens: tokens,
87
+ lines: content.split('\n').length,
88
+ language: this.detectLanguage(fileInfo.extension)
89
+ };
90
+
91
+ if (this.options.methodLevel) {
92
+ const methods = this.methodAnalyzer.extractMethods(content, fileInfo.path);
93
+ analysis.methods = methods;
94
+ analysis.methodCount = methods.length;
95
+ }
96
+
97
+ return analysis;
98
+
99
+ } catch (error) {
100
+ logger.error(`Error analyzing file ${fileInfo.relativePath}: ${error.message}`);
101
+ return null;
102
+ }
103
+ }
104
+
105
+ updateStats(analysis) {
106
+ this.stats.totalFiles++;
107
+ this.stats.totalTokens += analysis.tokens;
108
+ this.stats.totalSize += analysis.size;
109
+
110
+ if (!this.stats.byLanguage[analysis.language]) {
111
+ this.stats.byLanguage[analysis.language] = {
112
+ files: 0,
113
+ tokens: 0,
114
+ size: 0
115
+ };
116
+ }
117
+
118
+ this.stats.byLanguage[analysis.language].files++;
119
+ this.stats.byLanguage[analysis.language].tokens += analysis.tokens;
120
+ this.stats.byLanguage[analysis.language].size += analysis.size;
121
+
122
+ this.stats.largestFiles.push({
123
+ path: analysis.relativePath,
124
+ tokens: analysis.tokens,
125
+ size: analysis.size
126
+ });
127
+
128
+ this.stats.largestFiles.sort((a, b) => b.tokens - a.tokens);
129
+ this.stats.largestFiles = this.stats.largestFiles.slice(0, 10);
130
+
131
+ if (analysis.methods) {
132
+ this.stats.totalMethods += analysis.methodCount || 0;
133
+ }
134
+ }
135
+
136
+ detectLanguage(extension) {
137
+ const languageMap = {
138
+ '.js': 'JavaScript',
139
+ '.jsx': 'JavaScript',
140
+ '.ts': 'TypeScript',
141
+ '.tsx': 'TypeScript',
142
+ '.py': 'Python',
143
+ '.rb': 'Ruby',
144
+ '.php': 'PHP',
145
+ '.java': 'Java',
146
+ '.kt': 'Kotlin',
147
+ '.cs': 'C#',
148
+ '.go': 'Go',
149
+ '.rs': 'Rust',
150
+ '.swift': 'Swift',
151
+ '.c': 'C',
152
+ '.cpp': 'C++',
153
+ '.h': 'C/C++',
154
+ '.hpp': 'C++',
155
+ '.scala': 'Scala'
156
+ };
157
+
158
+ return languageMap[extension] || 'Other';
159
+ }
160
+
161
+ getStats() {
162
+ return { ...this.stats };
163
+ }
164
+
165
+ reset() {
166
+ this.stats = this.initStats();
167
+ }
168
+
169
+ getLanguageDistribution() {
170
+ return Object.entries(this.stats.byLanguage)
171
+ .map(([language, stats]) => ({
172
+ language,
173
+ ...stats,
174
+ percentage: (stats.tokens / this.stats.totalTokens) * 100
175
+ }))
176
+ .sort((a, b) => b.tokens - a.tokens);
177
+ }
178
+ }
179
+
180
+ export default Analyzer;