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.
- package/LICENSE +18 -18
- package/README.md +28 -223
- package/STIGMERGY.md +61 -61
- package/docs/PROJECT_CONSTITUTION.md +433 -433
- package/docs/PROJECT_STRUCTURE_CURRENT.md +80 -80
- package/examples/calculator-example.js +72 -72
- package/examples/cline_usage_examples.md +364 -364
- package/examples/encryption-example.js +67 -67
- package/examples/json-parser-example.js +120 -120
- package/examples/json-validation-example.js +64 -64
- package/examples/rest-client-example.js +52 -52
- package/examples/rest_client_example.js +54 -54
- package/package.json +36 -15
- package/scripts/build.js +74 -74
- package/scripts/post-deployment-config.js +296 -296
- package/scripts/preinstall-check.js +173 -173
- package/scripts/publish.js +58 -268
- package/scripts/run-layered-tests.js +247 -0
- package/scripts/safe-install.js +139 -139
- package/scripts/simple-publish.js +57 -59
- package/src/adapters/claude/install_claude_integration.js +292 -0
- package/src/adapters/codebuddy/install_codebuddy_integration.js +349 -0
- package/src/adapters/codex/install_codex_integration.js +395 -0
- package/src/adapters/copilot/install_copilot_integration.js +716 -0
- package/src/adapters/gemini/install_gemini_integration.js +304 -0
- package/src/adapters/iflow/install_iflow_integration.js +304 -0
- package/src/adapters/qoder/install_qoder_integration.js +1090 -0
- package/src/adapters/qwen/install_qwen_integration.js +285 -0
- package/src/auth.js +173 -173
- package/src/auth_command.js +208 -208
- package/src/calculator.js +313 -313
- package/src/cli/router.js +417 -38
- package/src/core/cache_cleaner.js +767 -744
- package/src/core/cli_help_analyzer.js +680 -674
- package/src/core/cli_parameter_handler.js +132 -127
- package/src/core/cli_tools.js +89 -89
- package/src/core/coordination/index.js +16 -16
- package/src/core/coordination/nodejs/AdapterManager.js +102 -89
- package/src/core/coordination/nodejs/CLCommunication.js +132 -124
- package/src/core/coordination/nodejs/CLIIntegrationManager.js +272 -236
- package/src/core/coordination/nodejs/HealthChecker.js +76 -77
- package/src/core/coordination/nodejs/HookDeploymentManager.js +263 -190
- package/src/core/coordination/nodejs/StatisticsCollector.js +71 -71
- package/src/core/coordination/nodejs/index.js +90 -72
- package/src/core/coordination/nodejs/utils/Logger.js +29 -29
- package/src/core/enhanced_installer.js +479 -456
- package/src/core/enhanced_uninstaller.js +638 -618
- package/src/core/error_handler.js +406 -406
- package/src/core/installer.js +815 -294
- package/src/core/memory_manager.js +83 -83
- package/src/core/rest_client.js +160 -160
- package/src/core/smart_router.js +249 -146
- package/src/core/upgrade_manager.js +76 -59
- package/src/data_encryption.js +143 -143
- package/src/data_structures.js +440 -440
- package/src/deploy.js +55 -55
- package/src/index.js +30 -30
- package/src/test/cli-availability-checker.js +194 -0
- package/src/test/test-environment.js +289 -0
- package/src/utils/helpers.js +35 -35
- package/src/utils.js +921 -915
- package/src/weatherProcessor.js +228 -228
- package/test/cache-cleaner-implemented.test.js +0 -328
- package/test/cache-cleaner.test.js +0 -390
- package/test/calculator.test.js +0 -215
- package/test/collision-test.js +0 -26
- package/test/comprehensive-enhanced-features.test.js +0 -252
- package/test/comprehensive-execution-test.js +0 -428
- package/test/conflict-prevention-test.js +0 -95
- package/test/cross-cli-detection-test.js +0 -33
- package/test/csv-processing-test.js +0 -36
- package/test/deploy-hooks-test.js +0 -250
- package/test/e2e/claude-cli-test.js +0 -128
- package/test/e2e/collaboration-test.js +0 -75
- package/test/e2e/comprehensive-test.js +0 -431
- package/test/e2e/error-handling-test.js +0 -90
- package/test/e2e/individual-tool-test.js +0 -143
- package/test/e2e/other-cli-test.js +0 -130
- package/test/e2e/qoder-cli-test.js +0 -128
- package/test/e2e/run-e2e-tests.js +0 -73
- package/test/e2e/test-data.js +0 -88
- package/test/e2e/test-utils.js +0 -222
- package/test/encryption-simple-test.js +0 -110
- package/test/encryption.test.js +0 -129
- package/test/enhanced-main-alignment.test.js +0 -298
- package/test/enhanced-uninstaller-implemented.test.js +0 -271
- package/test/enhanced-uninstaller.test.js +0 -284
- package/test/error-handling-test.js +0 -341
- package/test/fibonacci.test.js +0 -178
- package/test/final-deploy-test.js +0 -221
- package/test/final-install-test.js +0 -226
- package/test/hash-table-demo.js +0 -33
- package/test/hash-table-test.js +0 -26
- package/test/hash_table_test.js +0 -114
- package/test/hook-system-integration-test.js +0 -307
- package/test/iflow-integration-test.js +0 -292
- package/test/improved-install-test.js +0 -362
- package/test/install-command-test.js +0 -370
- package/test/json-parser-test.js +0 -161
- package/test/json-validation-test.js +0 -164
- package/test/natural-language-skills-test.js +0 -320
- package/test/nl-integration-test.js +0 -179
- package/test/parameter-parsing-test.js +0 -143
- package/test/plugin-deployment-test.js +0 -316
- package/test/postinstall-test.js +0 -269
- package/test/python-plugins-test.js +0 -259
- package/test/real-test.js +0 -435
- package/test/remaining-adapters-test.js +0 -256
- package/test/rest-client-test.js +0 -56
- package/test/rest_client.test.js +0 -85
- package/test/safe-installation-cleaner.test.js +0 -343
- package/test/simple-iflow-hook-test.js +0 -137
- package/test/stigmergy-upgrade-test.js +0 -243
- package/test/system-compatibility-test.js +0 -467
- package/test/tdd-deploy-fix-test.js +0 -324
- package/test/tdd-fixes-test.js +0 -211
- package/test/third-party-skills-test.js +0 -321
- package/test/tool-selection-integration-test.js +0 -158
- package/test/unit/calculator-full.test.js +0 -191
- package/test/unit/calculator-simple.test.js +0 -96
- package/test/unit/calculator.test.js +0 -97
- package/test/unit/cli-scanner.test.js +0 -291
- package/test/unit/cli_parameter_handler.test.js +0 -116
- package/test/unit/cross-cli-executor.test.js +0 -399
- package/test/weather-processor.test.js +0 -104
package/src/deploy.js
CHANGED
|
@@ -1,55 +1,55 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Stigmergy Deployment Script
|
|
5
|
-
* This script deploys hooks and integrations for all available CLI tools
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
const fs = require('fs').promises;
|
|
9
|
-
const path = require('path');
|
|
10
|
-
|
|
11
|
-
// Import the main Stigmergy installer
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
// Set up global error handlers using our error handler module
|
|
15
|
-
const { setupGlobalErrorHandlers } = require('./core/error_handler');
|
|
16
|
-
setupGlobalErrorHandlers();
|
|
17
|
-
|
|
18
|
-
async function deploy() {
|
|
19
|
-
console.log('Stigmergy Deployment Script');
|
|
20
|
-
console.log('==========================');
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
// Create installer instance
|
|
24
|
-
const installer = new StigmergyInstaller();
|
|
25
|
-
|
|
26
|
-
// Scan for available tools
|
|
27
|
-
console.log('[SCAN] Scanning for available CLI tools...');
|
|
28
|
-
const scanResult = await installer.scanCLI();
|
|
29
|
-
const available = scanResult.available;
|
|
30
|
-
|
|
31
|
-
// Deploy hooks for all available tools
|
|
32
|
-
console.log('[DEPLOY] Deploying hooks for all available tools...');
|
|
33
|
-
await installer.deployHooks(available);
|
|
34
|
-
|
|
35
|
-
console.log('\n[SUCCESS] Deployment completed successfully!');
|
|
36
|
-
return true;
|
|
37
|
-
} catch (error) {
|
|
38
|
-
console.error('[ERROR] Deployment failed:', error.message);
|
|
39
|
-
return false;
|
|
40
|
-
}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
// Run deployment if called directly
|
|
44
|
-
if (require.main === module) {
|
|
45
|
-
deploy()
|
|
46
|
-
.then((success) => {
|
|
47
|
-
process.exit(success ? 0 : 1);
|
|
48
|
-
})
|
|
49
|
-
.catch((error) => {
|
|
50
|
-
console.error('[FATAL ERROR]:', error.message);
|
|
51
|
-
process.exit(1);
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
module.exports = { deploy };
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stigmergy Deployment Script
|
|
5
|
+
* This script deploys hooks and integrations for all available CLI tools
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs').promises;
|
|
9
|
+
const path = require('path');
|
|
10
|
+
|
|
11
|
+
// Import the main Stigmergy installer
|
|
12
|
+
const StigmergyInstaller = require('./core/installer');
|
|
13
|
+
|
|
14
|
+
// Set up global error handlers using our error handler module
|
|
15
|
+
const { setupGlobalErrorHandlers } = require('./core/error_handler');
|
|
16
|
+
setupGlobalErrorHandlers();
|
|
17
|
+
|
|
18
|
+
async function deploy() {
|
|
19
|
+
console.log('Stigmergy Deployment Script');
|
|
20
|
+
console.log('==========================');
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
// Create installer instance
|
|
24
|
+
const installer = new StigmergyInstaller();
|
|
25
|
+
|
|
26
|
+
// Scan for available tools
|
|
27
|
+
console.log('[SCAN] Scanning for available CLI tools...');
|
|
28
|
+
const scanResult = await installer.scanCLI();
|
|
29
|
+
const available = scanResult.available;
|
|
30
|
+
|
|
31
|
+
// Deploy hooks for all available tools
|
|
32
|
+
console.log('[DEPLOY] Deploying hooks for all available tools...');
|
|
33
|
+
await installer.deployHooks(available);
|
|
34
|
+
|
|
35
|
+
console.log('\n[SUCCESS] Deployment completed successfully!');
|
|
36
|
+
return true;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
console.error('[ERROR] Deployment failed:', error.message);
|
|
39
|
+
return false;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Run deployment if called directly
|
|
44
|
+
if (require.main === module) {
|
|
45
|
+
deploy()
|
|
46
|
+
.then((success) => {
|
|
47
|
+
process.exit(success ? 0 : 1);
|
|
48
|
+
})
|
|
49
|
+
.catch((error) => {
|
|
50
|
+
console.error('[FATAL ERROR]:', error.message);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
module.exports = { deploy };
|
package/src/index.js
CHANGED
|
@@ -1,30 +1,30 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Stigmergy CLI - Multi-Agents Cross-AI CLI Tools Collaboration System
|
|
5
|
-
* Unified Entry Point
|
|
6
|
-
* International Version - Pure English & ANSI Only
|
|
7
|
-
* Version: 1.0.94
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
// Import all components
|
|
11
|
-
const MemoryManager = require('./core/memory_manager');
|
|
12
|
-
const StigmergyInstaller = require('./core/installer');
|
|
13
|
-
const { maxOfTwo, isAuthenticated } = require('./utils/helpers');
|
|
14
|
-
const main = require('./cli/router');
|
|
15
|
-
|
|
16
|
-
// Run the main application
|
|
17
|
-
if (require.main === module) {
|
|
18
|
-
main().catch(error => {
|
|
19
|
-
console.error('[FATAL] Unhandled error in Stigmergy CLI:', error);
|
|
20
|
-
process.exit(1);
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
module.exports = {
|
|
25
|
-
MemoryManager,
|
|
26
|
-
StigmergyInstaller,
|
|
27
|
-
maxOfTwo,
|
|
28
|
-
isAuthenticated,
|
|
29
|
-
main
|
|
30
|
-
};
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Stigmergy CLI - Multi-Agents Cross-AI CLI Tools Collaboration System
|
|
5
|
+
* Unified Entry Point
|
|
6
|
+
* International Version - Pure English & ANSI Only
|
|
7
|
+
* Version: 1.0.94
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Import all components
|
|
11
|
+
const MemoryManager = require('./core/memory_manager');
|
|
12
|
+
const StigmergyInstaller = require('./core/installer');
|
|
13
|
+
const { maxOfTwo, isAuthenticated } = require('./utils/helpers');
|
|
14
|
+
const main = require('./cli/router');
|
|
15
|
+
|
|
16
|
+
// Run the main application
|
|
17
|
+
if (require.main === module) {
|
|
18
|
+
main().catch((error) => {
|
|
19
|
+
console.error('[FATAL] Unhandled error in Stigmergy CLI:', error);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
MemoryManager,
|
|
26
|
+
StigmergyInstaller,
|
|
27
|
+
maxOfTwo,
|
|
28
|
+
isAuthenticated,
|
|
29
|
+
main,
|
|
30
|
+
};
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI Availability Checker
|
|
3
|
+
* Detects which CLI tools are installed and available for testing
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const { spawn } = require('child_process');
|
|
7
|
+
const { CLI_TOOLS } = require('../../src/core/cli_tools');
|
|
8
|
+
|
|
9
|
+
class CLIAvailabilityChecker {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.checkedTools = new Map();
|
|
12
|
+
this.timeout = 5000; // 5 seconds timeout for each check
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if a CLI tool is installed and available
|
|
17
|
+
* @param {string} toolName - Name of the CLI tool
|
|
18
|
+
* @returns {Promise<boolean>} True if tool is available
|
|
19
|
+
*/
|
|
20
|
+
async isToolAvailable(toolName) {
|
|
21
|
+
if (this.checkedTools.has(toolName)) {
|
|
22
|
+
return this.checkedTools.get(toolName);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const tool = CLI_TOOLS[toolName];
|
|
26
|
+
if (!tool) {
|
|
27
|
+
this.checkedTools.set(toolName, false);
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
try {
|
|
32
|
+
const isAvailable = await this.checkToolExists(toolName, tool.version);
|
|
33
|
+
this.checkedTools.set(toolName, isAvailable);
|
|
34
|
+
return isAvailable;
|
|
35
|
+
} catch (error) {
|
|
36
|
+
this.checkedTools.set(toolName, false);
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Check if a specific command exists
|
|
43
|
+
* @param {string} toolName - Tool name
|
|
44
|
+
* @param {string} versionCommand - Command to check version
|
|
45
|
+
* @returns {Promise<boolean>} True if command exists
|
|
46
|
+
*/
|
|
47
|
+
async checkToolExists(toolName, versionCommand) {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
const parts = versionCommand.split(' ');
|
|
50
|
+
const command = parts[0];
|
|
51
|
+
const args = parts.slice(1);
|
|
52
|
+
|
|
53
|
+
const child = spawn(command, args, {
|
|
54
|
+
stdio: 'pipe',
|
|
55
|
+
shell: true,
|
|
56
|
+
windowsHide: true
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
let resolved = false;
|
|
60
|
+
let stdout = '';
|
|
61
|
+
let stderr = '';
|
|
62
|
+
|
|
63
|
+
child.stdout?.on('data', (data) => {
|
|
64
|
+
stdout += data.toString();
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
child.stderr?.on('data', (data) => {
|
|
68
|
+
stderr += data.toString();
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// Set timeout
|
|
72
|
+
const timeoutId = setTimeout(() => {
|
|
73
|
+
if (!resolved) {
|
|
74
|
+
resolved = true;
|
|
75
|
+
child.kill('SIGTERM');
|
|
76
|
+
resolve(false);
|
|
77
|
+
}
|
|
78
|
+
}, this.timeout);
|
|
79
|
+
|
|
80
|
+
child.on('close', (code) => {
|
|
81
|
+
if (!resolved) {
|
|
82
|
+
resolved = true;
|
|
83
|
+
clearTimeout(timeoutId);
|
|
84
|
+
|
|
85
|
+
// Check for serious errors
|
|
86
|
+
if (stderr.includes('ERR_MODULE_NOT_FOUND') ||
|
|
87
|
+
stderr.includes('Cannot find module') ||
|
|
88
|
+
stderr.includes('command not found') ||
|
|
89
|
+
stderr.includes('is not recognized')) {
|
|
90
|
+
resolve(false);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Consider exit code 0, 1, or 255 as success
|
|
95
|
+
// Some CLIs return 255 for version commands
|
|
96
|
+
// But also check that we got some output (not just empty error)
|
|
97
|
+
const hasOutput = stdout.length > 0 || (stderr.length > 0 && !stderr.includes('Error'));
|
|
98
|
+
resolve((code === 0 || code === 1 || code === 255) && hasOutput);
|
|
99
|
+
}
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
child.on('error', () => {
|
|
103
|
+
if (!resolved) {
|
|
104
|
+
resolved = true;
|
|
105
|
+
clearTimeout(timeoutId);
|
|
106
|
+
resolve(false);
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Get availability of all CLI tools
|
|
114
|
+
* @returns {Promise<Object>} Map of tool availability
|
|
115
|
+
*/
|
|
116
|
+
async checkAllTools() {
|
|
117
|
+
const results = {};
|
|
118
|
+
const toolNames = Object.keys(CLI_TOOLS);
|
|
119
|
+
|
|
120
|
+
// Check tools sequentially to avoid conflicts
|
|
121
|
+
for (const toolName of toolNames) {
|
|
122
|
+
results[toolName] = await this.isToolAvailable(toolName);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return results;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Get only available tools
|
|
130
|
+
* @returns {Promise<Array>} List of available tool names
|
|
131
|
+
*/
|
|
132
|
+
async getAvailableTools() {
|
|
133
|
+
const allTools = await this.checkAllTools();
|
|
134
|
+
return Object.entries(allTools)
|
|
135
|
+
.filter(([_, available]) => available)
|
|
136
|
+
.map(([name, _]) => name);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Get installation commands for unavailable tools
|
|
141
|
+
* @returns {Promise<Object>} Map of tool to installation command
|
|
142
|
+
*/
|
|
143
|
+
async getInstallationCommands() {
|
|
144
|
+
const allTools = await this.checkAllTools();
|
|
145
|
+
const commands = {};
|
|
146
|
+
|
|
147
|
+
Object.entries(allTools).forEach(([toolName, available]) => {
|
|
148
|
+
if (!available && CLI_TOOLS[toolName]) {
|
|
149
|
+
commands[toolName] = CLI_TOOLS[toolName].install;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
return commands;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Print availability report
|
|
158
|
+
*/
|
|
159
|
+
async printAvailabilityReport() {
|
|
160
|
+
const allTools = await this.checkAllTools();
|
|
161
|
+
const available = Object.entries(allTools).filter(([_, a]) => a).length;
|
|
162
|
+
const total = Object.keys(allTools).length;
|
|
163
|
+
|
|
164
|
+
console.log('\n=== CLI Tool Availability Report ===');
|
|
165
|
+
console.log(`Available: ${available}/${total}\n`);
|
|
166
|
+
|
|
167
|
+
console.log('�?Available Tools:');
|
|
168
|
+
Object.entries(allTools)
|
|
169
|
+
.filter(([_, available]) => available)
|
|
170
|
+
.forEach(([toolName, _]) => {
|
|
171
|
+
console.log(` - ${toolName}`);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
const unavailable = Object.entries(allTools).filter(([_, a]) => !a);
|
|
175
|
+
if (unavailable.length > 0) {
|
|
176
|
+
console.log('\n�?Unavailable Tools:');
|
|
177
|
+
unavailable.forEach(([toolName, _]) => {
|
|
178
|
+
console.log(` - ${toolName}`);
|
|
179
|
+
console.log(` Install: ${CLI_TOOLS[toolName].install}`);
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
console.log('=====================================\n');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Clear cache of checked tools
|
|
188
|
+
*/
|
|
189
|
+
clearCache() {
|
|
190
|
+
this.checkedTools.clear();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
module.exports = CLIAvailabilityChecker;
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Test Environment Isolation
|
|
3
|
+
* Creates isolated environment for testing without affecting user setup
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const { spawn } = require('child_process');
|
|
10
|
+
|
|
11
|
+
class TestEnvironment {
|
|
12
|
+
constructor(name = 'stigmergy-test') {
|
|
13
|
+
this.name = name;
|
|
14
|
+
this.baseDir = path.join(os.tmpdir(), name);
|
|
15
|
+
this.originalEnv = { ...process.env };
|
|
16
|
+
this.isSetup = false;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Setup isolated test environment
|
|
21
|
+
* @param {Object} options - Setup options
|
|
22
|
+
*/
|
|
23
|
+
async setup(options = {}) {
|
|
24
|
+
const { clean = true } = options;
|
|
25
|
+
|
|
26
|
+
// Create base directory
|
|
27
|
+
if (clean && fs.existsSync(this.baseDir)) {
|
|
28
|
+
await this.cleanup();
|
|
29
|
+
}
|
|
30
|
+
fs.mkdirSync(this.baseDir, { recursive: true });
|
|
31
|
+
|
|
32
|
+
// Setup home directory
|
|
33
|
+
this.testHome = path.join(this.baseDir, 'home');
|
|
34
|
+
fs.mkdirSync(this.testHome, { recursive: true });
|
|
35
|
+
|
|
36
|
+
// Setup test configuration directories
|
|
37
|
+
this.configDirs = {
|
|
38
|
+
stigmergy: path.join(this.testHome, '.stigmergy'),
|
|
39
|
+
claude: path.join(this.testHome, '.claude'),
|
|
40
|
+
gemini: path.join(this.testHome, '.gemini'),
|
|
41
|
+
qwen: path.join(this.testHome, '.qwen'),
|
|
42
|
+
// Add more as needed
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
Object.values(this.configDirs).forEach(dir => {
|
|
46
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
// Modify environment variables
|
|
50
|
+
process.env.HOME = this.testHome;
|
|
51
|
+
process.env.USERPROFILE = this.testHome; // Windows
|
|
52
|
+
process.env.PATH = this.createSafePath();
|
|
53
|
+
|
|
54
|
+
this.isSetup = true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create safe PATH with essential tools only
|
|
59
|
+
* @returns {string} Safe PATH
|
|
60
|
+
*/
|
|
61
|
+
createSafePath() {
|
|
62
|
+
const essentialPaths = [
|
|
63
|
+
'/usr/bin',
|
|
64
|
+
'/usr/local/bin',
|
|
65
|
+
path.join(process.env.ProgramFiles || '', 'Git', 'bin'),
|
|
66
|
+
path.join(process.env.ProgramFiles || '', 'nodejs'),
|
|
67
|
+
].filter(p => p && fs.existsSync(p));
|
|
68
|
+
|
|
69
|
+
return essentialPaths.join(path.delimiter);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Setup mock CLI tools for testing
|
|
74
|
+
* @param {Object} mockTools - Map of tool name to mock implementation
|
|
75
|
+
*/
|
|
76
|
+
async setupMockCLIs(mockTools = {}) {
|
|
77
|
+
const mockBinDir = path.join(this.testHome, 'bin');
|
|
78
|
+
fs.mkdirSync(mockBinDir, { recursive: true });
|
|
79
|
+
|
|
80
|
+
// Add mock bin directory to PATH
|
|
81
|
+
process.env.PATH = `${mockBinDir}${path.delimiter}${process.env.PATH}`;
|
|
82
|
+
|
|
83
|
+
for (const [toolName, behavior] of Object.entries(mockTools)) {
|
|
84
|
+
await this.createMockCLI(toolName, behavior, mockBinDir);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Create a mock CLI tool
|
|
90
|
+
* @param {string} name - Tool name
|
|
91
|
+
* @param {Object} behavior - Mock behavior
|
|
92
|
+
* @param {string} binDir - Binary directory
|
|
93
|
+
*/
|
|
94
|
+
async createMockCLI(name, behavior, binDir) {
|
|
95
|
+
const script = this.generateMockScript(name, behavior);
|
|
96
|
+
const scriptPath = path.join(binDir, name);
|
|
97
|
+
|
|
98
|
+
if (process.platform === 'win32') {
|
|
99
|
+
// Windows batch file
|
|
100
|
+
const batPath = scriptPath + '.cmd';
|
|
101
|
+
fs.writeFileSync(batPath, `@echo off\n${script}\n`, 'utf8');
|
|
102
|
+
// Make executable
|
|
103
|
+
fs.chmodSync(batPath, '755');
|
|
104
|
+
} else {
|
|
105
|
+
// Unix shell script
|
|
106
|
+
fs.writeFileSync(scriptPath, `#!/bin/bash\n${script}\n`, 'utf8');
|
|
107
|
+
// Make executable
|
|
108
|
+
fs.chmodSync(scriptPath, '755');
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Generate mock script content
|
|
114
|
+
* @param {string} toolName - Tool name
|
|
115
|
+
* @param {Object} behavior - Mock behavior definition
|
|
116
|
+
* @returns {string} Script content
|
|
117
|
+
*/
|
|
118
|
+
generateMockScript(toolName, behavior) {
|
|
119
|
+
const defaultBehavior = {
|
|
120
|
+
version: `${toolName} version 1.0.0-mock`,
|
|
121
|
+
success: true,
|
|
122
|
+
delay: 0,
|
|
123
|
+
output: `Mock ${toolName} executed successfully`
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const config = { ...defaultBehavior, ...behavior };
|
|
127
|
+
|
|
128
|
+
let script = '';
|
|
129
|
+
if (config.delay > 0) {
|
|
130
|
+
script += `sleep ${config.delay}\n`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Handle version command
|
|
134
|
+
script += `if echo "$*" | grep -q "\\-\\-version\\|-v"; then\n`;
|
|
135
|
+
script += ` echo "${config.version}"\n`;
|
|
136
|
+
script += ` exit 0\n`;
|
|
137
|
+
script += `fi\n`;
|
|
138
|
+
|
|
139
|
+
// Handle help command
|
|
140
|
+
script += `if echo "$*" | grep -q "\\-\\-help\\|-h"; then\n`;
|
|
141
|
+
script += ` echo "Mock ${toolName} help"\n`;
|
|
142
|
+
script += ` echo "Usage: ${toolName} [options]"\n`;
|
|
143
|
+
script += ` exit 0\n`;
|
|
144
|
+
script += `fi\n`;
|
|
145
|
+
|
|
146
|
+
// Handle prompt flag
|
|
147
|
+
script += `PROMPT=""\n`;
|
|
148
|
+
script += `for arg in "$@"; do\n`;
|
|
149
|
+
script += ` if [[ "$arg" == "-p" ]]; then\n`;
|
|
150
|
+
script += ` PROMPT="$1"\n`;
|
|
151
|
+
script += ` shift\n`;
|
|
152
|
+
script += ` fi\n`;
|
|
153
|
+
script += ` shift\n`;
|
|
154
|
+
script += `done\n`;
|
|
155
|
+
|
|
156
|
+
// Generate response
|
|
157
|
+
script += `if [ -n "$PROMPT" ]; then\n`;
|
|
158
|
+
script += ` echo "Mock response for: $PROMPT"\n`;
|
|
159
|
+
script += ` echo "${config.output}"\n`;
|
|
160
|
+
script += `else\n`;
|
|
161
|
+
script += ` echo "${config.output}"\n`;
|
|
162
|
+
script += `fi\n`;
|
|
163
|
+
|
|
164
|
+
// Handle success/failure
|
|
165
|
+
script += config.success ? 'exit 0\n' : 'exit 1\n';
|
|
166
|
+
|
|
167
|
+
return script;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Execute command in isolated environment
|
|
172
|
+
* @param {string} command - Command to execute
|
|
173
|
+
* @param {Array} args - Command arguments
|
|
174
|
+
* @param {Object} options - Execution options
|
|
175
|
+
* @returns {Promise<Object>} Execution result
|
|
176
|
+
*/
|
|
177
|
+
async execute(command, args = [], options = {}) {
|
|
178
|
+
if (!this.isSetup) {
|
|
179
|
+
throw new Error('Test environment not setup. Call setup() first.');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return new Promise((resolve) => {
|
|
183
|
+
let stdout = '';
|
|
184
|
+
let stderr = '';
|
|
185
|
+
|
|
186
|
+
const child = spawn(command, args, {
|
|
187
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
188
|
+
shell: true,
|
|
189
|
+
env: process.env, // Use modified environment
|
|
190
|
+
...options
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
child.stdout?.on('data', (data) => {
|
|
194
|
+
stdout += data.toString();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
child.stderr?.on('data', (data) => {
|
|
198
|
+
stderr += data.toString();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
child.on('close', (code) => {
|
|
202
|
+
resolve({
|
|
203
|
+
success: code === 0,
|
|
204
|
+
stdout,
|
|
205
|
+
stderr,
|
|
206
|
+
exitCode: code
|
|
207
|
+
});
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
child.on('error', (error) => {
|
|
211
|
+
resolve({
|
|
212
|
+
success: false,
|
|
213
|
+
stdout,
|
|
214
|
+
stderr,
|
|
215
|
+
error: error.message
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Create test data in environment
|
|
223
|
+
* @param {Object} testData - Test data structure
|
|
224
|
+
*/
|
|
225
|
+
createTestData(testData = {}) {
|
|
226
|
+
for (const [filePath, content] of Object.entries(testData)) {
|
|
227
|
+
const fullPath = path.join(this.testHome, filePath);
|
|
228
|
+
const dir = path.dirname(fullPath);
|
|
229
|
+
|
|
230
|
+
// Create directory if it doesn't exist
|
|
231
|
+
if (!fs.existsSync(dir)) {
|
|
232
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Write file
|
|
236
|
+
if (typeof content === 'object') {
|
|
237
|
+
fs.writeFileSync(fullPath, JSON.stringify(content, null, 2), 'utf8');
|
|
238
|
+
} else {
|
|
239
|
+
fs.writeFileSync(fullPath, content, 'utf8');
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Restore original environment
|
|
246
|
+
*/
|
|
247
|
+
restore() {
|
|
248
|
+
if (!this.isSetup) return;
|
|
249
|
+
|
|
250
|
+
// Restore environment variables
|
|
251
|
+
Object.keys(this.originalEnv).forEach(key => {
|
|
252
|
+
process.env[key] = this.originalEnv[key];
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// Remove any additions
|
|
256
|
+
const addedKeys = Object.keys(process.env).filter(key => !this.originalEnv.hasOwnProperty(key));
|
|
257
|
+
addedKeys.forEach(key => {
|
|
258
|
+
delete process.env[key];
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
this.isSetup = false;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Cleanup test environment
|
|
266
|
+
*/
|
|
267
|
+
async cleanup() {
|
|
268
|
+
this.restore();
|
|
269
|
+
|
|
270
|
+
try {
|
|
271
|
+
if (fs.existsSync(this.baseDir)) {
|
|
272
|
+
fs.rmSync(this.baseDir, { recursive: true, force: true });
|
|
273
|
+
}
|
|
274
|
+
} catch (error) {
|
|
275
|
+
console.warn(`Warning: Could not cleanup test directory: ${error.message}`);
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Get path in test environment
|
|
281
|
+
* @param {...string} parts - Path parts
|
|
282
|
+
* @returns {string} Full path in test environment
|
|
283
|
+
*/
|
|
284
|
+
getPath(...parts) {
|
|
285
|
+
return path.join(this.testHome, ...parts);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = TestEnvironment;
|