stigmergy 1.2.0 → 1.2.6

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 (125) hide show
  1. package/LICENSE +18 -18
  2. package/README.md +28 -223
  3. package/STIGMERGY.md +61 -61
  4. package/docs/PROJECT_CONSTITUTION.md +433 -433
  5. package/docs/PROJECT_STRUCTURE_CURRENT.md +80 -80
  6. package/examples/calculator-example.js +72 -72
  7. package/examples/cline_usage_examples.md +364 -364
  8. package/examples/encryption-example.js +67 -67
  9. package/examples/json-parser-example.js +120 -120
  10. package/examples/json-validation-example.js +64 -64
  11. package/examples/rest-client-example.js +52 -52
  12. package/examples/rest_client_example.js +54 -54
  13. package/package.json +36 -15
  14. package/scripts/build.js +74 -74
  15. package/scripts/post-deployment-config.js +296 -296
  16. package/scripts/preinstall-check.js +173 -173
  17. package/scripts/publish.js +58 -268
  18. package/scripts/run-layered-tests.js +247 -0
  19. package/scripts/safe-install.js +139 -139
  20. package/scripts/simple-publish.js +57 -59
  21. package/src/adapters/claude/install_claude_integration.js +292 -0
  22. package/src/adapters/codebuddy/install_codebuddy_integration.js +349 -0
  23. package/src/adapters/codex/install_codex_integration.js +395 -0
  24. package/src/adapters/copilot/install_copilot_integration.js +716 -0
  25. package/src/adapters/gemini/install_gemini_integration.js +304 -0
  26. package/src/adapters/iflow/install_iflow_integration.js +304 -0
  27. package/src/adapters/qoder/install_qoder_integration.js +1090 -0
  28. package/src/adapters/qwen/install_qwen_integration.js +285 -0
  29. package/src/auth.js +173 -173
  30. package/src/auth_command.js +208 -208
  31. package/src/calculator.js +313 -313
  32. package/src/cli/router.js +417 -38
  33. package/src/core/cache_cleaner.js +767 -744
  34. package/src/core/cli_help_analyzer.js +680 -674
  35. package/src/core/cli_parameter_handler.js +132 -127
  36. package/src/core/cli_tools.js +89 -89
  37. package/src/core/coordination/index.js +16 -16
  38. package/src/core/coordination/nodejs/AdapterManager.js +102 -89
  39. package/src/core/coordination/nodejs/CLCommunication.js +132 -124
  40. package/src/core/coordination/nodejs/CLIIntegrationManager.js +272 -236
  41. package/src/core/coordination/nodejs/HealthChecker.js +76 -77
  42. package/src/core/coordination/nodejs/HookDeploymentManager.js +263 -190
  43. package/src/core/coordination/nodejs/StatisticsCollector.js +71 -71
  44. package/src/core/coordination/nodejs/index.js +90 -72
  45. package/src/core/coordination/nodejs/utils/Logger.js +29 -29
  46. package/src/core/enhanced_installer.js +479 -456
  47. package/src/core/enhanced_uninstaller.js +638 -618
  48. package/src/core/error_handler.js +406 -406
  49. package/src/core/installer.js +815 -294
  50. package/src/core/memory_manager.js +83 -83
  51. package/src/core/rest_client.js +160 -160
  52. package/src/core/smart_router.js +249 -146
  53. package/src/core/upgrade_manager.js +76 -59
  54. package/src/data_encryption.js +143 -143
  55. package/src/data_structures.js +440 -440
  56. package/src/deploy.js +55 -55
  57. package/src/index.js +30 -30
  58. package/src/test/cli-availability-checker.js +194 -0
  59. package/src/test/test-environment.js +289 -0
  60. package/src/utils/helpers.js +35 -35
  61. package/src/utils.js +921 -915
  62. package/src/weatherProcessor.js +228 -228
  63. package/test/cache-cleaner-implemented.test.js +0 -328
  64. package/test/cache-cleaner.test.js +0 -390
  65. package/test/calculator.test.js +0 -215
  66. package/test/collision-test.js +0 -26
  67. package/test/comprehensive-enhanced-features.test.js +0 -252
  68. package/test/comprehensive-execution-test.js +0 -428
  69. package/test/conflict-prevention-test.js +0 -95
  70. package/test/cross-cli-detection-test.js +0 -33
  71. package/test/csv-processing-test.js +0 -36
  72. package/test/deploy-hooks-test.js +0 -250
  73. package/test/e2e/claude-cli-test.js +0 -128
  74. package/test/e2e/collaboration-test.js +0 -75
  75. package/test/e2e/comprehensive-test.js +0 -431
  76. package/test/e2e/error-handling-test.js +0 -90
  77. package/test/e2e/individual-tool-test.js +0 -143
  78. package/test/e2e/other-cli-test.js +0 -130
  79. package/test/e2e/qoder-cli-test.js +0 -128
  80. package/test/e2e/run-e2e-tests.js +0 -73
  81. package/test/e2e/test-data.js +0 -88
  82. package/test/e2e/test-utils.js +0 -222
  83. package/test/encryption-simple-test.js +0 -110
  84. package/test/encryption.test.js +0 -129
  85. package/test/enhanced-main-alignment.test.js +0 -298
  86. package/test/enhanced-uninstaller-implemented.test.js +0 -271
  87. package/test/enhanced-uninstaller.test.js +0 -284
  88. package/test/error-handling-test.js +0 -341
  89. package/test/fibonacci.test.js +0 -178
  90. package/test/final-deploy-test.js +0 -221
  91. package/test/final-install-test.js +0 -226
  92. package/test/hash-table-demo.js +0 -33
  93. package/test/hash-table-test.js +0 -26
  94. package/test/hash_table_test.js +0 -114
  95. package/test/hook-system-integration-test.js +0 -307
  96. package/test/iflow-integration-test.js +0 -292
  97. package/test/improved-install-test.js +0 -362
  98. package/test/install-command-test.js +0 -370
  99. package/test/json-parser-test.js +0 -161
  100. package/test/json-validation-test.js +0 -164
  101. package/test/natural-language-skills-test.js +0 -320
  102. package/test/nl-integration-test.js +0 -179
  103. package/test/parameter-parsing-test.js +0 -143
  104. package/test/plugin-deployment-test.js +0 -316
  105. package/test/postinstall-test.js +0 -269
  106. package/test/python-plugins-test.js +0 -259
  107. package/test/real-test.js +0 -435
  108. package/test/remaining-adapters-test.js +0 -256
  109. package/test/rest-client-test.js +0 -56
  110. package/test/rest_client.test.js +0 -85
  111. package/test/safe-installation-cleaner.test.js +0 -343
  112. package/test/simple-iflow-hook-test.js +0 -137
  113. package/test/stigmergy-upgrade-test.js +0 -243
  114. package/test/system-compatibility-test.js +0 -467
  115. package/test/tdd-deploy-fix-test.js +0 -324
  116. package/test/tdd-fixes-test.js +0 -211
  117. package/test/third-party-skills-test.js +0 -321
  118. package/test/tool-selection-integration-test.js +0 -158
  119. package/test/unit/calculator-full.test.js +0 -191
  120. package/test/unit/calculator-simple.test.js +0 -96
  121. package/test/unit/calculator.test.js +0 -97
  122. package/test/unit/cli-scanner.test.js +0 -291
  123. package/test/unit/cli_parameter_handler.test.js +0 -116
  124. package/test/unit/cross-cli-executor.test.js +0 -399
  125. package/test/weather-processor.test.js +0 -104
