stigmergy 1.1.6 โ†’ 1.2.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.
@@ -0,0 +1,271 @@
1
+ /**
2
+ * Implementation Tests for Enhanced Stigmergy Uninstaller
3
+ *
4
+ * Tests that verify the actual implementation works correctly
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const os = require('os');
10
+ const EnhancedUninstaller = require('../src/core/enhanced_uninstaller');
11
+
12
+ class EnhancedUninstallerImplementationTests {
13
+ constructor() {
14
+ this.testDir = path.join(os.tmpdir(), 'stigmergy-uninstaller-impl-test');
15
+ this.configDir = path.join(this.testDir, '.stigmergy');
16
+ this.testConfigDir = path.join(this.testDir, '.stigmergy-test');
17
+ this.testResults = [];
18
+ }
19
+
20
+ async runAllTests() {
21
+ console.log('๐Ÿงช Running Enhanced Uninstaller Implementation Tests...\n');
22
+
23
+ await this.setupTestEnvironment();
24
+
25
+ try {
26
+ await this.testDryRunMode();
27
+ await this.testCompleteUninstall();
28
+ await this.testErrorHandling();
29
+ await this.testDirectoryScanning();
30
+ await this.testFilePatternMatching();
31
+
32
+ this.printResults();
33
+ } finally {
34
+ await this.cleanupTestEnvironment();
35
+ }
36
+ }
37
+
38
+ async setupTestEnvironment() {
39
+ console.log('๐Ÿ“‹ Setting up implementation test environment...');
40
+
41
+ // Create test directories
42
+ fs.mkdirSync(this.configDir, { recursive: true });
43
+ fs.mkdirSync(this.testConfigDir, { recursive: true });
44
+
45
+ // Create mock configuration files
46
+ fs.writeFileSync(path.join(this.configDir, 'config.json'), JSON.stringify({
47
+ version: '1.1.8',
48
+ clis: ['claude', 'gemini', 'qwen']
49
+ }, null, 2));
50
+
51
+ fs.writeFileSync(path.join(this.configDir, 'auth.json'), JSON.stringify({
52
+ tokens: { mock: 'token' }
53
+ }, null, 2));
54
+
55
+ fs.writeFileSync(path.join(this.configDir, 'error.log'), 'Mock error log\n');
56
+
57
+ // Create mock hooks directory
58
+ const hooksDir = path.join(this.configDir, 'hooks');
59
+ fs.mkdirSync(hooksDir, { recursive: true });
60
+
61
+ ['claude', 'gemini', 'qwen'].forEach(cli => {
62
+ const cliHookDir = path.join(hooksDir, cli);
63
+ fs.mkdirSync(cliHookDir, { recursive: true });
64
+ fs.writeFileSync(path.join(cliHookDir, `${cli}_nodejs_hook.js`), '// Mock hook');
65
+ fs.writeFileSync(path.join(cliHookDir, 'config.json'), '{}');
66
+ });
67
+
68
+ console.log('โœ… Implementation test environment setup complete\n');
69
+ }
70
+
71
+ async testDryRunMode() {
72
+ console.log('๐Ÿ” TEST 1: Dry Run Mode');
73
+
74
+ const uninstaller = new EnhancedUninstaller({
75
+ dryRun: true,
76
+ verbose: true
77
+ });
78
+
79
+ // Verify files exist before dry run
80
+ const configExists = fs.existsSync(this.configDir);
81
+ this.assert(configExists, 'Test configuration should exist before uninstall');
82
+
83
+ // Run dry uninstall
84
+ const results = await uninstaller.completeUninstall();
85
+
86
+ // Verify files still exist after dry run
87
+ const configStillExists = fs.existsSync(this.configDir);
88
+ this.assert(configStillExists, 'Configuration should still exist after dry run');
89
+
90
+ // Verify dry run detected files
91
+ this.assert(results.filesRemoved === 0, 'Dry run should not remove files');
92
+ this.assert(results.directoriesRemoved === 0, 'Dry run should not remove directories');
93
+
94
+ this.recordResult('Dry Run Mode', 'โœ…');
95
+ }
96
+
97
+ async testCompleteUninstall() {
98
+ console.log('๐Ÿ—‘๏ธ TEST 2: Complete Uninstall');
99
+
100
+ // Recreate test environment
101
+ await this.setupTestEnvironment();
102
+
103
+ const uninstaller = new EnhancedUninstaller({
104
+ dryRun: false,
105
+ verbose: false
106
+ });
107
+
108
+ // Verify files exist before uninstall
109
+ const beforeFiles = [
110
+ path.join(this.configDir, 'config.json'),
111
+ path.join(this.configDir, 'auth.json'),
112
+ path.join(this.configDir, 'error.log'),
113
+ path.join(this.configDir, 'hooks', 'claude', 'claude_nodejs_hook.js')
114
+ ];
115
+
116
+ const beforeUninstall = beforeFiles.every(file => fs.existsSync(file));
117
+ this.assert(beforeUninstall, 'Test files should exist before uninstall');
118
+
119
+ // Run complete uninstall
120
+ const results = await uninstaller.completeUninstall();
121
+
122
+ // Verify cleanup after uninstall
123
+ const afterUninstall = !beforeFiles.some(file => fs.existsSync(file));
124
+ this.assert(afterUninstall, 'All Stigmergy files should be removed after uninstall');
125
+
126
+ // Verify results
127
+ this.assert(results.filesRemoved > 0, 'Should report files removed');
128
+ this.assert(results.directoriesRemoved >= 0, 'Should report directories removed');
129
+
130
+ this.recordResult('Complete Uninstall', 'โœ…');
131
+ }
132
+
133
+ async testErrorHandling() {
134
+ console.log('โš ๏ธ TEST 3: Error Handling');
135
+
136
+ const uninstaller = new EnhancedUninstaller({
137
+ force: true,
138
+ dryRun: true
139
+ });
140
+
141
+ // Test with non-existent directory
142
+ const nonExistentPath = path.join(this.testDir, 'does-not-exist');
143
+
144
+ // Should handle missing directory gracefully
145
+ const plan = await uninstaller.createUninstallPlan();
146
+
147
+ // Should not crash on missing directories
148
+ this.assert(plan !== undefined, 'Should handle missing directories gracefully');
149
+
150
+ // Test uninstall with non-existent paths
151
+ const results = await uninstaller.completeUninstall();
152
+
153
+ // Should not report errors for non-existent files in dry run
154
+ this.assert(Array.isArray(results.errors), 'Should return error array');
155
+
156
+ this.recordResult('Error Handling', 'โœ…');
157
+ }
158
+
159
+ async testDirectoryScanning() {
160
+ console.log('๐Ÿ“ TEST 4: Directory Scanning');
161
+
162
+ // Create a more complex directory structure
163
+ const complexDir = path.join(this.testDir, '.stigmergy-complex');
164
+ fs.mkdirSync(complexDir, { recursive: true });
165
+
166
+ fs.mkdirSync(path.join(complexDir, 'subdir1'), { recursive: true });
167
+ fs.mkdirSync(path.join(complexDir, 'subdir2'), { recursive: true });
168
+
169
+ fs.writeFileSync(path.join(complexDir, 'file1.txt'), 'content');
170
+ fs.writeFileSync(path.join(complexDir, 'subdir1', 'file2.txt'), 'content');
171
+ fs.writeFileSync(path.join(complexDir, 'subdir2', 'file3.txt'), 'content');
172
+
173
+ const uninstaller = new EnhancedUninstaller({ dryRun: true });
174
+
175
+ // Test plan creation
176
+ const plan = await uninstaller.createUninstallPlan();
177
+
178
+ // Should scan directories correctly
179
+ this.assert(plan.files.length > 0, 'Should find files in directory scan');
180
+ this.assert(plan.directories.length > 0, 'Should find directories in scan');
181
+
182
+ // Clean up
183
+ fs.rmSync(complexDir, { recursive: true, force: true });
184
+
185
+ this.recordResult('Directory Scanning', 'โœ…');
186
+ }
187
+
188
+ async testFilePatternMatching() {
189
+ console.log('๐ŸŽฏ TEST 5: File Pattern Matching');
190
+
191
+ const uninstaller = new EnhancedUninstaller({ dryRun: true });
192
+
193
+ // Test internal pattern matching methods
194
+ const testFiles = [
195
+ 'stigmergy-config.json',
196
+ 'cross-cli-hook.js',
197
+ 'integration-settings.json',
198
+ 'cache-data.tmp',
199
+ 'normal-file.txt'
200
+ ];
201
+
202
+ // Test if methods exist and work
203
+ let stigmergyFilesFound = 0;
204
+
205
+ for (const fileName of testFiles) {
206
+ if (uninstaller.isStigmergyFile && uninstaller.isStigmergyFile(fileName)) {
207
+ stigmergyFilesFound++;
208
+ }
209
+ }
210
+
211
+ this.assert(stigmergyFilesFound >= 4, 'Should identify Stigmergy files correctly');
212
+
213
+ this.recordResult('File Pattern Matching', 'โœ…');
214
+ }
215
+
216
+ assert(condition, message) {
217
+ if (condition) {
218
+ console.log(` โœ… ${message}`);
219
+ } else {
220
+ console.log(` โŒ ${message}`);
221
+ throw new Error(`Assertion failed: ${message}`);
222
+ }
223
+ }
224
+
225
+ recordResult(testName, status) {
226
+ this.testResults.push({ name: testName, status });
227
+ }
228
+
229
+ printResults() {
230
+ console.log('\n๐Ÿ“Š IMPLEMENTATION TEST RESULTS:');
231
+ console.log('=' .repeat(50));
232
+
233
+ this.testResults.forEach(result => {
234
+ console.log(`${result.status} ${result.name}`);
235
+ });
236
+
237
+ const passed = this.testResults.filter(r => r.status === 'โœ…').length;
238
+ const total = this.testResults.length;
239
+
240
+ console.log('\n๐Ÿ“ˆ Summary:');
241
+ console.log(`Total tests: ${total}`);
242
+ console.log(`Passed: ${passed}`);
243
+ console.log(`Failed: ${total - passed}`);
244
+
245
+ if (passed === total) {
246
+ console.log('\n๐ŸŽ‰ All implementation tests passed!');
247
+ console.log('โœ… Enhanced uninstaller is working correctly!');
248
+ } else {
249
+ console.log('\nโŒ Some tests failed. Review the implementation.');
250
+ }
251
+ }
252
+
253
+ async cleanupTestEnvironment() {
254
+ console.log('\n๐Ÿงน Cleaning up implementation test environment...');
255
+
256
+ try {
257
+ fs.rmSync(this.testDir, { recursive: true, force: true });
258
+ console.log('โœ… Implementation test environment cleaned up');
259
+ } catch (error) {
260
+ console.warn('โš ๏ธ Warning: Could not clean up test environment:', error.message);
261
+ }
262
+ }
263
+ }
264
+
265
+ // Run tests if this file is executed directly
266
+ if (require.main === module) {
267
+ const tests = new EnhancedUninstallerImplementationTests();
268
+ tests.runAllTests().catch(console.error);
269
+ }
270
+
271
+ module.exports = EnhancedUninstallerImplementationTests;
@@ -0,0 +1,284 @@
1
+ /**
2
+ * TDD Tests for Enhanced Stigmergy Uninstaller
3
+ *
4
+ * Test-first approach to ensure complete cleanup of Stigmergy installation
5
+ */
6
+
7
+ const fs = require('fs');
8
+ const path = require('path');
9
+ const os = require('os');
10
+ const { spawnSync } = require('child_process');
11
+
12
+ class EnhancedUninstallerTests {
13
+ constructor() {
14
+ this.testDir = path.join(os.tmpdir(), 'stigmergy-uninstaller-test');
15
+ this.configDir = path.join(this.testDir, '.stigmergy');
16
+ this.testConfigDir = path.join(this.testDir, '.stigmergy-test');
17
+ this.npmCacheDir = path.join(this.testDir, 'npm-cache');
18
+ this.testResults = [];
19
+ }
20
+
21
+ async runAllTests() {
22
+ console.log('๐Ÿงช Running Enhanced Uninstaller TDD Tests...\n');
23
+
24
+ await this.setupTestEnvironment();
25
+
26
+ try {
27
+ await this.testCompleteUninstall();
28
+ await this.testCacheCleaning();
29
+ await this.testNPXCacheCleaning();
30
+ await this.testCLIConfigCleanup();
31
+ await this.testErrorHandling();
32
+ await this.testDryRunMode();
33
+
34
+ this.printResults();
35
+ } finally {
36
+ await this.cleanupTestEnvironment();
37
+ }
38
+ }
39
+
40
+ async setupTestEnvironment() {
41
+ console.log('๐Ÿ“‹ Setting up test environment...');
42
+
43
+ // Create test directories
44
+ fs.mkdirSync(this.configDir, { recursive: true });
45
+ fs.mkdirSync(this.testConfigDir, { recursive: true });
46
+ fs.mkdirSync(this.npmCacheDir, { recursive: true });
47
+
48
+ // Create mock configuration files
49
+ fs.writeFileSync(path.join(this.configDir, 'config.json'), JSON.stringify({
50
+ version: '1.1.8',
51
+ clis: ['claude', 'gemini', 'qwen']
52
+ }, null, 2));
53
+
54
+ fs.writeFileSync(path.join(this.configDir, 'auth.json'), JSON.stringify({
55
+ tokens: { mock: 'token' }
56
+ }, null, 2));
57
+
58
+ fs.writeFileSync(path.join(this.configDir, 'error.log'), 'Mock error log\n');
59
+
60
+ // Create mock hooks directory
61
+ const hooksDir = path.join(this.configDir, 'hooks');
62
+ fs.mkdirSync(hooksDir, { recursive: true });
63
+
64
+ ['claude', 'gemini', 'qwen'].forEach(cli => {
65
+ const cliHookDir = path.join(hooksDir, cli);
66
+ fs.mkdirSync(cliHookDir, { recursive: true });
67
+ fs.writeFileSync(path.join(cliHookDir, `${cli}_nodejs_hook.js`), '// Mock hook');
68
+ fs.writeFileSync(path.join(cliHookDir, 'config.json'), '{}');
69
+ });
70
+
71
+ // Create mock CLI config directories
72
+ ['claude', 'gemini', 'qwen'].forEach(cli => {
73
+ const cliConfig = path.join(this.testDir, `.${cli}`);
74
+ fs.mkdirSync(cliConfig, { recursive: true });
75
+ fs.writeFileSync(path.join(cliConfig, 'stigmergy-config.json'), '{}');
76
+ });
77
+
78
+ // Create mock npm cache files
79
+ const npxCacheDir = path.join(this.npmCacheDir, '_npx');
80
+ fs.mkdirSync(npxCacheDir, { recursive: true });
81
+ fs.mkdirSync(path.join(npxCacheDir, 'mock-cache-id'), { recursive: true });
82
+
83
+ console.log('โœ… Test environment setup complete\n');
84
+ }
85
+
86
+ async testCompleteUninstall() {
87
+ console.log('๐Ÿ” TEST 1: Complete Uninstall Functionality');
88
+
89
+ const expectedFiles = [
90
+ path.join(this.configDir, 'config.json'),
91
+ path.join(this.configDir, 'auth.json'),
92
+ path.join(this.configDir, 'error.log'),
93
+ path.join(this.configDir, 'hooks', 'claude', 'claude_nodejs_hook.js')
94
+ ];
95
+
96
+ // Verify test files exist before uninstall
97
+ const beforeUninstall = expectedFiles.every(file => fs.existsSync(file));
98
+ this.assert(beforeUninstall, 'Test files should exist before uninstall');
99
+
100
+ // TODO: After implementing EnhancedUninstaller, call it here
101
+ // const uninstaller = new EnhancedUninstaller();
102
+ // await uninstaller.completeUninstall({ dryRun: false, testMode: true });
103
+
104
+ // TODO: Verify cleanup after uninstall
105
+ // const afterUninstall = !expectedFiles.some(file => fs.existsSync(file));
106
+ // this.assert(afterUninstall, 'All Stigmergy files should be removed after uninstall');
107
+
108
+ this.recordResult('Complete Uninstall', 'โณ Pending implementation');
109
+ }
110
+
111
+ async testCacheCleaning() {
112
+ console.log('๐Ÿงน TEST 2: Cache Cleaning Functionality');
113
+
114
+ // Mock cache files
115
+ const cacheFiles = [
116
+ path.join(this.configDir, 'cache', 'test-cache.json'),
117
+ path.join(this.configDir, 'memory.json')
118
+ ];
119
+
120
+ cacheFiles.forEach(file => {
121
+ const dir = path.dirname(file);
122
+ fs.mkdirSync(dir, { recursive: true });
123
+ fs.writeFileSync(file, 'mock cache data');
124
+ });
125
+
126
+ // Verify cache files exist
127
+ const beforeCleanup = cacheFiles.every(file => fs.existsSync(file));
128
+ this.assert(beforeCleanup, 'Cache files should exist before cleanup');
129
+
130
+ // TODO: After implementing cache cleaner
131
+ // const cleaner = new CacheCleaner();
132
+ // await cleaner.cleanAllCaches({ includeStigmergy: true, includeNPM: false });
133
+
134
+ // TODO: Verify cache files are removed
135
+ // const afterCleanup = !cacheFiles.some(file => fs.existsSync(file));
136
+ // this.assert(afterCleanup, 'Cache files should be removed after cleanup');
137
+
138
+ this.recordResult('Cache Cleaning', 'โณ Pending implementation');
139
+ }
140
+
141
+ async testNPXCacheCleaning() {
142
+ console.log('๐Ÿ“ฆ TEST 3: NPX Cache Cleaning');
143
+
144
+ // Create mock npx cache
145
+ const npxCachePath = path.join(this.npmCacheDir, '_npx', 'test-cache-id', 'node_modules', 'stigmergy');
146
+ fs.mkdirSync(npxCachePath, { recursive: true });
147
+ fs.writeFileSync(path.join(npxCachePath, 'package.json'), '{}');
148
+
149
+ // Verify npx cache exists
150
+ const beforeCleanup = fs.existsSync(npxCachePath);
151
+ this.assert(beforeCleanup, 'NPX cache should exist before cleanup');
152
+
153
+ // TODO: After implementing NPX cache cleaner
154
+ // const cleaner = new NPXCacheCleaner();
155
+ // const removed = await cleaner.cleanStigmergyFromNPXCache();
156
+ // this.assert(removed > 0, 'Should remove Stigmergy from NPX cache');
157
+
158
+ this.recordResult('NPX Cache Cleaning', 'โณ Pending implementation');
159
+ }
160
+
161
+ async testCLIConfigCleanup() {
162
+ console.log('โš™๏ธ TEST 4: CLI Configuration Cleanup');
163
+
164
+ const cliConfigs = ['claude', 'gemini', 'qwen'].map(cli => ({
165
+ cli,
166
+ configPath: path.join(this.testDir, `.${cli}`, 'stigmergy-config.json'),
167
+ hooksPath: path.join(this.testDir, `.${cli}`, 'hooks', 'stigmergy')
168
+ }));
169
+
170
+ // Create mock CLI hooks
171
+ cliConfigs.forEach(config => {
172
+ fs.mkdirSync(path.dirname(config.hooksPath), { recursive: true });
173
+ fs.writeFileSync(config.hooksPath, '// mock hook');
174
+ });
175
+
176
+ // Verify CLI configs exist
177
+ const beforeCleanup = cliConfigs.every(config =>
178
+ fs.existsSync(config.configPath) || fs.existsSync(config.hooksPath)
179
+ );
180
+ this.assert(beforeCleanup, 'CLI configurations should exist before cleanup');
181
+
182
+ // TODO: After implementing CLI config cleaner
183
+ // const cleaner = new CLIConfigCleaner();
184
+ // const results = await cleaner.cleanCLIConfigurations(['claude', 'gemini', 'qwen']);
185
+ // this.assert(results.cleaned > 0, 'Should clean CLI configurations');
186
+
187
+ this.recordResult('CLI Config Cleanup', 'โณ Pending implementation');
188
+ }
189
+
190
+ async testErrorHandling() {
191
+ console.log('โš ๏ธ TEST 5: Error Handling');
192
+
193
+ // Test with non-existent directory
194
+ const nonExistentPath = path.join(this.testDir, 'does-not-exist');
195
+
196
+ // TODO: Test error handling for various scenarios
197
+ // const uninstaller = new EnhancedUninstaller();
198
+
199
+ // Should handle missing directory gracefully
200
+ // const result1 = await uninstaller.removeDirectory(nonExistentPath);
201
+ // this.assert(!result1.error, 'Should handle missing directory gracefully');
202
+
203
+ // Should handle permission denied gracefully
204
+ // const result2 = await uninstaller.removeDirectory('/root/protected');
205
+ // this.assert(result2.error, 'Should detect permission errors');
206
+
207
+ this.recordResult('Error Handling', 'โณ Pending implementation');
208
+ }
209
+
210
+ async testDryRunMode() {
211
+ console.log('๐Ÿ” TEST 6: Dry Run Mode');
212
+
213
+ // Create files that should be preserved in dry run
214
+ const testFile = path.join(this.configDir, 'should-preserve.txt');
215
+ fs.writeFileSync(testFile, 'preserve me');
216
+
217
+ // TODO: Test dry run functionality
218
+ // const uninstaller = new EnhancedUninstaller();
219
+ // const plan = await uninstaller.createUninstallPlan({ dryRun: true });
220
+
221
+ // Should return what would be deleted
222
+ // this.assert(plan.files.length > 0, 'Should identify files to delete');
223
+
224
+ // Should not actually delete files in dry run
225
+ // const stillExists = fs.existsSync(testFile);
226
+ // this.assert(stillExists, 'Files should not be deleted in dry run mode');
227
+
228
+ this.recordResult('Dry Run Mode', 'โณ Pending implementation');
229
+ }
230
+
231
+ async cleanupTestEnvironment() {
232
+ console.log('\n๐Ÿงน Cleaning up test environment...');
233
+
234
+ try {
235
+ fs.rmSync(this.testDir, { recursive: true, force: true });
236
+ console.log('โœ… Test environment cleaned up');
237
+ } catch (error) {
238
+ console.warn('โš ๏ธ Warning: Could not clean up test environment:', error.message);
239
+ }
240
+ }
241
+
242
+ assert(condition, message) {
243
+ if (condition) {
244
+ console.log(` โœ… ${message}`);
245
+ } else {
246
+ console.log(` โŒ ${message}`);
247
+ throw new Error(`Assertion failed: ${message}`);
248
+ }
249
+ }
250
+
251
+ recordResult(testName, status) {
252
+ this.testResults.push({ name: testName, status });
253
+ }
254
+
255
+ printResults() {
256
+ console.log('\n๐Ÿ“Š TEST RESULTS:');
257
+ console.log('=' .repeat(50));
258
+
259
+ this.testResults.forEach(result => {
260
+ console.log(`${result.status} ${result.name}`);
261
+ });
262
+
263
+ const pending = this.testResults.filter(r => r.status.includes('Pending')).length;
264
+ const total = this.testResults.length;
265
+
266
+ console.log('\n๐Ÿ“ˆ Summary:');
267
+ console.log(`Total tests: ${total}`);
268
+ console.log(`Pending implementation: ${pending}`);
269
+ console.log(`Ready for implementation: ${pending}/${total}`);
270
+
271
+ if (pending === total) {
272
+ console.log('\n๐Ÿš€ All test cases defined successfully!');
273
+ console.log('๐Ÿ’ก Ready to implement the enhanced uninstaller functionality.');
274
+ }
275
+ }
276
+ }
277
+
278
+ // Run tests if this file is executed directly
279
+ if (require.main === module) {
280
+ const tests = new EnhancedUninstallerTests();
281
+ tests.runAllTests().catch(console.error);
282
+ }
283
+
284
+ module.exports = EnhancedUninstallerTests;
@@ -29,7 +29,7 @@ const CLI_TOOLS = {
29
29
  qwen: {
30
30
  name: 'Qwen CLI',
31
31
  version: 'qwen --version',
32
- install: 'npm install -g @alibaba/qwen-cli',
32
+ install: 'npm install -g @qwen-code/qwen-code',
33
33
  hooksDir: path.join(os.homedir(), '.qwen', 'hooks'),
34
34
  config: path.join(os.homedir(), '.qwen', 'config.json')
35
35
  },