stigmergy 1.0.68 → 1.0.70
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +306 -300
- package/README.md +469 -301
- package/package.json +97 -81
- package/scripts/publish.js +268 -0
- package/scripts/simple-publish.js +59 -0
- package/src/index.js +12 -0
- package/test/enhanced-main-alignment.test.js +298 -0
- package/test/hook-system-integration-test.js +307 -0
- package/test/natural-language-skills-test.js +320 -0
- package/test/nl-integration-test.js +179 -0
- package/test/parameter-parsing-test.js +143 -0
- package/test/real-test.js +435 -0
- package/test/system-compatibility-test.js +447 -0
- package/test/tdd-fixes-test.js +211 -0
- package/test/third-party-skills-test.js +321 -0
- package/test/tool-selection-integration-test.js +157 -0
- package/test/unit/cli-scanner.test.js +291 -0
- package/test/unit/cross-cli-executor.test.js +399 -0
- package/src/adapters/claude/__init__.py +0 -13
- package/src/adapters/claude/claude_skills_integration.py +0 -609
- package/src/adapters/claude/hook_adapter.py +0 -663
- package/src/adapters/claude/install_claude_integration.py +0 -265
- package/src/adapters/claude/skills_hook_adapter.py +0 -841
- package/src/adapters/claude/standalone_claude_adapter.py +0 -384
- package/src/adapters/cline/__init__.py +0 -20
- package/src/adapters/cline/config.py +0 -108
- package/src/adapters/cline/install_cline_integration.py +0 -617
- package/src/adapters/cline/mcp_server.py +0 -713
- package/src/adapters/cline/standalone_cline_adapter.py +0 -459
- package/src/adapters/codebuddy/__init__.py +0 -13
- package/src/adapters/codebuddy/buddy_adapter.py +0 -1125
- package/src/adapters/codebuddy/install_codebuddy_integration.py +0 -279
- package/src/adapters/codebuddy/skills_hook_adapter.py +0 -672
- package/src/adapters/codebuddy/skills_integration.py +0 -395
- package/src/adapters/codebuddy/standalone_codebuddy_adapter.py +0 -403
- package/src/adapters/codex/__init__.py +0 -11
- package/src/adapters/codex/base.py +0 -46
- package/src/adapters/codex/install_codex_integration.py +0 -311
- package/src/adapters/codex/mcp_server.py +0 -493
- package/src/adapters/codex/natural_language_parser.py +0 -82
- package/src/adapters/codex/slash_command_adapter.py +0 -326
- package/src/adapters/codex/standalone_codex_adapter.py +0 -362
- package/src/adapters/copilot/__init__.py +0 -13
- package/src/adapters/copilot/install_copilot_integration.py +0 -564
- package/src/adapters/copilot/mcp_adapter.py +0 -772
- package/src/adapters/copilot/mcp_server.py +0 -168
- package/src/adapters/copilot/standalone_copilot_adapter.py +0 -114
- package/src/adapters/gemini/__init__.py +0 -13
- package/src/adapters/gemini/extension_adapter.py +0 -690
- package/src/adapters/gemini/install_gemini_integration.py +0 -257
- package/src/adapters/gemini/standalone_gemini_adapter.py +0 -366
- package/src/adapters/iflow/__init__.py +0 -7
- package/src/adapters/iflow/hook_adapter.py +0 -1038
- package/src/adapters/iflow/hook_installer.py +0 -536
- package/src/adapters/iflow/install_iflow_integration.py +0 -271
- package/src/adapters/iflow/official_hook_adapter.py +0 -1272
- package/src/adapters/iflow/standalone_iflow_adapter.py +0 -48
- package/src/adapters/iflow/workflow_adapter.py +0 -793
- package/src/adapters/qoder/hook_installer.py +0 -732
- package/src/adapters/qoder/install_qoder_integration.py +0 -265
- package/src/adapters/qoder/notification_hook_adapter.py +0 -863
- package/src/adapters/qoder/standalone_qoder_adapter.py +0 -48
- package/src/adapters/qwen/__init__.py +0 -17
- package/src/adapters/qwencode/__init__.py +0 -13
- package/src/adapters/qwencode/inheritance_adapter.py +0 -818
- package/src/adapters/qwencode/install_qwencode_integration.py +0 -276
- package/src/adapters/qwencode/standalone_qwencode_adapter.py +0 -399
- package/src/atomic_collaboration_handler.py +0 -461
- package/src/cli_collaboration_agent.py +0 -697
- package/src/collaboration/hooks.py +0 -315
- package/src/core/__init__.py +0 -21
- package/src/core/ai_environment_scanner.py +0 -331
- package/src/core/base_adapter.py +0 -220
- package/src/core/cli_hook_integration.py +0 -406
- package/src/core/cross_cli_executor.py +0 -713
- package/src/core/cross_cli_mapping.py +0 -1165
- package/src/core/cross_platform_encoding.py +0 -365
- package/src/core/cross_platform_safe_cli.py +0 -894
- package/src/core/direct_cli_executor.py +0 -805
- package/src/core/direct_cli_hook_system.py +0 -958
- package/src/core/enhanced_init_processor.py +0 -467
- package/src/core/graceful_cli_executor.py +0 -912
- package/src/core/md_enhancer.py +0 -342
- package/src/core/md_generator.py +0 -619
- package/src/core/models.py +0 -218
- package/src/core/parser.py +0 -108
- package/src/core/real_cli_hook_system.py +0 -852
- package/src/core/real_cross_cli_system.py +0 -925
- package/src/core/verified_cross_cli_system.py +0 -961
- package/src/deploy.js +0 -737
- package/src/enhanced-main.js +0 -626
- package/src/enhanced_deploy.js +0 -303
- package/src/enhanced_universal_cli_setup.py +0 -930
- package/src/kimi_wrapper.py +0 -104
- package/src/main.js +0 -1309
- package/src/shell_integration.py +0 -398
- package/src/simple-main.js +0 -315
- package/src/smart_router_creator.py +0 -323
- package/src/universal_cli_setup.py +0 -1289
- package/src/utils/__init__.py +0 -12
- package/src/utils/cli_detector.py +0 -445
- package/src/utils/file_utils.py +0 -246
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TDD: CLI Scanner Unit Tests
|
|
3
|
+
* 测试驱动开发 - 先写测试,再写实现
|
|
4
|
+
* 使用ANSI编码,无Unicode字符
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const assert = require('assert');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
// 测试目标类 - 尚未实现
|
|
11
|
+
class CLIScanner {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.scanResults = new Map();
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async scanForCLI(cliName) {
|
|
17
|
+
throw new Error('Not implemented yet - TDD approach');
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
async detectInstalledCLIs() {
|
|
21
|
+
throw new Error('Not implemented yet - TDD approach');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
validateCLIExecutable(command) {
|
|
25
|
+
throw new Error('Not implemented yet - TDD approach');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
describe('CLI Scanner Unit Tests - ANSI Encoding, Node.js First', () => {
|
|
30
|
+
let scanner;
|
|
31
|
+
|
|
32
|
+
beforeEach(() => {
|
|
33
|
+
scanner = new CLIScanner();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
describe('Basic CLI Detection', () => {
|
|
37
|
+
it('should detect node command is available', async () => {
|
|
38
|
+
// 测试最基础的情况 - node命令应该可用
|
|
39
|
+
const result = await scanner.scanForCLI('node');
|
|
40
|
+
|
|
41
|
+
assert.strictEqual(result.cliName, 'node');
|
|
42
|
+
assert.strictEqual(result.available, true);
|
|
43
|
+
assert.ok(result.version);
|
|
44
|
+
assert.ok(result.path);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should detect npm command is available', async () => {
|
|
48
|
+
const result = await scanner.scanForCLI('npm');
|
|
49
|
+
|
|
50
|
+
assert.strictEqual(result.cliName, 'npm');
|
|
51
|
+
assert.strictEqual(result.available, true);
|
|
52
|
+
assert.ok(result.version);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('should handle non-existent CLI gracefully', async () => {
|
|
56
|
+
const result = await scanner.scanForCLI('non-existent-cli-12345');
|
|
57
|
+
|
|
58
|
+
assert.strictEqual(result.cliName, 'non-existent-cli-12345');
|
|
59
|
+
assert.strictEqual(result.available, false);
|
|
60
|
+
assert.ok(result.error);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
describe('AI CLI Detection', () => {
|
|
65
|
+
it('should detect Claude CLI if installed', async () => {
|
|
66
|
+
const result = await scanner.scanForCLI('claude');
|
|
67
|
+
|
|
68
|
+
assert.strictEqual(result.cliName, 'claude');
|
|
69
|
+
assert(typeof result.available === 'boolean');
|
|
70
|
+
|
|
71
|
+
if (result.available) {
|
|
72
|
+
assert.ok(result.version);
|
|
73
|
+
assert.ok(result.path);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should detect Gemini CLI if installed', async () => {
|
|
78
|
+
const result = await scanner.scanForCLI('gemini');
|
|
79
|
+
|
|
80
|
+
assert.strictEqual(result.cliName, 'gemini');
|
|
81
|
+
assert(typeof result.available === 'boolean');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('should detect Qwen CLI if installed', async () => {
|
|
85
|
+
const result = await scanner.scanForCLI('qwen');
|
|
86
|
+
|
|
87
|
+
assert.strictEqual(result.cliName, 'qwen');
|
|
88
|
+
assert(typeof result.available === 'boolean');
|
|
89
|
+
});
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
describe('CLI Validation', () => {
|
|
93
|
+
it('should validate valid Windows commands', () => {
|
|
94
|
+
if (process.platform === 'win32') {
|
|
95
|
+
assert.ok(scanner.validateCLIExecutable('cmd'));
|
|
96
|
+
assert.ok(scanner.validateCLIExecutable('powershell'));
|
|
97
|
+
assert.ok(scanner.validateCLIExecutable('node'));
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('should validate valid Unix commands', () => {
|
|
102
|
+
if (process.platform !== 'win32') {
|
|
103
|
+
assert.ok(scanner.validateCLIExecutable('sh'));
|
|
104
|
+
assert.ok(scanner.validateCLIExecutable('ls'));
|
|
105
|
+
assert.ok(scanner.validateCLIExecutable('node'));
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should reject invalid commands', () => {
|
|
110
|
+
assert.strictEqual(scanner.validateCLIExecutable(''), false);
|
|
111
|
+
assert.strictEqual(scanner.validateCLIExecutable('non-existent-command-12345'), false);
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
describe('Batch Detection', () => {
|
|
116
|
+
it('should detect all basic development tools', async () => {
|
|
117
|
+
const tools = ['node', 'npm', 'git'];
|
|
118
|
+
const results = await scanner.detectInstalledCLIs(tools);
|
|
119
|
+
|
|
120
|
+
assert.ok(results.size >= 2); // 至少node和npm应该可用
|
|
121
|
+
assert.ok(results.has('node'));
|
|
122
|
+
assert.ok(results.has('npm'));
|
|
123
|
+
|
|
124
|
+
// node和npm都应该可用
|
|
125
|
+
assert.strictEqual(results.get('node').available, true);
|
|
126
|
+
assert.strictEqual(results.get('npm').available, true);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it('should handle mixed available/unavailable tools', async () => {
|
|
130
|
+
const tools = ['node', 'non-existent-cli', 'npm'];
|
|
131
|
+
const results = await scanner.detectInstalledCLIs(tools);
|
|
132
|
+
|
|
133
|
+
assert.strictEqual(results.size, 3);
|
|
134
|
+
assert.ok(results.has('node'));
|
|
135
|
+
assert.ok(results.has('npm'));
|
|
136
|
+
assert.ok(results.has('non-existent-cli'));
|
|
137
|
+
|
|
138
|
+
assert.strictEqual(results.get('node').available, true);
|
|
139
|
+
assert.strictEqual(results.get('npm').available, true);
|
|
140
|
+
assert.strictEqual(results.get('non-existent-cli').available, false);
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
describe('Error Handling', () => {
|
|
145
|
+
it('should handle null input gracefully', async () => {
|
|
146
|
+
try {
|
|
147
|
+
await scanner.scanForCLI(null);
|
|
148
|
+
assert.fail('Should have thrown an error');
|
|
149
|
+
} catch (error) {
|
|
150
|
+
assert.ok(error.message.includes('Invalid CLI name'));
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it('should handle empty string gracefully', async () => {
|
|
155
|
+
try {
|
|
156
|
+
await scanner.scanForCLI('');
|
|
157
|
+
assert.fail('Should have thrown an error');
|
|
158
|
+
} catch (error) {
|
|
159
|
+
assert.ok(error.message.includes('Invalid CLI name'));
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('should handle timeout during scanning', async () => {
|
|
164
|
+
// 测试超时处理
|
|
165
|
+
const timeoutMs = 1000;
|
|
166
|
+
const startTime = Date.now();
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
await scanner.scanForCLI('sleep-command-that-does-not-exist', { timeout: timeoutMs });
|
|
170
|
+
} catch (error) {
|
|
171
|
+
const elapsed = Date.now() - startTime;
|
|
172
|
+
assert.ok(elapsed < timeoutMs + 1000); // 允许一些误差
|
|
173
|
+
assert.ok(error.message.includes('timeout') || error.message.includes('not found'));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('Platform Specific Tests', () => {
|
|
179
|
+
it('should work on Windows', async () => {
|
|
180
|
+
if (process.platform === 'win32') {
|
|
181
|
+
const result = await scanner.scanForCLI('node');
|
|
182
|
+
assert.strictEqual(result.available, true);
|
|
183
|
+
assert.ok(result.path.includes('.exe') || result.path.includes('node'));
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('should work on Unix-like systems', async () => {
|
|
188
|
+
if (process.platform !== 'win32') {
|
|
189
|
+
const result = await scanner.scanForCLI('node');
|
|
190
|
+
assert.strictEqual(result.available, true);
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
describe('Performance Tests', () => {
|
|
196
|
+
it('should scan multiple CLIs efficiently', async () => {
|
|
197
|
+
const tools = ['node', 'npm', 'git', 'code'];
|
|
198
|
+
const startTime = Date.now();
|
|
199
|
+
|
|
200
|
+
const results = await scanner.detectInstalledCLIs(tools);
|
|
201
|
+
|
|
202
|
+
const elapsed = Date.now() - startTime;
|
|
203
|
+
assert.ok(elapsed < 5000); // 应该在5秒内完成
|
|
204
|
+
assert.strictEqual(results.size, tools.length);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it('should cache scan results', async () => {
|
|
208
|
+
const cliName = 'node';
|
|
209
|
+
|
|
210
|
+
// 第一次扫描
|
|
211
|
+
const startTime1 = Date.now();
|
|
212
|
+
const result1 = await scanner.scanForCLI(cliName);
|
|
213
|
+
const time1 = Date.now() - startTime1;
|
|
214
|
+
|
|
215
|
+
// 第二次扫描(应该使用缓存)
|
|
216
|
+
const startTime2 = Date.now();
|
|
217
|
+
const result2 = await scanner.scanForCLI(cliName);
|
|
218
|
+
const time2 = Date.now() - startTime2;
|
|
219
|
+
|
|
220
|
+
assert.deepStrictEqual(result1, result2);
|
|
221
|
+
assert.ok(time2 <= time1); // 缓存应该更快或相等
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
// 运行测试
|
|
227
|
+
if (require.main === module) {
|
|
228
|
+
console.log('Running CLI Scanner Unit Tests...');
|
|
229
|
+
|
|
230
|
+
// 简单的测试运行器
|
|
231
|
+
const testMethods = [
|
|
232
|
+
'should detect node command is available',
|
|
233
|
+
'should detect npm command is available',
|
|
234
|
+
'should handle non-existent CLI gracefully',
|
|
235
|
+
'should validate valid Windows commands',
|
|
236
|
+
'should reject invalid commands',
|
|
237
|
+
'should handle null input gracefully'
|
|
238
|
+
];
|
|
239
|
+
|
|
240
|
+
(async () => {
|
|
241
|
+
const scanner = new CLIScanner();
|
|
242
|
+
let passed = 0;
|
|
243
|
+
let failed = 0;
|
|
244
|
+
|
|
245
|
+
for (const testName of testMethods) {
|
|
246
|
+
try {
|
|
247
|
+
console.log(`Testing: ${testName}`);
|
|
248
|
+
|
|
249
|
+
if (testName.includes('node')) {
|
|
250
|
+
const result = await scanner.scanForCLI('node');
|
|
251
|
+
if (result.available) {
|
|
252
|
+
console.log(`[PASS] ${testName}`);
|
|
253
|
+
passed++;
|
|
254
|
+
} else {
|
|
255
|
+
console.log(`[FAIL] ${testName} - Node not available`);
|
|
256
|
+
failed++;
|
|
257
|
+
}
|
|
258
|
+
} else if (testName.includes('npm')) {
|
|
259
|
+
const result = await scanner.scanForCLI('npm');
|
|
260
|
+
if (result.available) {
|
|
261
|
+
console.log(`[PASS] ${testName}`);
|
|
262
|
+
passed++;
|
|
263
|
+
} else {
|
|
264
|
+
console.log(`[FAIL] ${testName} - NPM not available`);
|
|
265
|
+
failed++;
|
|
266
|
+
}
|
|
267
|
+
} else if (testName.includes('non-existent')) {
|
|
268
|
+
try {
|
|
269
|
+
await scanner.scanForCLI('non-existent-cli-12345');
|
|
270
|
+
console.log(`[FAIL] ${testName} - Should have thrown error`);
|
|
271
|
+
failed++;
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.log(`[PASS] ${testName}`);
|
|
274
|
+
passed++;
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
console.log(`[SKIP] ${testName} - Implementation needed`);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
} catch (error) {
|
|
281
|
+
console.log(`[ERROR] ${testName} - ${error.message}`);
|
|
282
|
+
failed++;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
console.log(`\nTest Results: ${passed} passed, ${failed} failed`);
|
|
287
|
+
console.log('Implementation needed for full TDD approach.');
|
|
288
|
+
})();
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
module.exports = CLIScanner;
|
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TDD: Cross-CLI Executor Unit Tests
|
|
3
|
+
* 测试驱动开发 - 先写测试,再写实现
|
|
4
|
+
* 使用ANSI编码,无Unicode字符,Node.js优先
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const assert = require('assert');
|
|
8
|
+
const EventEmitter = require('events');
|
|
9
|
+
|
|
10
|
+
// 测试目标类 - 尚未实现
|
|
11
|
+
class CrossCLIExecutor extends EventEmitter {
|
|
12
|
+
constructor() {
|
|
13
|
+
super();
|
|
14
|
+
this.executionHistory = [];
|
|
15
|
+
this.activeExecutions = new Map();
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async executeCrossCLI(sourceCLI, targetCLI, task, options = {}) {
|
|
19
|
+
throw new Error('Not implemented yet - TDD approach');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
async executeCommand(command, args, options = {}) {
|
|
23
|
+
throw new Error('Not implemented yet - TDD approach');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
validateExecutionParams(sourceCLI, targetCLI, task) {
|
|
27
|
+
throw new Error('Not implemented yet - TDD approach');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
getExecutionHistory() {
|
|
31
|
+
return this.executionHistory;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getActiveExecutions() {
|
|
35
|
+
return Array.from(this.activeExecutions.values());
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
describe('Cross-CLI Executor Unit Tests - ANSI Encoding', () => {
|
|
40
|
+
let executor;
|
|
41
|
+
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
executor = new CrossCLIExecutor();
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
describe('Parameter Validation', () => {
|
|
47
|
+
it('should validate correct execution parameters', () => {
|
|
48
|
+
const result = executor.validateExecutionParams('claude', 'gemini', 'translate this text');
|
|
49
|
+
|
|
50
|
+
assert.strictEqual(result.valid, true);
|
|
51
|
+
assert.strictEqual(result.sourceCLI, 'claude');
|
|
52
|
+
assert.strictEqual(result.targetCLI, 'gemini');
|
|
53
|
+
assert.strictEqual(result.task, 'translate this text');
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it('should reject empty source CLI', () => {
|
|
57
|
+
const result = executor.validateExecutionParams('', 'gemini', 'test task');
|
|
58
|
+
|
|
59
|
+
assert.strictEqual(result.valid, false);
|
|
60
|
+
assert.ok(result.error.includes('source CLI'));
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it('should reject empty target CLI', () => {
|
|
64
|
+
const result = executor.validateExecutionParams('claude', '', 'test task');
|
|
65
|
+
|
|
66
|
+
assert.strictEqual(result.valid, false);
|
|
67
|
+
assert.ok(result.error.includes('target CLI'));
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('should reject empty task', () => {
|
|
71
|
+
const result = executor.validateExecutionParams('claude', 'gemini', '');
|
|
72
|
+
|
|
73
|
+
assert.strictEqual(result.valid, false);
|
|
74
|
+
assert.ok(result.error.includes('task'));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should reject same source and target CLI', () => {
|
|
78
|
+
const result = executor.validateExecutionParams('claude', 'claude', 'test task');
|
|
79
|
+
|
|
80
|
+
assert.strictEqual(result.valid, false);
|
|
81
|
+
assert.ok(result.error.includes('same'));
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
describe('Command Execution', () => {
|
|
86
|
+
it('should execute node --version successfully', async () => {
|
|
87
|
+
const result = await executor.executeCommand('node', ['--version'], {
|
|
88
|
+
timeout: 5000
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
assert.strictEqual(result.success, true);
|
|
92
|
+
assert.ok(result.stdout);
|
|
93
|
+
assert.strictEqual(result.exitCode, 0);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('should execute npm --version successfully', async () => {
|
|
97
|
+
const result = await executor.executeCommand('npm', ['--version'], {
|
|
98
|
+
timeout: 5000
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
assert.strictEqual(result.success, true);
|
|
102
|
+
assert.ok(result.stdout);
|
|
103
|
+
assert.strictEqual(result.exitCode, 0);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('should handle non-existent command gracefully', async () => {
|
|
107
|
+
const result = await executor.executeCommand('non-existent-command-12345', [], {
|
|
108
|
+
timeout: 3000
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
assert.strictEqual(result.success, false);
|
|
112
|
+
assert.ok(result.error);
|
|
113
|
+
assert.ok(result.error.includes('not found') || result.error.includes('ENOENT'));
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it('should handle command timeout', async () => {
|
|
117
|
+
const startTime = Date.now();
|
|
118
|
+
const result = await executor.executeCommand('node', ['-e', 'setTimeout(() => {}, 10000)'], {
|
|
119
|
+
timeout: 2000
|
|
120
|
+
});
|
|
121
|
+
const elapsed = Date.now() - startTime;
|
|
122
|
+
|
|
123
|
+
assert.strictEqual(result.success, false);
|
|
124
|
+
assert.ok(elapsed < 5000); // 应该在超时时间内返回
|
|
125
|
+
assert.ok(result.error.includes('timeout') || result.error.includes('killed'));
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it('should capture stderr output', async () => {
|
|
129
|
+
const result = await executor.executeCommand('node', ['-e', 'console.error("error message")'], {
|
|
130
|
+
timeout: 5000
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
assert.strictEqual(result.success, true);
|
|
134
|
+
assert.ok(result.stderr.includes('error message'));
|
|
135
|
+
});
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
describe('Cross-CLI Execution', () => {
|
|
139
|
+
it('should execute simple claude to gemini translation', async () => {
|
|
140
|
+
// Mock实际调用,因为真实CLI可能未安装
|
|
141
|
+
const result = await executor.executeCrossCLI('claude', 'gemini', 'translate "hello world" to chinese', {
|
|
142
|
+
dryRun: true, // 干运行模式
|
|
143
|
+
timeout: 5000
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
assert.strictEqual(result.success, true);
|
|
147
|
+
assert.strictEqual(result.sourceCLI, 'claude');
|
|
148
|
+
assert.strictEqual(result.targetCLI, 'gemini');
|
|
149
|
+
assert.ok(result.executionId);
|
|
150
|
+
assert.ok(typeof result.executionTime === 'number');
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it('should handle execution errors gracefully', async () => {
|
|
154
|
+
const result = await executor.executeCrossCLI('non-existent', 'gemini', 'test task', {
|
|
155
|
+
timeout: 3000
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
assert.strictEqual(result.success, false);
|
|
159
|
+
assert.ok(result.error);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('should prevent duplicate executions', async () => {
|
|
163
|
+
const task = 'test duplicate task';
|
|
164
|
+
const options = {
|
|
165
|
+
preventDuplicate: true,
|
|
166
|
+
timeout: 5000
|
|
167
|
+
};
|
|
168
|
+
|
|
169
|
+
const result1 = await executor.executeCrossCLI('claude', 'gemini', task, options);
|
|
170
|
+
const result2 = await executor.executeCrossCLI('claude', 'gemini', task, options);
|
|
171
|
+
|
|
172
|
+
assert.strictEqual(result1.success, true);
|
|
173
|
+
assert.strictEqual(result2.duplicate, true);
|
|
174
|
+
assert.strictEqual(result2.executionId, result1.executionId);
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
describe('Execution History Management', () => {
|
|
179
|
+
it('should track execution history', async () => {
|
|
180
|
+
await executor.executeCommand('node', ['--version']);
|
|
181
|
+
|
|
182
|
+
const history = executor.getExecutionHistory();
|
|
183
|
+
assert.ok(history.length > 0);
|
|
184
|
+
|
|
185
|
+
const lastExecution = history[history.length - 1];
|
|
186
|
+
assert.strictEqual(lastExecution.command, 'node');
|
|
187
|
+
assert.strictEqual(lastExecution.success, true);
|
|
188
|
+
assert.ok(lastExecution.timestamp);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it('should track both successful and failed executions', async () => {
|
|
192
|
+
await executor.executeCommand('node', ['--version']);
|
|
193
|
+
await executor.executeCommand('non-existent-command', []);
|
|
194
|
+
|
|
195
|
+
const history = executor.getExecutionHistory();
|
|
196
|
+
assert.ok(history.length >= 2);
|
|
197
|
+
|
|
198
|
+
const results = history.map(h => h.success);
|
|
199
|
+
assert.ok(results.includes(true));
|
|
200
|
+
assert.ok(results.includes(false));
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it('should limit history size', async () => {
|
|
204
|
+
// 执行多个命令
|
|
205
|
+
for (let i = 0; i < 150; i++) {
|
|
206
|
+
await executor.executeCommand('node', ['--version']);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const history = executor.getExecutionHistory();
|
|
210
|
+
assert.ok(history.length <= 100); // 应该有历史大小限制
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe('Active Execution Tracking', () => {
|
|
215
|
+
it('should track active executions', async () => {
|
|
216
|
+
const executionPromise = executor.executeCommand('node', ['-e', 'setTimeout(() => {}, 2000)'], {
|
|
217
|
+
timeout: 5000
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// 立即检查活跃执行
|
|
221
|
+
const activeExecutions = executor.getActiveExecutions();
|
|
222
|
+
assert.ok(activeExecutions.length > 0);
|
|
223
|
+
|
|
224
|
+
const execution = activeExecutions[0];
|
|
225
|
+
assert.strictEqual(execution.command, 'node');
|
|
226
|
+
assert.strictEqual(execution.status, 'running');
|
|
227
|
+
|
|
228
|
+
await executionPromise;
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
it('should clean up completed executions', async () => {
|
|
232
|
+
await executor.executeCommand('node', ['--version']);
|
|
233
|
+
|
|
234
|
+
const activeExecutions = executor.getActiveExecutions();
|
|
235
|
+
const completedExecutions = activeExecutions.filter(e => e.status === 'completed');
|
|
236
|
+
assert.strictEqual(completedExecutions.length, 0);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe('Event Emission', () => {
|
|
241
|
+
it('should emit execution start event', (done) => {
|
|
242
|
+
executor.on('execution:start', (data) => {
|
|
243
|
+
assert.strictEqual(data.command, 'node');
|
|
244
|
+
assert.ok(data.executionId);
|
|
245
|
+
done();
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
executor.executeCommand('node', ['--version']);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should emit execution complete event', (done) => {
|
|
252
|
+
executor.on('execution:complete', (data) => {
|
|
253
|
+
assert.strictEqual(data.command, 'node');
|
|
254
|
+
assert.strictEqual(data.success, true);
|
|
255
|
+
done();
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
executor.executeCommand('node', ['--version']);
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should emit execution error event', (done) => {
|
|
262
|
+
executor.on('execution:error', (data) => {
|
|
263
|
+
assert.strictEqual(data.command, 'non-existent-command');
|
|
264
|
+
assert.strictEqual(data.success, false);
|
|
265
|
+
done();
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
executor.executeCommand('non-existent-command', []);
|
|
269
|
+
});
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
describe('Performance Tests', () => {
|
|
273
|
+
it('should execute multiple commands concurrently', async () => {
|
|
274
|
+
const commands = [
|
|
275
|
+
['node', ['--version']],
|
|
276
|
+
['npm', ['--version']],
|
|
277
|
+
['node', ['-e', 'console.log("test")']]
|
|
278
|
+
];
|
|
279
|
+
|
|
280
|
+
const startTime = Date.now();
|
|
281
|
+
const results = await Promise.all(
|
|
282
|
+
commands.map(([cmd, args]) => executor.executeCommand(cmd, args))
|
|
283
|
+
);
|
|
284
|
+
const elapsed = Date.now() - startTime;
|
|
285
|
+
|
|
286
|
+
assert.strictEqual(results.length, 3);
|
|
287
|
+
assert.ok(results.every(r => r.success));
|
|
288
|
+
assert.ok(elapsed < 10000); // 应该在10秒内完成并发执行
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
it('should handle memory usage efficiently', async () => {
|
|
292
|
+
const initialMemory = process.memoryUsage();
|
|
293
|
+
|
|
294
|
+
// 执行多个命令
|
|
295
|
+
for (let i = 0; i < 50; i++) {
|
|
296
|
+
await executor.executeCommand('node', ['--version']);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const finalMemory = process.memoryUsage();
|
|
300
|
+
const memoryIncrease = finalMemory.heapUsed - initialMemory.heapUsed;
|
|
301
|
+
|
|
302
|
+
// 内存增长应该控制在合理范围内
|
|
303
|
+
assert.ok(memoryIncrease < 50 * 1024 * 1024); // 小于50MB
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
describe('Platform Specific Tests', () => {
|
|
308
|
+
it('should work on Windows platform', async () => {
|
|
309
|
+
if (process.platform === 'win32') {
|
|
310
|
+
const result = await executor.executeCommand('cmd', ['/c', 'echo test'], {
|
|
311
|
+
timeout: 5000
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
assert.strictEqual(result.success, true);
|
|
315
|
+
assert.ok(result.stdout.includes('test'));
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
it('should work on Unix-like platforms', async () => {
|
|
320
|
+
if (process.platform !== 'win32') {
|
|
321
|
+
const result = await executor.executeCommand('echo', ['test'], {
|
|
322
|
+
timeout: 5000
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
assert.strictEqual(result.success, true);
|
|
326
|
+
assert.ok(result.stdout.includes('test'));
|
|
327
|
+
}
|
|
328
|
+
});
|
|
329
|
+
});
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// 运行测试
|
|
333
|
+
if (require.main === module) {
|
|
334
|
+
console.log('Running Cross-CLI Executor Unit Tests...');
|
|
335
|
+
|
|
336
|
+
(async () => {
|
|
337
|
+
const executor = new CrossCLIExecutor();
|
|
338
|
+
let passed = 0;
|
|
339
|
+
let failed = 0;
|
|
340
|
+
|
|
341
|
+
const tests = [
|
|
342
|
+
async () => {
|
|
343
|
+
console.log('Testing parameter validation...');
|
|
344
|
+
const result = executor.validateExecutionParams('claude', 'gemini', 'test task');
|
|
345
|
+
if (result.valid) {
|
|
346
|
+
console.log('[PASS] Parameter validation');
|
|
347
|
+
return true;
|
|
348
|
+
}
|
|
349
|
+
return false;
|
|
350
|
+
},
|
|
351
|
+
async () => {
|
|
352
|
+
console.log('Testing command execution...');
|
|
353
|
+
try {
|
|
354
|
+
const result = await executor.executeCommand('node', ['--version'], { timeout: 3000 });
|
|
355
|
+
if (result.success) {
|
|
356
|
+
console.log('[PASS] Command execution');
|
|
357
|
+
return true;
|
|
358
|
+
}
|
|
359
|
+
} catch (error) {
|
|
360
|
+
console.log(`[FAIL] Command execution: ${error.message}`);
|
|
361
|
+
}
|
|
362
|
+
return false;
|
|
363
|
+
},
|
|
364
|
+
async () => {
|
|
365
|
+
console.log('Testing error handling...');
|
|
366
|
+
try {
|
|
367
|
+
const result = await executor.executeCommand('non-existent-command-12345', [], { timeout: 2000 });
|
|
368
|
+
if (!result.success) {
|
|
369
|
+
console.log('[PASS] Error handling');
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
} catch (error) {
|
|
373
|
+
console.log('[PASS] Error handling (exception thrown)');
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
];
|
|
379
|
+
|
|
380
|
+
for (const test of tests) {
|
|
381
|
+
try {
|
|
382
|
+
const result = await test();
|
|
383
|
+
if (result) {
|
|
384
|
+
passed++;
|
|
385
|
+
} else {
|
|
386
|
+
failed++;
|
|
387
|
+
}
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.log(`[ERROR] Test failed: ${error.message}`);
|
|
390
|
+
failed++;
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
console.log(`\nTest Results: ${passed} passed, ${failed} failed`);
|
|
395
|
+
console.log('Implementation needed for full TDD approach.');
|
|
396
|
+
})();
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
module.exports = CrossCLIExecutor;
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Claude CLI 适配器包
|
|
3
|
-
|
|
4
|
-
基于 Claude CLI 官方 Hook 系统的原生集成
|
|
5
|
-
支持多种实现方式,全部无抽象层
|
|
6
|
-
"""
|
|
7
|
-
|
|
8
|
-
from .standalone_claude_adapter import get_standalone_claude_adapter, StandaloneClaudeAdapter
|
|
9
|
-
|
|
10
|
-
# 向后兼容的别名
|
|
11
|
-
ClaudeHookAdapter = StandaloneClaudeAdapter
|
|
12
|
-
|
|
13
|
-
__all__ = ['StandaloneClaudeAdapter', 'get_standalone_claude_adapter', 'ClaudeHookAdapter']
|