stigmergy 1.3.8 → 1.3.9

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stigmergy",
3
- "version": "1.3.8",
3
+ "version": "1.3.9",
4
4
  "description": "Stigmergy CLI - Multi-Agents Cross-AI CLI Tools Collaboration System",
5
5
  "main": "src/index.js",
6
6
  "bin": {
package/src/utils.js CHANGED
@@ -699,7 +699,14 @@ async function executeCommand(command, args = [], options = {}) {
699
699
  });
700
700
  }
701
701
 
702
+ let timeoutId = null;
703
+
702
704
  child.on('close', (code) => {
705
+ // Clear timeout if it exists
706
+ if (timeoutId) {
707
+ clearTimeout(timeoutId);
708
+ }
709
+
703
710
  resolve({
704
711
  code,
705
712
  stdout,
@@ -709,6 +716,11 @@ async function executeCommand(command, args = [], options = {}) {
709
716
  });
710
717
 
711
718
  child.on('error', (error) => {
719
+ // Clear timeout if it exists
720
+ if (timeoutId) {
721
+ clearTimeout(timeoutId);
722
+ }
723
+
712
724
  // Provide more detailed error information
713
725
  let errorMessage = error.message;
714
726
  if (error.code === 'ENOENT') {
@@ -729,7 +741,7 @@ async function executeCommand(command, args = [], options = {}) {
729
741
 
730
742
  // Handle timeout
731
743
  if (opts.timeout) {
732
- setTimeout(() => {
744
+ timeoutId = setTimeout(() => {
733
745
  child.kill();
734
746
  reject({
735
747
  error: new Error('Command timeout'),
@@ -0,0 +1,284 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Edge Cases and Error Handling Tests: Smart Routing
5
+ * Tests edge cases and error handling scenarios for the SmartRouter class
6
+ */
7
+
8
+ const SmartRouter = require('../src/core/smart_router');
9
+
10
+ // Mock CLI_TOOLS to avoid actual CLI detection during testing
11
+ jest.mock('../src/core/cli_tools', () => ({
12
+ CLI_TOOLS: {
13
+ claude: { name: 'Claude CLI', command: 'claude', version: 'claude --version' },
14
+ qwen: { name: 'Qwen CLI', command: 'qwen', version: 'qwen --version' },
15
+ gemini: { name: 'Gemini CLI', command: 'gemini', version: 'gemini --version' },
16
+ iflow: { name: 'iFlow CLI', command: 'iflow', version: 'iflow --version' },
17
+ codebuddy: { name: 'CodeBuddy CLI', command: 'codebuddy', version: 'codebuddy --version' },
18
+ codex: { name: 'Codex CLI', command: 'codex', version: 'codex --version' },
19
+ qodercli: { name: 'QoderCLI', command: 'qodercli', version: 'qodercli --version' },
20
+ copilot: { name: 'Copilot CLI', command: 'copilot', version: 'copilot --version' },
21
+ kode: { name: 'Kode CLI', command: 'kode', version: 'kode --version' }
22
+ },
23
+ validateCLITool: jest.fn().mockReturnValue(true)
24
+ }));
25
+
26
+ // Mock CLIHelpAnalyzer
27
+ jest.mock('../src/core/cli_help_analyzer', () => {
28
+ return jest.fn().mockImplementation(() => {
29
+ return {
30
+ setCLITools: jest.fn(),
31
+ initialize: jest.fn().mockResolvedValue(undefined),
32
+ analyzeCLI: jest.fn().mockResolvedValue({
33
+ success: true,
34
+ cliName: 'test-cli',
35
+ patterns: {
36
+ commands: [],
37
+ subcommands: []
38
+ }
39
+ }),
40
+ loadPersistentConfig: jest.fn().mockResolvedValue({
41
+ failedAttempts: {},
42
+ cliPatterns: {}
43
+ }),
44
+ getCachedAnalysis: jest.fn().mockResolvedValue(null),
45
+ isCacheExpired: jest.fn().mockReturnValue(false),
46
+ generateOptimizedCall: jest.fn().mockReturnValue({
47
+ optimizedPrompt: 'test prompt'
48
+ }),
49
+ getAgentSkillCompatibilityScore: jest.fn().mockReturnValue({ score: 0.5, reasons: ['test'] }),
50
+ getEnhancedCLIPattern: jest.fn().mockResolvedValue({})
51
+ };
52
+ });
53
+ });
54
+
55
+ describe('Smart Routing Edge Cases and Error Handling', () => {
56
+ let router;
57
+
58
+ beforeEach(async () => {
59
+ router = new SmartRouter();
60
+ await router.initialize();
61
+ });
62
+
63
+ test('should handle invalid input types gracefully', async () => {
64
+ // Test with numbers
65
+ let result = await router.smartRoute(123);
66
+ expect(result.tool).toBe('claude'); // Should default to default tool
67
+ expect(result.prompt).toBe('123');
68
+
69
+ // Test with objects
70
+ result = await router.smartRoute({ message: 'test' });
71
+ expect(result.tool).toBe('claude');
72
+ expect(result.prompt).toBe('[object Object]');
73
+
74
+ // Test with arrays
75
+ result = await router.smartRoute(['test', 'array']);
76
+ expect(result.tool).toBe('claude');
77
+ expect(result.prompt).toBe('test,array');
78
+ });
79
+
80
+ test('should handle very long input strings', async () => {
81
+ const longInput = 'a'.repeat(10000) + ' use claude to analyze this';
82
+ const result = await router.smartRoute(longInput);
83
+ expect(result.tool).toBe('claude');
84
+ // The prompt should contain the meaningful part of the input
85
+ expect(result.prompt).toContain('analyze this');
86
+ });
87
+
88
+ test('should handle input with special characters and unicode', async () => {
89
+ const specialInput = '使用claude分析这段包含特殊字符的代码: !@#$%^&*()中文测试';
90
+ const result = await router.smartRoute(specialInput);
91
+ expect(result.tool).toBe('claude');
92
+ expect(result.prompt).toBe('分析这段包含特殊字符的代码: !@#$%^&*()中文测试');
93
+ });
94
+
95
+ test('should handle multiple tool mentions and choose appropriately', async () => {
96
+ const input = 'use claude and qwen and gemini to analyze this code';
97
+ const result = await router.smartRoute(input);
98
+ // Should pick the first matching tool
99
+ expect(['claude', 'qwen', 'gemini']).toContain(result.tool);
100
+ expect(result.prompt).toBeDefined();
101
+ });
102
+
103
+ test('should handle conflicting routing patterns', async () => {
104
+ const input = 'please use claude to write code with qwen';
105
+ const result = await router.smartRoute(input);
106
+ // Should handle the primary routing intent
107
+ expect(['claude', 'qwen']).toContain(result.tool);
108
+ expect(result.prompt).toBeDefined();
109
+ });
110
+
111
+ test('should handle non-existent tools gracefully', async () => {
112
+ // Test with a tool that doesn't exist in the system
113
+ const input = 'use non-existent-tool to do something';
114
+ const result = await router.smartRoute(input);
115
+ // Should default to default tool
116
+ expect(result.tool).toBe('claude');
117
+ expect(result.prompt).toBe('use non-existent-tool to do something');
118
+ });
119
+
120
+ test('should handle tools with similar names correctly', async () => {
121
+ // Test tools that might have similar names or keywords
122
+ const input = 'use codex and codebuddy to help with code';
123
+ const result = await router.smartRoute(input);
124
+ // Should match the first appropriate tool
125
+ expect(['codex', 'codebuddy']).toContain(result.tool);
126
+ });
127
+
128
+ test('should handle routing when CLI analysis fails', async () => {
129
+ // Mock a failure in CLI analysis
130
+ const originalGetOptimizedCLIPattern = router.getOptimizedCLIPattern;
131
+ router.getOptimizedCLIPattern = jest.fn().mockRejectedValue(new Error('CLI analysis failed'));
132
+
133
+ try {
134
+ const result = await router.smartRoute('analyze this code with claude');
135
+ // Should still return a valid result even if CLI analysis fails
136
+ expect(result.tool).toBe('claude');
137
+ expect(result.prompt).toBe('analyze this code with claude');
138
+ } finally {
139
+ // Restore original method
140
+ router.getOptimizedCLIPattern = originalGetOptimizedCLIPattern;
141
+ }
142
+ });
143
+
144
+ test('should handle routing when validation fails', async () => {
145
+ // Mock a validation failure
146
+ const originalValidateCLITool = require('../src/core/cli_tools').validateCLITool;
147
+ require('../src/core/cli_tools').validateCLITool.mockReturnValue(false);
148
+
149
+ try {
150
+ const result = await router.smartRoute('use claude to analyze');
151
+ // Should still work with default fallback
152
+ expect(result.tool).toBe('claude');
153
+ } finally {
154
+ // Restore original validation
155
+ require('../src/core/cli_tools').validateCLITool.mockReturnValue(true);
156
+ }
157
+ });
158
+
159
+ test('should handle enhanced routing when compatibility scoring fails', async () => {
160
+ const originalGetAgentSkillCompatibilityScore = router.getAgentSkillCompatibilityScore;
161
+
162
+ // Mock a failure in compatibility scoring
163
+ router.getAgentSkillCompatibilityScore = jest.fn().mockRejectedValue(new Error('Scoring failed'));
164
+
165
+ try {
166
+ const result = await router.smartRouteEnhanced('use claude to analyze');
167
+ // Should still return a result with fallback scoring
168
+ expect(result.tool).toBe('claude');
169
+ expect(result.prompt).toBe('use claude to analyze');
170
+ expect(result.confidence).toBeDefined();
171
+ } finally {
172
+ // Restore original method
173
+ router.getAgentSkillCompatibilityScore = originalGetAgentSkillCompatibilityScore;
174
+ }
175
+ });
176
+
177
+ test('should handle fallback routing generation properly', async () => {
178
+ // Test the fallback generation method directly
179
+ const primaryRoute = { tool: 'claude', prompt: 'analyze this code' };
180
+ const error = new Error('Primary route failed');
181
+
182
+ const fallbacks = await router.generateFallbackRoutes(primaryRoute, error);
183
+
184
+ // Should return an array of fallback routes
185
+ expect(Array.isArray(fallbacks)).toBe(true);
186
+
187
+ // Each fallback should have the required properties
188
+ for (const fallback of fallbacks) {
189
+ expect(fallback.tool).toBeDefined();
190
+ expect(fallback.prompt).toBe(primaryRoute.prompt);
191
+ expect(fallback.confidence).toBeDefined();
192
+ expect(fallback.compatibility).toBeDefined();
193
+ }
194
+ });
195
+
196
+ test('should handle execution with multiple retries', async () => {
197
+ // Test the executeEnhancedRoute method with retry logic
198
+ // This is a complex test that verifies the retry mechanism works
199
+ const originalSmartRouteEnhanced = router.smartRouteEnhanced;
200
+
201
+ // Mock the method to fail the first time and succeed the second time
202
+ let callCount = 0;
203
+ router.smartRouteEnhanced = jest.fn().mockImplementation((input) => {
204
+ callCount++;
205
+ if (callCount === 1) {
206
+ throw new Error('First attempt failed');
207
+ }
208
+ return originalSmartRouteEnhanced.call(router, input);
209
+ });
210
+
211
+ try {
212
+ const result = await router.executeEnhancedRoute('use claude to analyze');
213
+ // Should succeed after retry
214
+ expect(result.tool).toBe('claude');
215
+ } finally {
216
+ // Restore original method
217
+ router.smartRouteEnhanced = originalSmartRouteEnhanced;
218
+ }
219
+ });
220
+
221
+ test('should handle cache expiration properly', async () => {
222
+ // Test the isRecentFailure method with various timestamps
223
+ const now = new Date();
224
+ const oneHourAgo = new Date(now.getTime() - 60 * 60 * 1000); // Exactly 1 hour ago
225
+ const fiftyNineMinsAgo = new Date(now.getTime() - 59 * 60 * 1000); // 59 minutes ago
226
+ const twoHoursAgo = new Date(now.getTime() - 2 * 60 * 60 * 1000); // 2 hours ago
227
+
228
+ // 59 minutes ago should be considered recent (less than 1 hour)
229
+ expect(router.isRecentFailure(fiftyNineMinsAgo.toISOString())).toBe(true);
230
+
231
+ // 1 hour ago should not be considered recent (equal to 1 hour)
232
+ expect(router.isRecentFailure(oneHourAgo.toISOString())).toBe(false);
233
+
234
+ // 2 hours ago should not be considered recent
235
+ expect(router.isRecentFailure(twoHoursAgo.toISOString())).toBe(false);
236
+ });
237
+
238
+ test('should handle unusual input patterns', async () => {
239
+ const unusualInputs = [
240
+ 'CLAUDE analyze this', // Uppercase
241
+ ' use claude to analyze ', // Extra spaces
242
+ 'use\nclaude\nto\nanalyze', // Newlines
243
+ 'use\tclaude\tto\tanalyze', // Tabs
244
+ 'use claude to analyze', // Multiple spaces
245
+ 'claude\'s analysis needed', // Possessive form
246
+ 'about claude and his analysis', // Reference to tool
247
+ ];
248
+
249
+ for (const input of unusualInputs) {
250
+ const result = await router.smartRoute(input);
251
+ // Should handle all these inputs gracefully
252
+ expect(result.tool).toBeDefined();
253
+ expect(result.prompt).toBeDefined();
254
+ }
255
+ });
256
+
257
+ test('should handle empty or invalid CLI patterns', async () => {
258
+ const originalGetOptimizedCLIPattern = router.getOptimizedCLIPattern;
259
+
260
+ // Mock to return null/empty patterns
261
+ router.getOptimizedCLIPattern = jest.fn().mockResolvedValue(null);
262
+
263
+ try {
264
+ const result = await router.smartRoute('use claude to analyze');
265
+ // Should still work even with null CLI pattern
266
+ expect(result.tool).toBe('claude');
267
+ } finally {
268
+ // Restore original method
269
+ router.getOptimizedCLIPattern = originalGetOptimizedCLIPattern;
270
+ }
271
+ });
272
+
273
+ test('should handle complex nested routing scenarios', async () => {
274
+ // Test complex scenario with multiple potential matches
275
+ const complexInput = 'Please use Claude (the best AI) to analyze this code, but if Claude is not available, try Qwen or Gemini';
276
+ const result = await router.smartRoute(complexInput);
277
+
278
+ // Should identify the primary intent (Claude)
279
+ expect(['claude', 'qwen', 'gemini']).toContain(result.tool);
280
+ expect(result.prompt).toBeDefined();
281
+ });
282
+ });
283
+
284
+ console.log('Smart Routing Edge Cases and Error Handling Tests - All tests defined and ready to run');