@@ -1,744 +1,767 @@
1
- /**
2
- * Comprehensive Cache Cleaner for Stigmergy
3
- *
4
- * Intelligent cache cleaning with selective removal, performance optimization,
5
- * and error recovery capabilities.
6
- */
7
-
8
- const fs = require('fs');
9
- const path = require('path');
10
- const os = require('os');
11
- const { spawnSync } = require('child_process');
12
-
13
- class CacheCleaner {
14
- constructor(options = {}) {
15
- this.options = {
16
- dryRun: options.dryRun || false,
17
- force: options.force || false,
18
- verbose: options.verbose || false,
19
- preserveRecent: options.preserveRecent || 24 * 60 * 60 * 1000, // 24 hours
20
- batchSize: options.batchSize || 50,
21
- parallel: options.parallel || true,
22
- ...options
23
- };
24
-
25
- this.homeDir = os.homedir();
26
- this.results = {
27
- filesRemoved: 0,
28
- directoriesRemoved: 0,
29
- bytesFreed: 0,
30
- errors: [],
31
- skipped: []
32
- };
33
- }
34
-
35
- /**
36
- * Clean all caches comprehensively
37
- */
38
- async cleanAllCaches(options = {}) {
39
- const config = {
40
- cleanStigmergy: true,
41
- cleanNPX: true,
42
- cleanNPM: true,
43
- cleanCLI: true,
44
- cleanTemp: true,
45
- ...options
46
- };
47
-
48
- console.log('🧹 Starting Comprehensive Cache Cleaning...\n');
49
-
50
- if (this.options.dryRun) {
51
- console.log('šŸ” DRY RUN MODE - No files will be deleted\n');
52
- }
53
-
54
- try {
55
- // 1. Clean Stigmergy cache
56
- if (config.cleanStigmergy) {
57
- await this.cleanStigmergyCache();
58
- }
59
-
60
- // 2. Clean NPX cache
61
- if (config.cleanNPX) {
62
- await this.cleanNPXCache();
63
- }
64
-
65
- // 3. Clean NPM cache
66
- if (config.cleanNPM) {
67
- await this.cleanNPMCache();
68
- }
69
-
70
- // 4. Clean CLI configurations
71
- if (config.cleanCLI) {
72
- await this.cleanCLIConfigurations();
73
- }
74
-
75
- // 5. Clean temporary files
76
- if (config.cleanTemp) {
77
- await this.cleanTemporaryFiles();
78
- }
79
-
80
- // 6. Print summary
81
- this.printSummary();
82
-
83
- return this.results;
84
-
85
- } catch (error) {
86
- console.error('āŒ Cache cleaning failed:', error.message);
87
- this.results.errors.push(error.message);
88
- return this.results;
89
- }
90
- }
91
-
92
- /**
93
- * Clean Stigmergy cache and temporary files
94
- */
95
- async cleanStigmergyCache() {
96
- console.log('šŸ“ Cleaning Stigmergy cache...');
97
-
98
- const stigmergyDir = path.join(this.homeDir, '.stigmergy');
99
- const testDir = path.join(this.homeDir, '.stigmergy-test');
100
-
101
- // Clean main cache directory
102
- if (fs.existsSync(stigmergyDir)) {
103
- await this.cleanStigmergyDirectory(stigmergyDir, 'main');
104
- }
105
-
106
- // Clean test directory
107
- if (fs.existsSync(testDir)) {
108
- await this.cleanStigmergyDirectory(testDir, 'test');
109
- }
110
-
111
- // Clean cache subdirectories specifically
112
- const cachePaths = [
113
- path.join(stigmergyDir, 'cache'),
114
- path.join(stigmergyDir, 'logs'),
115
- path.join(stigmergyDir, 'temp'),
116
- path.join(stigmergyDir, '.tmp')
117
- ];
118
-
119
- for (const cachePath of cachePaths) {
120
- if (fs.existsSync(cachePath)) {
121
- await this.cleanDirectory(cachePath);
122
- }
123
- }
124
-
125
- console.log('āœ… Stigmergy cache cleaning completed');
126
- }
127
-
128
- /**
129
- * Clean a specific Stigmergy directory
130
- */
131
- async cleanStigmergyDirectory(dirPath, type) {
132
- console.log(` šŸ“‚ Cleaning ${type} directory...`);
133
-
134
- const files = await this.scanDirectory(dirPath);
135
- const recentFiles = this.filterRecentFiles(files);
136
-
137
- if (recentFiles.length === 0) {
138
- console.log(` ā„¹ļø No recent files to clean in ${type} directory`);
139
- return;
140
- }
141
-
142
- console.log(` šŸ“‹ Found ${recentFiles.length} files to clean`);
143
-
144
- if (this.options.dryRun) {
145
- this.logFiles(recentFiles, ' šŸ” ');
146
- return;
147
- }
148
-
149
- const removed = await this.batchRemoveFiles(recentFiles);
150
- console.log(` āœ… Removed ${removed} files from ${type} directory`);
151
- }
152
-
153
- /**
154
- * Clean NPX cache of Stigmergy entries
155
- */
156
- async cleanNPXCache() {
157
- console.log('šŸ“¦ Cleaning NPX cache...');
158
-
159
- const npxCacheDirs = await this.findNPXCacheDirectories();
160
-
161
- if (npxCacheDirs.length === 0) {
162
- console.log(' ā„¹ļø No Stigmergy entries in NPX cache');
163
- return;
164
- }
165
-
166
- console.log(` šŸ“¦ Found ${npxCacheDirs.length} Stigmergy cache entries`);
167
-
168
- if (this.options.dryRun) {
169
- this.logFiles(npxCacheDirs, ' šŸ” ');
170
- return;
171
- }
172
-
173
- let removed = 0;
174
- const failed = [];
175
-
176
- for (const cacheDir of npxCacheDirs) {
177
- try {
178
- const size = await this.getDirectorySize(cacheDir);
179
- await this.removeDirectory(cacheDir);
180
- this.results.bytesFreed += size;
181
- removed++;
182
- } catch (error) {
183
- failed.push(cacheDir);
184
- this.results.errors.push(`NPX cache ${cacheDir}: ${error.message}`);
185
- }
186
- }
187
-
188
- console.log(` āœ… Removed ${removed} NPX cache entries`);
189
- if (failed.length > 0) {
190
- console.log(` āš ļø Failed to remove ${failed.length} entries`);
191
- }
192
- }
193
-
194
- /**
195
- * Clean NPM cache
196
- */
197
- async cleanNPMCache() {
198
- console.log('šŸ“¦ Cleaning NPM cache...');
199
-
200
- try {
201
- // Use npm cache clean command
202
- if (this.options.dryRun) {
203
- console.log(' šŸ” Would run: npm cache clean --force');
204
- return;
205
- }
206
-
207
- const result = spawnSync('npm', ['cache', 'clean', '--force'], {
208
- encoding: 'utf8',
209
- shell: true,
210
- stdio: this.options.verbose ? 'inherit' : 'pipe'
211
- });
212
-
213
- if (result.status === 0) {
214
- console.log(' āœ… NPM cache cleaned successfully');
215
- } else {
216
- console.log(' āš ļø NPM cache clean failed, trying manual cleanup');
217
- await this.manualNPMCacheClean();
218
- }
219
-
220
- } catch (error) {
221
- console.error(` āŒ Failed to clean NPM cache: ${error.message}`);
222
- this.results.errors.push(`NPM cache: ${error.message}`);
223
- }
224
- }
225
-
226
- /**
227
- * Manual NPM cache cleaning fallback
228
- */
229
- async manualNPMCacheClean() {
230
- const npmCacheDirs = [
231
- path.join(this.homeDir, '.npm', '_cacache'),
232
- path.join(this.homeDir, 'AppData', 'Local', 'npm-cache', '_cacache')
233
- ];
234
-
235
- for (const cacheDir of npmCacheDirs) {
236
- if (fs.existsSync(cacheDir)) {
237
- console.log(` 🧹 Manual cleanup of ${cacheDir}`);
238
- await this.cleanDirectory(cacheDir);
239
- }
240
- }
241
- }
242
-
243
- /**
244
- * Clean CLI configurations
245
- */
246
- async cleanCLIConfigurations() {
247
- console.log('āš™ļø Cleaning CLI configurations...');
248
-
249
- const supportedCLIs = [
250
- 'claude', 'gemini', 'qwen', 'iflow', 'qodercli',
251
- 'codebuddy', 'codex', 'copilot', 'qwencode'
252
- ];
253
-
254
- let totalCleaned = 0;
255
-
256
- for (const cli of supportedCLIs) {
257
- const cliConfig = path.join(this.homeDir, `.${cli}`);
258
-
259
- if (!fs.existsSync(cliConfig)) {
260
- continue;
261
- }
262
-
263
- const stigmergyFiles = await this.findStigmergyFiles(cliConfig);
264
-
265
- if (stigmergyFiles.length === 0) {
266
- continue;
267
- }
268
-
269
- console.log(` šŸ“‚ ${cli}: ${stigmergyFiles.length} Stigmergy files`);
270
-
271
- if (this.options.dryRun) {
272
- this.logFiles(stigmergyFiles, ' šŸ” ');
273
- continue;
274
- }
275
-
276
- const removed = await this.batchRemoveFiles(stigmergyFiles);
277
- totalCleaned += removed;
278
-
279
- if (removed > 0) {
280
- console.log(` āœ… Cleaned ${removed} files from ${cli}`);
281
- }
282
- }
283
-
284
- if (totalCleaned > 0) {
285
- console.log(` āœ… Cleaned ${totalCleaned} CLI configuration files`);
286
- }
287
- }
288
-
289
- /**
290
- * Clean temporary files
291
- */
292
- async cleanTemporaryFiles() {
293
- console.log('šŸ—‘ļø Cleaning temporary files...');
294
-
295
- const tempDirs = [
296
- os.tmpdir(),
297
- path.join(this.homeDir, 'AppData', 'Local', 'Temp'),
298
- path.join(this.homeDir, 'AppData', 'Local', 'npm-cache', '_tmp')
299
- ];
300
-
301
- let totalRemoved = 0;
302
-
303
- for (const tempDir of tempDirs) {
304
- if (!fs.existsSync(tempDir)) {
305
- continue;
306
- }
307
-
308
- const tempFiles = await this.findStigmergyTempFiles(tempDir);
309
-
310
- if (tempFiles.length === 0) {
311
- continue;
312
- }
313
-
314
- console.log(` šŸ“‚ ${path.basename(tempDir)}: ${tempFiles.length} temporary files`);
315
-
316
- if (this.options.dryRun) {
317
- this.logFiles(tempFiles.slice(0, 5), ' šŸ” ');
318
- if (tempFiles.length > 5) {
319
- console.log(` ... and ${tempFiles.length - 5} more`);
320
- }
321
- continue;
322
- }
323
-
324
- const removed = await this.batchRemoveFiles(tempFiles);
325
- totalRemoved += removed;
326
-
327
- if (removed > 0) {
328
- console.log(` āœ… Removed ${removed} files from ${path.basename(tempDir)}`);
329
- }
330
- }
331
-
332
- if (totalRemoved > 0) {
333
- console.log(` āœ… Removed ${totalRemoved} temporary files`);
334
- }
335
- }
336
-
337
- /**
338
- * Selective cleaning with patterns
339
- */
340
- async selectiveClean(targetDirectory, options = {}) {
341
- const {
342
- preservePatterns = [],
343
- removePatterns = [],
344
- preserveRecent = this.options.preserveRecent
345
- } = options;
346
-
347
- console.log(`šŸŽÆ Selective cleaning: ${targetDirectory}`);
348
-
349
- if (!fs.existsSync(targetDirectory)) {
350
- console.log(' ā„¹ļø Directory not found');
351
- return;
352
- }
353
-
354
- const allFiles = await this.scanDirectory(targetDirectory);
355
- const filesToRemove = [];
356
-
357
- for (const file of allFiles) {
358
- // Check preserve patterns
359
- const shouldPreserve = preservePatterns.some(pattern =>
360
- this.matchPattern(file, pattern)
361
- ) || this.isRecentFile(file, preserveRecent);
362
-
363
- // Check remove patterns
364
- const shouldRemove = removePatterns.some(pattern =>
365
- this.matchPattern(file, pattern)
366
- );
367
-
368
- if (shouldRemove && !shouldPreserve) {
369
- filesToRemove.push(file);
370
- }
371
- }
372
-
373
- console.log(` šŸ“‹ Found ${filesToRemove.length} files to remove`);
374
-
375
- if (this.options.dryRun) {
376
- this.logFiles(filesToRemove, ' šŸ” ');
377
- return;
378
- }
379
-
380
- const removed = await this.batchRemoveFiles(filesToRemove);
381
- console.log(` āœ… Selectively removed ${removed} files`);
382
- }
383
-
384
- /**
385
- * Performance-optimized cleaning
386
- */
387
- async cleanWithPerformance(targetDirectory, options = {}) {
388
- const {
389
- batchSize = this.options.batchSize,
390
- parallel = this.options.parallel,
391
- maxConcurrency = 4
392
- } = options;
393
-
394
- console.log(`⚔ Performance cleaning: ${targetDirectory}`);
395
-
396
- const files = await this.scanDirectory(targetDirectory);
397
- const recentFiles = this.filterRecentFiles(files);
398
-
399
- console.log(` šŸ“Š Processing ${recentFiles.length} files in batches of ${batchSize}`);
400
-
401
- if (this.options.dryRun) {
402
- console.log(` šŸ” Would process in ${Math.ceil(recentFiles.length / batchSize)} batches`);
403
- return;
404
- }
405
-
406
- let removed = 0;
407
- const batches = this.createBatches(recentFiles, batchSize);
408
-
409
- if (parallel && batches.length > 1) {
410
- removed = await this.parallelRemoveBatches(batches, maxConcurrency);
411
- } else {
412
- for (const batch of batches) {
413
- const batchRemoved = await this.batchRemoveFiles(batch);
414
- removed += batchRemoved;
415
- }
416
- }
417
-
418
- console.log(` āœ… Performance cleaned ${removed} files`);
419
- return removed;
420
- }
421
-
422
- /**
423
- * Helper methods
424
- */
425
- async scanDirectory(dirPath, files = []) {
426
- try {
427
- const items = fs.readdirSync(dirPath, { withFileTypes: true });
428
-
429
- for (const item of items) {
430
- const fullPath = path.join(dirPath, item.name);
431
-
432
- if (item.isDirectory()) {
433
- await this.scanDirectory(fullPath, files);
434
- files.push(fullPath);
435
- } else {
436
- files.push(fullPath);
437
- }
438
- }
439
- } catch (error) {
440
- this.results.errors.push(`Scan error ${dirPath}: ${error.message}`);
441
- }
442
-
443
- return files;
444
- }
445
-
446
- async batchRemoveFiles(files) {
447
- let removed = 0;
448
-
449
- for (const file of files) {
450
- try {
451
- const stat = fs.statSync(file);
452
-
453
- if (this.removeFile(file)) {
454
- removed++;
455
- this.results.bytesFreed += stat.size;
456
- }
457
- } catch (error) {
458
- this.results.errors.push(`Remove error ${file}: ${error.message}`);
459
- }
460
- }
461
-
462
- this.results.filesRemoved += removed;
463
- return removed;
464
- }
465
-
466
- removeFile(filePath) {
467
- try {
468
- if (!fs.existsSync(filePath)) {
469
- return false;
470
- }
471
-
472
- const stat = fs.statSync(filePath);
473
-
474
- if (stat.isDirectory()) {
475
- fs.rmSync(filePath, { recursive: true, force: true });
476
- this.results.directoriesRemoved++;
477
- } else {
478
- fs.unlinkSync(filePath);
479
- }
480
-
481
- if (this.options.verbose) {
482
- console.log(` Removed: ${path.basename(filePath)}`);
483
- }
484
-
485
- return true;
486
- } catch (error) {
487
- if (!this.options.force) {
488
- throw error;
489
- }
490
- this.results.skipped.push(`${filePath}: ${error.message}`);
491
- return false;
492
- }
493
- }
494
-
495
- async removeDirectory(dirPath) {
496
- try {
497
- if (fs.existsSync(dirPath)) {
498
- const size = await this.getDirectorySize(dirPath);
499
- fs.rmSync(dirPath, { recursive: true, force: true });
500
- this.results.bytesFreed += size;
501
- this.results.directoriesRemoved++;
502
- return true;
503
- }
504
- return false;
505
- } catch (error) {
506
- if (!this.options.force) {
507
- throw error;
508
- }
509
- this.results.skipped.push(`Directory: ${dirPath} (${error.message})`);
510
- return false;
511
- }
512
- }
513
-
514
- async getDirectorySize(dirPath) {
515
- let totalSize = 0;
516
-
517
- try {
518
- const files = await this.scanDirectory(dirPath);
519
-
520
- for (const file of files) {
521
- try {
522
- const stat = fs.statSync(file);
523
- totalSize += stat.size;
524
- } catch (error) {
525
- // Skip files we can't stat
526
- }
527
- }
528
- } catch (error) {
529
- // Return 0 for directories we can't scan
530
- }
531
-
532
- return totalSize;
533
- }
534
-
535
- filterRecentFiles(files) {
536
- return files.filter(file => !this.isRecentFile(file, this.options.preserveRecent));
537
- }
538
-
539
- isRecentFile(filePath, maxAge) {
540
- try {
541
- const stat = fs.statSync(filePath);
542
- const age = Date.now() - stat.mtime.getTime();
543
- return age < maxAge;
544
- } catch (error) {
545
- return false;
546
- }
547
- }
548
-
549
- async findStigmergyFiles(dirPath) {
550
- const stigmergyFiles = [];
551
-
552
- try {
553
- const files = fs.readdirSync(dirPath, { withFileTypes: true });
554
-
555
- for (const file of files) {
556
- const fullPath = path.join(dirPath, file.name);
557
-
558
- if (file.isDirectory()) {
559
- stigmergyFiles.push(...await this.findStigmergyFiles(fullPath));
560
- } else if (this.isStigmergyFile(file.name)) {
561
- stigmergyFiles.push(fullPath);
562
- }
563
- }
564
- } catch (error) {
565
- // Skip directories we can't read
566
- }
567
-
568
- return stigmergyFiles;
569
- }
570
-
571
- isStigmergyFile(fileName) {
572
- const stigmergyPatterns = [
573
- 'stigmergy',
574
- 'cross-cli',
575
- 'hook',
576
- 'integration',
577
- 'cache',
578
- '.tmp',
579
- 'temp'
580
- ];
581
-
582
- const lowerFileName = fileName.toLowerCase();
583
- return stigmergyPatterns.some(pattern =>
584
- lowerFileName.includes(pattern.toLowerCase())
585
- );
586
- }
587
-
588
- async findNPXCacheDirectories() {
589
- const cacheDirs = [];
590
- const possibleNPXBases = [
591
- path.join(this.homeDir, 'AppData', 'Local', 'npm-cache', '_npx'),
592
- path.join(this.homeDir, '.npm', '_npx'),
593
- path.join(os.tmpdir(), 'npm-cache', '_npx')
594
- ];
595
-
596
- for (const npxCacheBase of possibleNPXBases) {
597
- if (!fs.existsSync(npxCacheBase)) {
598
- continue;
599
- }
600
-
601
- try {
602
- const entries = fs.readdirSync(npxCacheBase);
603
-
604
- for (const entry of entries) {
605
- const entryPath = path.join(npxCacheBase, entry);
606
- const stigmergyPath = path.join(entryPath, 'node_modules', 'stigmergy');
607
-
608
- if (fs.existsSync(stigmergyPath)) {
609
- cacheDirs.push(entryPath);
610
- }
611
- }
612
- } catch (error) {
613
- this.results.errors.push(`NPX scan error: ${error.message}`);
614
- }
615
- }
616
-
617
- return cacheDirs;
618
- }
619
-
620
- async findStigmergyTempFiles(tempDir) {
621
- const tempFiles = [];
622
-
623
- try {
624
- const files = fs.readdirSync(tempDir, { withFileTypes: true });
625
-
626
- for (const file of files) {
627
- if (this.isStigmergyFile(file.name) ||
628
- file.name.startsWith('stigmergy-') ||
629
- file.name.includes('stigmergy')) {
630
- const fullPath = path.join(tempDir, file.name);
631
- tempFiles.push(fullPath);
632
- }
633
- }
634
- } catch (error) {
635
- // Skip temp directories we can't read
636
- }
637
-
638
- return tempFiles;
639
- }
640
-
641
- async cleanDirectory(dirPath) {
642
- try {
643
- if (!fs.existsSync(dirPath)) {
644
- return;
645
- }
646
-
647
- const files = await this.scanDirectory(dirPath);
648
- const removed = await this.batchRemoveFiles(files);
649
-
650
- console.log(` 🧹 Cleaned ${removed} files from ${path.basename(dirPath)}`);
651
- } catch (error) {
652
- console.error(` āŒ Failed to clean ${dirPath}: ${error.message}`);
653
- this.results.errors.push(`Clean error ${dirPath}: ${error.message}`);
654
- }
655
- }
656
-
657
- matchPattern(filePath, pattern) {
658
- const fileName = path.basename(filePath);
659
-
660
- // Simple glob pattern matching
661
- const regexPattern = pattern
662
- .replace(/\*/g, '.*')
663
- .replace(/\?/g, '.');
664
-
665
- const regex = new RegExp(regexPattern, 'i');
666
- return regex.test(fileName);
667
- }
668
-
669
- createBatches(items, batchSize) {
670
- const batches = [];
671
- for (let i = 0; i < items.length; i += batchSize) {
672
- batches.push(items.slice(i, i + batchSize));
673
- }
674
- return batches;
675
- }
676
-
677
- async parallelRemoveBatches(batches, maxConcurrency) {
678
- let totalRemoved = 0;
679
- const executing = [];
680
-
681
- for (const batch of batches) {
682
- const promise = this.batchRemoveFiles(batch);
683
- executing.push(promise);
684
-
685
- if (executing.length >= maxConcurrency) {
686
- await Promise.race(executing);
687
- executing.splice(0, 1);
688
- }
689
- }
690
-
691
- await Promise.all(executing);
692
- return totalRemoved;
693
- }
694
-
695
- formatBytes(bytes) {
696
- const sizes = ['Bytes', 'KB', 'MB', 'GB'];
697
- if (bytes === 0) return '0 Bytes';
698
- const i = Math.floor(Math.log(bytes) / Math.log(1024));
699
- return Math.round(bytes / Math.pow(1024, i) * 100) / 100 + ' ' + sizes[i];
700
- }
701
-
702
- logFiles(files, prefix = '') {
703
- files.slice(0, 10).forEach(file => {
704
- console.log(`${prefix}${path.basename(file)}`);
705
- });
706
-
707
- if (files.length > 10) {
708
- console.log(`${prefix}... and ${files.length - 10} more`);
709
- }
710
- }
711
-
712
- printSummary() {
713
- console.log('\nšŸ“Š CACHE CLEANING SUMMARY:');
714
- console.log('=' .repeat(50));
715
-
716
- if (this.options.dryRun) {
717
- console.log('šŸ” DRY RUN MODE - No files were actually deleted');
718
- } else {
719
- console.log(`šŸ“ Directories removed: ${this.results.directoriesRemoved}`);
720
- console.log(`šŸ“„ Files removed: ${this.results.filesRemoved}`);
721
- console.log(`šŸ’¾ Space freed: ${this.formatBytes(this.results.bytesFreed)}`);
722
- }
723
-
724
- if (this.results.skipped.length > 0) {
725
- console.log(`ā­ļø Items skipped: ${this.results.skipped.length}`);
726
- if (this.options.verbose) {
727
- this.results.skipped.forEach(item => {
728
- console.log(` ${item}`);
729
- });
730
- }
731
- }
732
-
733
- if (this.results.errors.length > 0) {
734
- console.log(`āŒ Errors: ${this.results.errors.length}`);
735
- this.results.errors.forEach(error => {
736
- console.log(` ${error}`);
737
- });
738
- }
739
-
740
- console.log('\nāœ… Cache cleaning completed!');
741
- }
742
- }
743
-
744
- module.exports = CacheCleaner;
1
+ /**
2
+ * Comprehensive Cache Cleaner for Stigmergy
3
+ *
4
+ * Intelligent cache cleaning with selective removal, performance optimization,
5
+ * and error recovery capabilities.
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const os = require('os');
11
+ const { spawnSync } = require('child_process');
12
+
13
+ class CacheCleaner {
14
+ constructor(options = {}) {
15
+ this.options = {
16
+ dryRun: options.dryRun || false,
17
+ force: options.force || false,
18
+ verbose: options.verbose || false,
19
+ preserveRecent: options.preserveRecent || 24 * 60 * 60 * 1000, // 24 hours
20
+ batchSize: options.batchSize || 50,
21
+ parallel: options.parallel || true,
22
+ ...options,
23
+ };
24
+
25
+ this.homeDir = os.homedir();
26
+ this.results = {
27
+ filesRemoved: 0,
28
+ directoriesRemoved: 0,
29
+ bytesFreed: 0,
30
+ errors: [],
31
+ skipped: [],
32
+ };
33
+ }
34
+
35
+ /**
36
+ * Clean all caches comprehensively
37
+ */
38
+ async cleanAllCaches(options = {}) {
39
+ const config = {
40
+ cleanStigmergy: true,
41
+ cleanNPX: true,
42
+ cleanNPM: true,
43
+ cleanCLI: true,
44
+ cleanTemp: true,
45
+ ...options,
46
+ };
47
+
48
+ console.log('🧹 Starting Comprehensive Cache Cleaning...\n');
49
+
50
+ if (this.options.dryRun) {
51
+ console.log('šŸ” DRY RUN MODE - No files will be deleted\n');
52
+ }
53
+
54
+ try {
55
+ // 1. Clean Stigmergy cache
56
+ if (config.cleanStigmergy) {
57
+ await this.cleanStigmergyCache();
58
+ }
59
+
60
+ // 2. Clean NPX cache
61
+ if (config.cleanNPX) {
62
+ await this.cleanNPXCache();
63
+ }
64
+
65
+ // 3. Clean NPM cache
66
+ if (config.cleanNPM) {
67
+ await this.cleanNPMCache();
68
+ }
69
+
70
+ // 4. Clean CLI configurations
71
+ if (config.cleanCLI) {
72
+ await this.cleanCLIConfigurations();
73
+ }
74
+
75
+ // 5. Clean temporary files
76
+ if (config.cleanTemp) {
77
+ await this.cleanTemporaryFiles();
78
+ }
79
+
80
+ // 6. Print summary
81
+ this.printSummary();
82
+
83
+ return this.results;
84
+ } catch (error) {
85
+ console.error('ļæ½?Cache cleaning failed:', error.message);
86
+ this.results.errors.push(error.message);
87
+ return this.results;
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Clean Stigmergy cache and temporary files
93
+ */
94
+ async cleanStigmergyCache() {
95
+ console.log('šŸ“ Cleaning Stigmergy cache...');
96
+
97
+ const stigmergyDir = path.join(this.homeDir, '.stigmergy');
98
+ const testDir = path.join(this.homeDir, '.stigmergy-test');
99
+
100
+ // Clean main cache directory
101
+ if (fs.existsSync(stigmergyDir)) {
102
+ await this.cleanStigmergyDirectory(stigmergyDir, 'main');
103
+ }
104
+
105
+ // Clean test directory
106
+ if (fs.existsSync(testDir)) {
107
+ await this.cleanStigmergyDirectory(testDir, 'test');
108
+ }
109
+
110
+ // Clean cache subdirectories specifically
111
+ const cachePaths = [
112
+ path.join(stigmergyDir, 'cache'),
113
+ path.join(stigmergyDir, 'logs'),
114
+ path.join(stigmergyDir, 'temp'),
115
+ path.join(stigmergyDir, '.tmp'),
116
+ ];
117
+
118
+ for (const cachePath of cachePaths) {
119
+ if (fs.existsSync(cachePath)) {
120
+ await this.cleanDirectory(cachePath);
121
+ }
122
+ }
123
+
124
+ console.log('ļæ½?Stigmergy cache cleaning completed');
125
+ }
126
+
127
+ /**
128
+ * Clean a specific Stigmergy directory
129
+ */
130
+ async cleanStigmergyDirectory(dirPath, type) {
131
+ console.log(` šŸ“‚ Cleaning ${type} directory...`);
132
+
133
+ const files = await this.scanDirectory(dirPath);
134
+ const recentFiles = this.filterRecentFiles(files);
135
+
136
+ if (recentFiles.length === 0) {
137
+ console.log(` ā„¹ļø No recent files to clean in ${type} directory`);
138
+ return;
139
+ }
140
+
141
+ console.log(` šŸ“‹ Found ${recentFiles.length} files to clean`);
142
+
143
+ if (this.options.dryRun) {
144
+ this.logFiles(recentFiles, ' šŸ” ');
145
+ return;
146
+ }
147
+
148
+ const removed = await this.batchRemoveFiles(recentFiles);
149
+ console.log(` ļæ½?Removed ${removed} files from ${type} directory`);
150
+ }
151
+
152
+ /**
153
+ * Clean NPX cache of Stigmergy entries
154
+ */
155
+ async cleanNPXCache() {
156
+ console.log('šŸ“¦ Cleaning NPX cache...');
157
+
158
+ const npxCacheDirs = await this.findNPXCacheDirectories();
159
+
160
+ if (npxCacheDirs.length === 0) {
161
+ console.log(' ā„¹ļø No Stigmergy entries in NPX cache');
162
+ return;
163
+ }
164
+
165
+ console.log(` šŸ“¦ Found ${npxCacheDirs.length} Stigmergy cache entries`);
166
+
167
+ if (this.options.dryRun) {
168
+ this.logFiles(npxCacheDirs, ' šŸ” ');
169
+ return;
170
+ }
171
+
172
+ let removed = 0;
173
+ const failed = [];
174
+
175
+ for (const cacheDir of npxCacheDirs) {
176
+ try {
177
+ const size = await this.getDirectorySize(cacheDir);
178
+ await this.removeDirectory(cacheDir);
179
+ this.results.bytesFreed += size;
180
+ removed++;
181
+ } catch (error) {
182
+ failed.push(cacheDir);
183
+ this.results.errors.push(`NPX cache ${cacheDir}: ${error.message}`);
184
+ }
185
+ }
186
+
187
+ console.log(` ļæ½?Removed ${removed} NPX cache entries`);
188
+ if (failed.length > 0) {
189
+ console.log(` āš ļø Failed to remove ${failed.length} entries`);
190
+ }
191
+ }
192
+
193
+ /**
194
+ * Clean NPM cache
195
+ */
196
+ async cleanNPMCache() {
197
+ console.log('šŸ“¦ Cleaning NPM cache...');
198
+
199
+ try {
200
+ // Use npm cache clean command
201
+ if (this.options.dryRun) {
202
+ console.log(' šŸ” Would run: npm cache clean --force');
203
+ return;
204
+ }
205
+
206
+ const result = spawnSync('npm', ['cache', 'clean', '--force'], {
207
+ encoding: 'utf8',
208
+ shell: true,
209
+ stdio: this.options.verbose ? 'inherit' : 'pipe',
210
+ });
211
+
212
+ if (result.status === 0) {
213
+ console.log(' ļæ½?NPM cache cleaned successfully');
214
+ } else {
215
+ console.log(' āš ļø NPM cache clean failed, trying manual cleanup');
216
+ await this.manualNPMCacheClean();
217
+ }
218
+ } catch (error) {
219
+ console.error(` ļæ½?Failed to clean NPM cache: ${error.message}`);
220
+ this.results.errors.push(`NPM cache: ${error.message}`);
221
+ }
222
+ }
223
+
224
+ /**
225
+ * Manual NPM cache cleaning fallback
226
+ */
227
+ async manualNPMCacheClean() {
228
+ const npmCacheDirs = [
229
+ path.join(this.homeDir, '.npm', '_cacache'),
230
+ path.join(this.homeDir, 'AppData', 'Local', 'npm-cache', '_cacache'),
231
+ ];
232
+
233
+ for (const cacheDir of npmCacheDirs) {
234
+ if (fs.existsSync(cacheDir)) {
235
+ console.log(` 🧹 Manual cleanup of ${cacheDir}`);
236
+ await this.cleanDirectory(cacheDir);
237
+ }
238
+ }
239
+ }
240
+
241
+ /**
242
+ * Clean CLI configurations
243
+ */
244
+ async cleanCLIConfigurations() {
245
+ console.log('āš™ļø Cleaning CLI configurations...');
246
+
247
+ const supportedCLIs = [
248
+ 'claude',
249
+ 'gemini',
250
+ 'qwen',
251
+ 'iflow',
252
+ 'qodercli',
253
+ 'codebuddy',
254
+ 'codex',
255
+ 'copilot',
256
+ 'qwencode',
257
+ ];
258
+
259
+ let totalCleaned = 0;
260
+
261
+ for (const cli of supportedCLIs) {
262
+ const cliConfig = path.join(this.homeDir, `.${cli}`);
263
+
264
+ if (!fs.existsSync(cliConfig)) {
265
+ continue;
266
+ }
267
+
268
+ const stigmergyFiles = await this.findStigmergyFiles(cliConfig);
269
+
270
+ if (stigmergyFiles.length === 0) {
271
+ continue;
272
+ }
273
+
274
+ console.log(` šŸ“‚ ${cli}: ${stigmergyFiles.length} Stigmergy files`);
275
+
276
+ if (this.options.dryRun) {
277
+ this.logFiles(stigmergyFiles, ' šŸ” ');
278
+ continue;
279
+ }
280
+
281
+ const removed = await this.batchRemoveFiles(stigmergyFiles);
282
+ totalCleaned += removed;
283
+
284
+ if (removed > 0) {
285
+ console.log(` ļæ½?Cleaned ${removed} files from ${cli}`);
286
+ }
287
+ }
288
+
289
+ if (totalCleaned > 0) {
290
+ console.log(` ļæ½?Cleaned ${totalCleaned} CLI configuration files`);
291
+ }
292
+ }
293
+
294
+ /**
295
+ * Clean temporary files
296
+ */
297
+ async cleanTemporaryFiles() {
298
+ console.log('šŸ—‘ļæ½? Cleaning temporary files...');
299
+
300
+ const tempDirs = [
301
+ os.tmpdir(),
302
+ path.join(this.homeDir, 'AppData', 'Local', 'Temp'),
303
+ path.join(this.homeDir, 'AppData', 'Local', 'npm-cache', '_tmp'),
304
+ ];
305
+
306
+ let totalRemoved = 0;
307
+
308
+ for (const tempDir of tempDirs) {
309
+ if (!fs.existsSync(tempDir)) {
310
+ continue;
311
+ }
312
+
313
+ const tempFiles = await this.findStigmergyTempFiles(tempDir);
314
+
315
+ if (tempFiles.length === 0) {
316
+ continue;
317
+ }
318
+
319
+ console.log(
320
+ ` šŸ“‚ ${path.basename(tempDir)}: ${tempFiles.length} temporary files`,
321
+ );
322
+
323
+ if (this.options.dryRun) {
324
+ this.logFiles(tempFiles.slice(0, 5), ' šŸ” ');
325
+ if (tempFiles.length > 5) {
326
+ console.log(` ... and ${tempFiles.length - 5} more`);
327
+ }
328
+ continue;
329
+ }
330
+
331
+ const removed = await this.batchRemoveFiles(tempFiles);
332
+ totalRemoved += removed;
333
+
334
+ if (removed > 0) {
335
+ console.log(
336
+ ` ļæ½?Removed ${removed} files from ${path.basename(tempDir)}`,
337
+ );
338
+ }
339
+ }
340
+
341
+ if (totalRemoved > 0) {
342
+ console.log(` ļæ½?Removed ${totalRemoved} temporary files`);
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Selective cleaning with patterns
348
+ */
349
+ async selectiveClean(targetDirectory, options = {}) {
350
+ const {
351
+ preservePatterns = [],
352
+ removePatterns = [],
353
+ preserveRecent = this.options.preserveRecent,
354
+ } = options;
355
+
356
+ console.log(`šŸŽÆ Selective cleaning: ${targetDirectory}`);
357
+
358
+ if (!fs.existsSync(targetDirectory)) {
359
+ console.log(' ā„¹ļø Directory not found');
360
+ return;
361
+ }
362
+
363
+ const allFiles = await this.scanDirectory(targetDirectory);
364
+ const filesToRemove = [];
365
+
366
+ for (const file of allFiles) {
367
+ // Check preserve patterns
368
+ const shouldPreserve =
369
+ preservePatterns.some((pattern) => this.matchPattern(file, pattern)) ||
370
+ this.isRecentFile(file, preserveRecent);
371
+
372
+ // Check remove patterns
373
+ const shouldRemove = removePatterns.some((pattern) =>
374
+ this.matchPattern(file, pattern),
375
+ );
376
+
377
+ if (shouldRemove && !shouldPreserve) {
378
+ filesToRemove.push(file);
379
+ }
380
+ }
381
+
382
+ console.log(` šŸ“‹ Found ${filesToRemove.length} files to remove`);
383
+
384
+ if (this.options.dryRun) {
385
+ this.logFiles(filesToRemove, ' šŸ” ');
386
+ return;
387
+ }
388
+
389
+ const removed = await this.batchRemoveFiles(filesToRemove);
390
+ console.log(` ļæ½?Selectively removed ${removed} files`);
391
+ }
392
+
393
+ /**
394
+ * Performance-optimized cleaning
395
+ */
396
+ async cleanWithPerformance(targetDirectory, options = {}) {
397
+ const {
398
+ batchSize = this.options.batchSize,
399
+ parallel = this.options.parallel,
400
+ maxConcurrency = 4,
401
+ } = options;
402
+
403
+ console.log(`ļæ½?Performance cleaning: ${targetDirectory}`);
404
+
405
+ const files = await this.scanDirectory(targetDirectory);
406
+ const recentFiles = this.filterRecentFiles(files);
407
+
408
+ console.log(
409
+ ` šŸ“Š Processing ${recentFiles.length} files in batches of ${batchSize}`,
410
+ );
411
+
412
+ if (this.options.dryRun) {
413
+ console.log(
414
+ ` šŸ” Would process in ${Math.ceil(recentFiles.length / batchSize)} batches`,
415
+ );
416
+ return;
417
+ }
418
+
419
+ let removed = 0;
420
+ const batches = this.createBatches(recentFiles, batchSize);
421
+
422
+ if (parallel && batches.length > 1) {
423
+ removed = await this.parallelRemoveBatches(batches, maxConcurrency);
424
+ } else {
425
+ for (const batch of batches) {
426
+ const batchRemoved = await this.batchRemoveFiles(batch);
427
+ removed += batchRemoved;
428
+ }
429
+ }
430
+
431
+ console.log(` ļæ½?Performance cleaned ${removed} files`);
432
+ return removed;
433
+ }
434
+
435
+ /**
436
+ * Helper methods
437
+ */
438
+ async scanDirectory(dirPath, files = []) {
439
+ try {
440
+ const items = fs.readdirSync(dirPath, { withFileTypes: true });
441
+
442
+ for (const item of items) {
443
+ const fullPath = path.join(dirPath, item.name);
444
+
445
+ if (item.isDirectory()) {
446
+ await this.scanDirectory(fullPath, files);
447
+ files.push(fullPath);
448
+ } else {
449
+ files.push(fullPath);
450
+ }
451
+ }
452
+ } catch (error) {
453
+ this.results.errors.push(`Scan error ${dirPath}: ${error.message}`);
454
+ }
455
+
456
+ return files;
457
+ }
458
+
459
+ async batchRemoveFiles(files) {
460
+ let removed = 0;
461
+
462
+ for (const file of files) {
463
+ try {
464
+ const stat = fs.statSync(file);
465
+
466
+ if (this.removeFile(file)) {
467
+ removed++;
468
+ this.results.bytesFreed += stat.size;
469
+ }
470
+ } catch (error) {
471
+ this.results.errors.push(`Remove error ${file}: ${error.message}`);
472
+ }
473
+ }
474
+
475
+ this.results.filesRemoved += removed;
476
+ return removed;
477
+ }
478
+
479
+ removeFile(filePath) {
480
+ try {
481
+ if (!fs.existsSync(filePath)) {
482
+ return false;
483
+ }
484
+
485
+ const stat = fs.statSync(filePath);
486
+
487
+ if (stat.isDirectory()) {
488
+ fs.rmSync(filePath, { recursive: true, force: true });
489
+ this.results.directoriesRemoved++;
490
+ } else {
491
+ fs.unlinkSync(filePath);
492
+ }
493
+
494
+ if (this.options.verbose) {
495
+ console.log(` Removed: ${path.basename(filePath)}`);
496
+ }
497
+
498
+ return true;
499
+ } catch (error) {
500
+ if (!this.options.force) {
501
+ throw error;
502
+ }
503
+ this.results.skipped.push(`${filePath}: ${error.message}`);
504
+ return false;
505
+ }
506
+ }
507
+
508
+ async removeDirectory(dirPath) {
509
+ try {
510
+ if (fs.existsSync(dirPath)) {
511
+ const size = await this.getDirectorySize(dirPath);
512
+ fs.rmSync(dirPath, { recursive: true, force: true });
513
+ this.results.bytesFreed += size;
514
+ this.results.directoriesRemoved++;
515
+ return true;
516
+ }
517
+ return false;
518
+ } catch (error) {
519
+ if (!this.options.force) {
520
+ throw error;
521
+ }
522
+ this.results.skipped.push(`Directory: ${dirPath} (${error.message})`);
523
+ return false;
524
+ }
525
+ }
526
+
527
+ async getDirectorySize(dirPath) {
528
+ let totalSize = 0;
529
+
530
+ try {
531
+ const files = await this.scanDirectory(dirPath);
532
+
533
+ for (const file of files) {
534
+ try {
535
+ const stat = fs.statSync(file);
536
+ totalSize += stat.size;
537
+ } catch (error) {
538
+ // Skip files we can't stat
539
+ }
540
+ }
541
+ } catch (error) {
542
+ // Return 0 for directories we can't scan
543
+ }
544
+
545
+ return totalSize;
546
+ }
547
+
548
+ filterRecentFiles(files) {
549
+ return files.filter(
550
+ (file) => !this.isRecentFile(file, this.options.preserveRecent),
551
+ );
552
+ }
553
+
554
+ isRecentFile(filePath, maxAge) {
555
+ try {
556
+ const stat = fs.statSync(filePath);
557
+ const age = Date.now() - stat.mtime.getTime();
558
+ return age < maxAge;
559
+ } catch (error) {
560
+ return false;
561
+ }
562
+ }
563
+
564
+ async findStigmergyFiles(dirPath) {
565
+ const stigmergyFiles = [];
566
+
567
+ try {
568
+ const files = fs.readdirSync(dirPath, { withFileTypes: true });
569
+
570
+ for (const file of files) {
571
+ const fullPath = path.join(dirPath, file.name);
572
+
573
+ if (file.isDirectory()) {
574
+ stigmergyFiles.push(...(await this.findStigmergyFiles(fullPath)));
575
+ } else if (this.isStigmergyFile(file.name)) {
576
+ stigmergyFiles.push(fullPath);
577
+ }
578
+ }
579
+ } catch (error) {
580
+ // Skip directories we can't read
581
+ }
582
+
583
+ return stigmergyFiles;
584
+ }
585
+
586
+ isStigmergyFile(fileName) {
587
+ const stigmergyPatterns = [
588
+ 'stigmergy',
589
+ 'cross-cli',
590
+ 'hook',
591
+ 'integration',
592
+ 'cache',
593
+ '.tmp',
594
+ 'temp',
595
+ ];
596
+
597
+ const lowerFileName = fileName.toLowerCase();
598
+ return stigmergyPatterns.some((pattern) =>
599
+ lowerFileName.includes(pattern.toLowerCase()),
600
+ );
601
+ }
602
+
603
+ async findNPXCacheDirectories() {
604
+ const cacheDirs = [];
605
+ const possibleNPXBases = [
606
+ path.join(this.homeDir, 'AppData', 'Local', 'npm-cache', '_npx'),
607
+ path.join(this.homeDir, '.npm', '_npx'),
608
+ path.join(os.tmpdir(), 'npm-cache', '_npx'),
609
+ ];
610
+
611
+ for (const npxCacheBase of possibleNPXBases) {
612
+ if (!fs.existsSync(npxCacheBase)) {
613
+ continue;
614
+ }
615
+
616
+ try {
617
+ const entries = fs.readdirSync(npxCacheBase);
618
+
619
+ for (const entry of entries) {
620
+ const entryPath = path.join(npxCacheBase, entry);
621
+ const stigmergyPath = path.join(
622
+ entryPath,
623
+ 'node_modules',
624
+ 'stigmergy',
625
+ );
626
+
627
+ if (fs.existsSync(stigmergyPath)) {
628
+ cacheDirs.push(entryPath);
629
+ }
630
+ }
631
+ } catch (error) {
632
+ this.results.errors.push(`NPX scan error: ${error.message}`);
633
+ }
634
+ }
635
+
636
+ return cacheDirs;
637
+ }
638
+
639
+ async findStigmergyTempFiles(tempDir) {
640
+ const tempFiles = [];
641
+
642
+ try {
643
+ const files = fs.readdirSync(tempDir, { withFileTypes: true });
644
+
645
+ for (const file of files) {
646
+ if (
647
+ this.isStigmergyFile(file.name) ||
648
+ file.name.startsWith('stigmergy-') ||
649
+ file.name.includes('stigmergy')
650
+ ) {
651
+ const fullPath = path.join(tempDir, file.name);
652
+ tempFiles.push(fullPath);
653
+ }
654
+ }
655
+ } catch (error) {
656
+ // Skip temp directories we can't read
657
+ }
658
+
659
+ return tempFiles;
660
+ }
661
+
662
+ async cleanDirectory(dirPath) {
663
+ try {
664
+ if (!fs.existsSync(dirPath)) {
665
+ return;
666
+ }
667
+
668
+ const files = await this.scanDirectory(dirPath);
669
+ const removed = await this.batchRemoveFiles(files);
670
+
671
+ console.log(
672
+ ` 🧹 Cleaned ${removed} files from ${path.basename(dirPath)}`,
673
+ );
674
+ } catch (error) {
675
+ console.error(` ļæ½?Failed to clean ${dirPath}: ${error.message}`);
676
+ this.results.errors.push(`Clean error ${dirPath}: ${error.message}`);
677
+ }
678
+ }
679
+
680
+ matchPattern(filePath, pattern) {
681
+ const fileName = path.basename(filePath);
682
+
683
+ // Simple glob pattern matching
684
+ const regexPattern = pattern.replace(/\*/g, '.*').replace(/\?/g, '.');
685
+
686
+ const regex = new RegExp(regexPattern, 'i');
687
+ return regex.test(fileName);
688
+ }
689
+
690
+ createBatches(items, batchSize) {
691
+ const batches = [];
692
+ for (let i = 0; i < items.length; i += batchSize) {
693
+ batches.push(items.slice(i, i + batchSize));
694
+ }
695
+ return batches;
696
+ }
697
+
698
+ async parallelRemoveBatches(batches, maxConcurrency) {
699
+ let totalRemoved = 0;
700
+ const executing = [];
701
+
702
+ for (const batch of batches) {
703
+ const promise = this.batchRemoveFiles(batch);
704
+ executing.push(promise);
705
+
706
+ if (executing.length >= maxConcurrency) {
707
+ await Promise.race(executing);
708
+ executing.splice(0, 1);
709
+ }
710
+ }
711
+
712
+ await Promise.all(executing);
713
+ return totalRemoved;
714
+ }
715
+
716
+ formatBytes(bytes) {
717
+ const sizes = ['Bytes', 'KB', 'MB', 'GB'];
718
+ if (bytes === 0) return '0 Bytes';
719
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
720
+ return Math.round((bytes / Math.pow(1024, i)) * 100) / 100 + ' ' + sizes[i];
721
+ }
722
+
723
+ logFiles(files, prefix = '') {
724
+ files.slice(0, 10).forEach((file) => {
725
+ console.log(`${prefix}${path.basename(file)}`);
726
+ });
727
+
728
+ if (files.length > 10) {
729
+ console.log(`${prefix}... and ${files.length - 10} more`);
730
+ }
731
+ }
732
+
733
+ printSummary() {
734
+ console.log('\nšŸ“Š CACHE CLEANING SUMMARY:');
735
+ console.log('='.repeat(50));
736
+
737
+ if (this.options.dryRun) {
738
+ console.log('šŸ” DRY RUN MODE - No files were actually deleted');
739
+ } else {
740
+ console.log(`šŸ“ Directories removed: ${this.results.directoriesRemoved}`);
741
+ console.log(`šŸ“„ Files removed: ${this.results.filesRemoved}`);
742
+ console.log(
743
+ `šŸ’¾ Space freed: ${this.formatBytes(this.results.bytesFreed)}`,
744
+ );
745
+ }
746
+
747
+ if (this.results.skipped.length > 0) {
748
+ console.log(`ā­ļø Items skipped: ${this.results.skipped.length}`);
749
+ if (this.options.verbose) {
750
+ this.results.skipped.forEach((item) => {
751
+ console.log(` ${item}`);
752
+ });
753
+ }
754
+ }
755
+
756
+ if (this.results.errors.length > 0) {
757
+ console.log(`ļæ½?Errors: ${this.results.errors.length}`);
758
+ this.results.errors.forEach((error) => {
759
+ console.log(` ${error}`);
760
+ });
761
+ }
762
+
763
+ console.log('\nļæ½?Cache cleaning completed!');
764
+ }
765
+ }
766
+
767
+ module.exports = CacheCleaner;