stigmergy 1.1.6 → 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 +15 -7
- package/scripts/build.js +74 -74
- package/scripts/post-deployment-config.js +296 -296
- package/scripts/preinstall-check.js +173 -111
- 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 +792 -67
- package/src/core/cache_cleaner.js +767 -0
- 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 -0
- package/src/core/enhanced_uninstaller.js +638 -0
- package/src/core/error_handler.js +406 -406
- package/src/core/installer.js +816 -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 +420 -0
- 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/calculator.test.js +0 -215
- package/test/collision-test.js +0 -26
- 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/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/simple-iflow-hook-test.js +0 -137
- 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
|
@@ -1,406 +1,406 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Centralized Error Handling Utility for Stigmergy CLI
|
|
3
|
-
* Provides consistent error handling, logging, and reporting across the application
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
const fs = require('fs/promises');
|
|
7
|
-
const path = require('path');
|
|
8
|
-
const os = require('os');
|
|
9
|
-
const chalk = require('chalk');
|
|
10
|
-
|
|
11
|
-
// Error types enumeration
|
|
12
|
-
const ERROR_TYPES = {
|
|
13
|
-
VALIDATION: 'VALIDATION_ERROR',
|
|
14
|
-
NETWORK: 'NETWORK_ERROR',
|
|
15
|
-
FILE_SYSTEM: 'FILE_SYSTEM_ERROR',
|
|
16
|
-
CLI_TOOL: 'CLI_TOOL_ERROR',
|
|
17
|
-
CONFIGURATION: 'CONFIGURATION_ERROR',
|
|
18
|
-
PERMISSION: 'PERMISSION_ERROR',
|
|
19
|
-
UNKNOWN: 'UNKNOWN_ERROR',
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
// Log levels enumeration
|
|
23
|
-
const LOG_LEVELS = {
|
|
24
|
-
ERROR: 'ERROR',
|
|
25
|
-
WARN: 'WARN',
|
|
26
|
-
INFO: 'INFO',
|
|
27
|
-
DEBUG: 'DEBUG',
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
class StigmergyError extends Error {
|
|
31
|
-
constructor(
|
|
32
|
-
message,
|
|
33
|
-
type = ERROR_TYPES.UNKNOWN,
|
|
34
|
-
code = null,
|
|
35
|
-
details = null,
|
|
36
|
-
) {
|
|
37
|
-
super(message);
|
|
38
|
-
this.name = 'StigmergyError';
|
|
39
|
-
this.type = type;
|
|
40
|
-
this.code = code;
|
|
41
|
-
this.details = details;
|
|
42
|
-
this.timestamp = new Date().toISOString();
|
|
43
|
-
|
|
44
|
-
// Ensure proper stack trace
|
|
45
|
-
if (Error.captureStackTrace) {
|
|
46
|
-
Error.captureStackTrace(this, StigmergyError);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
class ErrorHandler {
|
|
52
|
-
constructor() {
|
|
53
|
-
this.logFile = path.join(os.homedir(), '.stigmergy', 'error.log');
|
|
54
|
-
this.maxLogSize = 10 * 1024 * 1024; // 10MB
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Create a standardized Stigmergy error
|
|
59
|
-
* @param {string} message - Error message
|
|
60
|
-
* @param {string} type - Error type from ERROR_TYPES
|
|
61
|
-
* @param {string|null} code - Error code
|
|
62
|
-
* @param {Object|null} details - Additional error details
|
|
63
|
-
* @returns {StigmergyError}
|
|
64
|
-
*/
|
|
65
|
-
createError(
|
|
66
|
-
message,
|
|
67
|
-
type = ERROR_TYPES.UNKNOWN,
|
|
68
|
-
code = null,
|
|
69
|
-
details = null,
|
|
70
|
-
) {
|
|
71
|
-
return new StigmergyError(message, type, code, details);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Log an error to console and file
|
|
76
|
-
* @param {Error|StigmergyError} error - The error to log
|
|
77
|
-
* @param {string} level - Log level (ERROR, WARN, INFO, DEBUG)
|
|
78
|
-
* @param {string|null} context - Context where error occurred
|
|
79
|
-
*/
|
|
80
|
-
async logError(error, level = LOG_LEVELS.ERROR, context = null) {
|
|
81
|
-
try {
|
|
82
|
-
const timestamp = new Date().toISOString();
|
|
83
|
-
const errorType = error.type || ERROR_TYPES.UNKNOWN;
|
|
84
|
-
const errorCode = error.code || 'NO_CODE';
|
|
85
|
-
const errorMessage = error.message || 'Unknown error';
|
|
86
|
-
|
|
87
|
-
// Format log entry
|
|
88
|
-
const logEntry = {
|
|
89
|
-
timestamp,
|
|
90
|
-
level,
|
|
91
|
-
type: errorType,
|
|
92
|
-
code: errorCode,
|
|
93
|
-
message: errorMessage,
|
|
94
|
-
context,
|
|
95
|
-
stack: error.stack || 'No stack trace available',
|
|
96
|
-
};
|
|
97
|
-
|
|
98
|
-
// Console output with enhanced formatting
|
|
99
|
-
const consoleMessage = `[${timestamp}] [${level}] [${errorType}] ${errorMessage}`;
|
|
100
|
-
const contextMessage = context ? `[CONTEXT] ${context}` : '';
|
|
101
|
-
|
|
102
|
-
switch (level) {
|
|
103
|
-
case LOG_LEVELS.ERROR:
|
|
104
|
-
console.error(chalk.red.bold(consoleMessage));
|
|
105
|
-
if (contextMessage) console.error(chalk.yellow(contextMessage));
|
|
106
|
-
if (error.stack) console.error(chalk.gray(error.stack));
|
|
107
|
-
break;
|
|
108
|
-
case LOG_LEVELS.WARN:
|
|
109
|
-
console.warn(chalk.yellow.bold(consoleMessage));
|
|
110
|
-
if (contextMessage) console.warn(chalk.yellow(contextMessage));
|
|
111
|
-
break;
|
|
112
|
-
case LOG_LEVELS.INFO:
|
|
113
|
-
console.info(chalk.blue(consoleMessage));
|
|
114
|
-
if (contextMessage) console.info(chalk.gray(contextMessage));
|
|
115
|
-
break;
|
|
116
|
-
case LOG_LEVELS.DEBUG:
|
|
117
|
-
console.debug(chalk.gray(consoleMessage));
|
|
118
|
-
if (contextMessage) console.debug(chalk.gray(contextMessage));
|
|
119
|
-
if (error.stack) console.debug(chalk.gray(error.stack));
|
|
120
|
-
break;
|
|
121
|
-
default:
|
|
122
|
-
console.log(chalk.white(consoleMessage));
|
|
123
|
-
if (contextMessage) console.log(chalk.gray(contextMessage));
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// File logging
|
|
127
|
-
await this.writeToFile(logEntry);
|
|
128
|
-
} catch (logError) {
|
|
129
|
-
console.error('[ERROR_HANDLER] Failed to log error:', logError.message);
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Write error to log file
|
|
135
|
-
* @param {Object} logEntry - Formatted log entry
|
|
136
|
-
*/
|
|
137
|
-
async writeToFile(logEntry) {
|
|
138
|
-
try {
|
|
139
|
-
// Ensure log directory exists
|
|
140
|
-
const logDir = path.dirname(this.logFile);
|
|
141
|
-
await fs.mkdir(logDir, { recursive: true });
|
|
142
|
-
|
|
143
|
-
// Check file size and rotate if necessary
|
|
144
|
-
await this.rotateLogFile();
|
|
145
|
-
|
|
146
|
-
// Append to log file
|
|
147
|
-
const logLine = JSON.stringify(logEntry) + '\n';
|
|
148
|
-
await fs.appendFile(this.logFile, logLine, { encoding: 'utf8' });
|
|
149
|
-
} catch (error) {
|
|
150
|
-
// Silent fail to prevent infinite loop
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
/**
|
|
155
|
-
* Rotate log file if it exceeds max size
|
|
156
|
-
*/
|
|
157
|
-
async rotateLogFile() {
|
|
158
|
-
try {
|
|
159
|
-
const stats = await fs.stat(this.logFile);
|
|
160
|
-
if (stats.size > this.maxLogSize) {
|
|
161
|
-
const backupFile = this.logFile + '.old';
|
|
162
|
-
await fs.rename(this.logFile, backupFile);
|
|
163
|
-
}
|
|
164
|
-
} catch (error) {
|
|
165
|
-
// File doesn't exist or other issue, continue
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Handle CLI tool execution errors
|
|
171
|
-
* @param {string} toolName - Name of the CLI tool
|
|
172
|
-
* @param {Error} error - Error that occurred
|
|
173
|
-
* @param {string|null} command - Command that failed
|
|
174
|
-
*/
|
|
175
|
-
async handleCLIError(toolName, error, command = null) {
|
|
176
|
-
const cliError = new StigmergyError(
|
|
177
|
-
`Failed to execute ${toolName}${command ? ` command: ${command}` : ''}`,
|
|
178
|
-
ERROR_TYPES.CLI_TOOL,
|
|
179
|
-
null,
|
|
180
|
-
{
|
|
181
|
-
tool: toolName,
|
|
182
|
-
command,
|
|
183
|
-
originalError: error.message,
|
|
184
|
-
stack: error.stack,
|
|
185
|
-
},
|
|
186
|
-
);
|
|
187
|
-
|
|
188
|
-
await this.logError(cliError, LOG_LEVELS.ERROR, 'CLI_EXECUTION');
|
|
189
|
-
return cliError;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Handle file system errors
|
|
194
|
-
* @param {string} operation - File operation that failed
|
|
195
|
-
* @param {string} filePath - Path of file involved
|
|
196
|
-
* @param {Error} error - Original error
|
|
197
|
-
*/
|
|
198
|
-
async handleFileError(operation, filePath, error) {
|
|
199
|
-
const fileError = new StigmergyError(
|
|
200
|
-
`Failed to ${operation} file: ${filePath}`,
|
|
201
|
-
ERROR_TYPES.FILE_SYSTEM,
|
|
202
|
-
error.code,
|
|
203
|
-
{
|
|
204
|
-
operation,
|
|
205
|
-
filePath,
|
|
206
|
-
originalError: error.message,
|
|
207
|
-
stack: error.stack,
|
|
208
|
-
},
|
|
209
|
-
);
|
|
210
|
-
|
|
211
|
-
await this.logError(fileError, LOG_LEVELS.ERROR, 'FILE_SYSTEM');
|
|
212
|
-
return fileError;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/**
|
|
216
|
-
* Handle network errors
|
|
217
|
-
* @param {string} operation - Network operation that failed
|
|
218
|
-
* @param {string} url - URL involved
|
|
219
|
-
* @param {Error} error - Original error
|
|
220
|
-
*/
|
|
221
|
-
async handleNetworkError(operation, url, error) {
|
|
222
|
-
const networkError = new StigmergyError(
|
|
223
|
-
`Network error during ${operation}: ${url}`,
|
|
224
|
-
ERROR_TYPES.NETWORK,
|
|
225
|
-
error.code,
|
|
226
|
-
{
|
|
227
|
-
operation,
|
|
228
|
-
url,
|
|
229
|
-
originalError: error.message,
|
|
230
|
-
stack: error.stack,
|
|
231
|
-
},
|
|
232
|
-
);
|
|
233
|
-
|
|
234
|
-
await this.logError(networkError, LOG_LEVELS.ERROR, 'NETWORK');
|
|
235
|
-
return networkError;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
/**
|
|
239
|
-
* Handle validation errors
|
|
240
|
-
* @param {string} field - Field that failed validation
|
|
241
|
-
* @param {string} value - Value that failed validation
|
|
242
|
-
* @param {string} reason - Reason for validation failure
|
|
243
|
-
*/
|
|
244
|
-
createValidationError(field, value, reason) {
|
|
245
|
-
const validationError = new StigmergyError(
|
|
246
|
-
`Validation failed for ${field}: ${reason}`,
|
|
247
|
-
ERROR_TYPES.VALIDATION,
|
|
248
|
-
'VALIDATION_FAILED',
|
|
249
|
-
{
|
|
250
|
-
field,
|
|
251
|
-
value,
|
|
252
|
-
reason,
|
|
253
|
-
},
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
return validationError;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
/**
|
|
260
|
-
* Wrap an async function with error handling
|
|
261
|
-
* @param {Function} fn - Async function to wrap
|
|
262
|
-
* @param {string} context - Context for error logging
|
|
263
|
-
* @returns {Function} Wrapped function
|
|
264
|
-
*/
|
|
265
|
-
wrapAsync(fn, context) {
|
|
266
|
-
return async (...args) => {
|
|
267
|
-
try {
|
|
268
|
-
return await fn(...args);
|
|
269
|
-
} catch (error) {
|
|
270
|
-
if (error instanceof StigmergyError) {
|
|
271
|
-
await this.logError(error, LOG_LEVELS.ERROR, context);
|
|
272
|
-
throw error;
|
|
273
|
-
} else {
|
|
274
|
-
const wrappedError = new StigmergyError(
|
|
275
|
-
error.message || 'Unknown error occurred',
|
|
276
|
-
ERROR_TYPES.UNKNOWN,
|
|
277
|
-
null,
|
|
278
|
-
{ originalError: error.message, stack: error.stack },
|
|
279
|
-
);
|
|
280
|
-
await this.logError(wrappedError, LOG_LEVELS.ERROR, context);
|
|
281
|
-
throw wrappedError;
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
};
|
|
285
|
-
}
|
|
286
|
-
|
|
287
|
-
/**
|
|
288
|
-
* Get error statistics from log file
|
|
289
|
-
*/
|
|
290
|
-
async getErrorStats() {
|
|
291
|
-
try {
|
|
292
|
-
const data = await fs.readFile(this.logFile, 'utf8');
|
|
293
|
-
const lines = data.split('\n').filter((line) => line.trim());
|
|
294
|
-
|
|
295
|
-
const stats = {
|
|
296
|
-
totalErrors: lines.length,
|
|
297
|
-
byType: {},
|
|
298
|
-
byLevel: {},
|
|
299
|
-
};
|
|
300
|
-
|
|
301
|
-
for (const line of lines) {
|
|
302
|
-
try {
|
|
303
|
-
const entry = JSON.parse(line);
|
|
304
|
-
stats.byType[entry.type] = (stats.byType[entry.type] || 0) + 1;
|
|
305
|
-
stats.byLevel[entry.level] = (stats.byLevel[entry.level] || 0) + 1;
|
|
306
|
-
} catch (parseError) {
|
|
307
|
-
// Skip malformed entries
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return stats;
|
|
312
|
-
} catch (error) {
|
|
313
|
-
return { totalErrors: 0, byType: {}, byLevel: {} };
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
|
|
317
|
-
/**
|
|
318
|
-
* Generate a formatted error report
|
|
319
|
-
* @param {Object} stats - Error statistics from getErrorStats()
|
|
320
|
-
* @returns {string} Formatted error report
|
|
321
|
-
*/
|
|
322
|
-
generateErrorReport(stats) {
|
|
323
|
-
let report = '\n=== Stigmergy CLI Error Report ===\n';
|
|
324
|
-
report += `Total Errors: ${stats.totalErrors}\n\n`;
|
|
325
|
-
|
|
326
|
-
report += 'Errors by Type:\n';
|
|
327
|
-
for (const [type, count] of Object.entries(stats.byType)) {
|
|
328
|
-
report += ` ${type}: ${count}\n`;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
report += '\nErrors by Level:\n';
|
|
332
|
-
for (const [level, count] of Object.entries(stats.byLevel)) {
|
|
333
|
-
report += ` ${level}: ${count}\n`;
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
report += '==================================\n';
|
|
337
|
-
return report;
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Print error report to console
|
|
342
|
-
*/
|
|
343
|
-
async printErrorReport() {
|
|
344
|
-
const stats = await this.getErrorStats();
|
|
345
|
-
const report = this.generateErrorReport(stats);
|
|
346
|
-
console.log(report);
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Set up global error handlers within the error handler module
|
|
351
|
-
function setupGlobalErrorHandlers() {
|
|
352
|
-
// Only set up handlers if they haven't been set up already
|
|
353
|
-
if (!process.listenerCount('unhandledRejection')) {
|
|
354
|
-
process.on('unhandledRejection', async (reason, promise) => {
|
|
355
|
-
console.error(
|
|
356
|
-
'[FATAL] Global Unhandled Rejection at:',
|
|
357
|
-
promise,
|
|
358
|
-
'reason:',
|
|
359
|
-
reason,
|
|
360
|
-
);
|
|
361
|
-
|
|
362
|
-
// Log the error using our error handler
|
|
363
|
-
const error =
|
|
364
|
-
reason instanceof Error ? reason : new Error(String(reason));
|
|
365
|
-
await errorHandler.logError(
|
|
366
|
-
error,
|
|
367
|
-
LOG_LEVELS.ERROR,
|
|
368
|
-
'global_unhandledRejection',
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
// Exit gracefully after a short delay to allow logging
|
|
372
|
-
setTimeout(() => process.exit(1), 100);
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
if (!process.listenerCount('uncaughtException')) {
|
|
377
|
-
process.on('uncaughtException', async (error) => {
|
|
378
|
-
console.error('[FATAL] Global Uncaught Exception:', error);
|
|
379
|
-
|
|
380
|
-
// Log the error using our error handler
|
|
381
|
-
await errorHandler.logError(
|
|
382
|
-
error,
|
|
383
|
-
LOG_LEVELS.ERROR,
|
|
384
|
-
'global_uncaughtException',
|
|
385
|
-
);
|
|
386
|
-
|
|
387
|
-
// Exit gracefully after a short delay to allow logging
|
|
388
|
-
setTimeout(() => process.exit(1), 100);
|
|
389
|
-
});
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
// Set up global handlers by default
|
|
394
|
-
setupGlobalErrorHandlers();
|
|
395
|
-
|
|
396
|
-
// Export singleton instance
|
|
397
|
-
const errorHandler = new ErrorHandler();
|
|
398
|
-
|
|
399
|
-
module.exports = {
|
|
400
|
-
ErrorHandler,
|
|
401
|
-
errorHandler,
|
|
402
|
-
StigmergyError,
|
|
403
|
-
ERROR_TYPES,
|
|
404
|
-
LOG_LEVELS,
|
|
405
|
-
setupGlobalErrorHandlers,
|
|
406
|
-
};
|
|
1
|
+
/**
|
|
2
|
+
* Centralized Error Handling Utility for Stigmergy CLI
|
|
3
|
+
* Provides consistent error handling, logging, and reporting across the application
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const fs = require('fs/promises');
|
|
7
|
+
const path = require('path');
|
|
8
|
+
const os = require('os');
|
|
9
|
+
const chalk = require('chalk');
|
|
10
|
+
|
|
11
|
+
// Error types enumeration
|
|
12
|
+
const ERROR_TYPES = {
|
|
13
|
+
VALIDATION: 'VALIDATION_ERROR',
|
|
14
|
+
NETWORK: 'NETWORK_ERROR',
|
|
15
|
+
FILE_SYSTEM: 'FILE_SYSTEM_ERROR',
|
|
16
|
+
CLI_TOOL: 'CLI_TOOL_ERROR',
|
|
17
|
+
CONFIGURATION: 'CONFIGURATION_ERROR',
|
|
18
|
+
PERMISSION: 'PERMISSION_ERROR',
|
|
19
|
+
UNKNOWN: 'UNKNOWN_ERROR',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Log levels enumeration
|
|
23
|
+
const LOG_LEVELS = {
|
|
24
|
+
ERROR: 'ERROR',
|
|
25
|
+
WARN: 'WARN',
|
|
26
|
+
INFO: 'INFO',
|
|
27
|
+
DEBUG: 'DEBUG',
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
class StigmergyError extends Error {
|
|
31
|
+
constructor(
|
|
32
|
+
message,
|
|
33
|
+
type = ERROR_TYPES.UNKNOWN,
|
|
34
|
+
code = null,
|
|
35
|
+
details = null,
|
|
36
|
+
) {
|
|
37
|
+
super(message);
|
|
38
|
+
this.name = 'StigmergyError';
|
|
39
|
+
this.type = type;
|
|
40
|
+
this.code = code;
|
|
41
|
+
this.details = details;
|
|
42
|
+
this.timestamp = new Date().toISOString();
|
|
43
|
+
|
|
44
|
+
// Ensure proper stack trace
|
|
45
|
+
if (Error.captureStackTrace) {
|
|
46
|
+
Error.captureStackTrace(this, StigmergyError);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
class ErrorHandler {
|
|
52
|
+
constructor() {
|
|
53
|
+
this.logFile = path.join(os.homedir(), '.stigmergy', 'error.log');
|
|
54
|
+
this.maxLogSize = 10 * 1024 * 1024; // 10MB
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Create a standardized Stigmergy error
|
|
59
|
+
* @param {string} message - Error message
|
|
60
|
+
* @param {string} type - Error type from ERROR_TYPES
|
|
61
|
+
* @param {string|null} code - Error code
|
|
62
|
+
* @param {Object|null} details - Additional error details
|
|
63
|
+
* @returns {StigmergyError}
|
|
64
|
+
*/
|
|
65
|
+
createError(
|
|
66
|
+
message,
|
|
67
|
+
type = ERROR_TYPES.UNKNOWN,
|
|
68
|
+
code = null,
|
|
69
|
+
details = null,
|
|
70
|
+
) {
|
|
71
|
+
return new StigmergyError(message, type, code, details);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Log an error to console and file
|
|
76
|
+
* @param {Error|StigmergyError} error - The error to log
|
|
77
|
+
* @param {string} level - Log level (ERROR, WARN, INFO, DEBUG)
|
|
78
|
+
* @param {string|null} context - Context where error occurred
|
|
79
|
+
*/
|
|
80
|
+
async logError(error, level = LOG_LEVELS.ERROR, context = null) {
|
|
81
|
+
try {
|
|
82
|
+
const timestamp = new Date().toISOString();
|
|
83
|
+
const errorType = error.type || ERROR_TYPES.UNKNOWN;
|
|
84
|
+
const errorCode = error.code || 'NO_CODE';
|
|
85
|
+
const errorMessage = error.message || 'Unknown error';
|
|
86
|
+
|
|
87
|
+
// Format log entry
|
|
88
|
+
const logEntry = {
|
|
89
|
+
timestamp,
|
|
90
|
+
level,
|
|
91
|
+
type: errorType,
|
|
92
|
+
code: errorCode,
|
|
93
|
+
message: errorMessage,
|
|
94
|
+
context,
|
|
95
|
+
stack: error.stack || 'No stack trace available',
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Console output with enhanced formatting
|
|
99
|
+
const consoleMessage = `[${timestamp}] [${level}] [${errorType}] ${errorMessage}`;
|
|
100
|
+
const contextMessage = context ? `[CONTEXT] ${context}` : '';
|
|
101
|
+
|
|
102
|
+
switch (level) {
|
|
103
|
+
case LOG_LEVELS.ERROR:
|
|
104
|
+
console.error(chalk.red.bold(consoleMessage));
|
|
105
|
+
if (contextMessage) console.error(chalk.yellow(contextMessage));
|
|
106
|
+
if (error.stack) console.error(chalk.gray(error.stack));
|
|
107
|
+
break;
|
|
108
|
+
case LOG_LEVELS.WARN:
|
|
109
|
+
console.warn(chalk.yellow.bold(consoleMessage));
|
|
110
|
+
if (contextMessage) console.warn(chalk.yellow(contextMessage));
|
|
111
|
+
break;
|
|
112
|
+
case LOG_LEVELS.INFO:
|
|
113
|
+
console.info(chalk.blue(consoleMessage));
|
|
114
|
+
if (contextMessage) console.info(chalk.gray(contextMessage));
|
|
115
|
+
break;
|
|
116
|
+
case LOG_LEVELS.DEBUG:
|
|
117
|
+
console.debug(chalk.gray(consoleMessage));
|
|
118
|
+
if (contextMessage) console.debug(chalk.gray(contextMessage));
|
|
119
|
+
if (error.stack) console.debug(chalk.gray(error.stack));
|
|
120
|
+
break;
|
|
121
|
+
default:
|
|
122
|
+
console.log(chalk.white(consoleMessage));
|
|
123
|
+
if (contextMessage) console.log(chalk.gray(contextMessage));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// File logging
|
|
127
|
+
await this.writeToFile(logEntry);
|
|
128
|
+
} catch (logError) {
|
|
129
|
+
console.error('[ERROR_HANDLER] Failed to log error:', logError.message);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Write error to log file
|
|
135
|
+
* @param {Object} logEntry - Formatted log entry
|
|
136
|
+
*/
|
|
137
|
+
async writeToFile(logEntry) {
|
|
138
|
+
try {
|
|
139
|
+
// Ensure log directory exists
|
|
140
|
+
const logDir = path.dirname(this.logFile);
|
|
141
|
+
await fs.mkdir(logDir, { recursive: true });
|
|
142
|
+
|
|
143
|
+
// Check file size and rotate if necessary
|
|
144
|
+
await this.rotateLogFile();
|
|
145
|
+
|
|
146
|
+
// Append to log file
|
|
147
|
+
const logLine = JSON.stringify(logEntry) + '\n';
|
|
148
|
+
await fs.appendFile(this.logFile, logLine, { encoding: 'utf8' });
|
|
149
|
+
} catch (error) {
|
|
150
|
+
// Silent fail to prevent infinite loop
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Rotate log file if it exceeds max size
|
|
156
|
+
*/
|
|
157
|
+
async rotateLogFile() {
|
|
158
|
+
try {
|
|
159
|
+
const stats = await fs.stat(this.logFile);
|
|
160
|
+
if (stats.size > this.maxLogSize) {
|
|
161
|
+
const backupFile = this.logFile + '.old';
|
|
162
|
+
await fs.rename(this.logFile, backupFile);
|
|
163
|
+
}
|
|
164
|
+
} catch (error) {
|
|
165
|
+
// File doesn't exist or other issue, continue
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Handle CLI tool execution errors
|
|
171
|
+
* @param {string} toolName - Name of the CLI tool
|
|
172
|
+
* @param {Error} error - Error that occurred
|
|
173
|
+
* @param {string|null} command - Command that failed
|
|
174
|
+
*/
|
|
175
|
+
async handleCLIError(toolName, error, command = null) {
|
|
176
|
+
const cliError = new StigmergyError(
|
|
177
|
+
`Failed to execute ${toolName}${command ? ` command: ${command}` : ''}`,
|
|
178
|
+
ERROR_TYPES.CLI_TOOL,
|
|
179
|
+
null,
|
|
180
|
+
{
|
|
181
|
+
tool: toolName,
|
|
182
|
+
command,
|
|
183
|
+
originalError: error.message,
|
|
184
|
+
stack: error.stack,
|
|
185
|
+
},
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
await this.logError(cliError, LOG_LEVELS.ERROR, 'CLI_EXECUTION');
|
|
189
|
+
return cliError;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Handle file system errors
|
|
194
|
+
* @param {string} operation - File operation that failed
|
|
195
|
+
* @param {string} filePath - Path of file involved
|
|
196
|
+
* @param {Error} error - Original error
|
|
197
|
+
*/
|
|
198
|
+
async handleFileError(operation, filePath, error) {
|
|
199
|
+
const fileError = new StigmergyError(
|
|
200
|
+
`Failed to ${operation} file: ${filePath}`,
|
|
201
|
+
ERROR_TYPES.FILE_SYSTEM,
|
|
202
|
+
error.code,
|
|
203
|
+
{
|
|
204
|
+
operation,
|
|
205
|
+
filePath,
|
|
206
|
+
originalError: error.message,
|
|
207
|
+
stack: error.stack,
|
|
208
|
+
},
|
|
209
|
+
);
|
|
210
|
+
|
|
211
|
+
await this.logError(fileError, LOG_LEVELS.ERROR, 'FILE_SYSTEM');
|
|
212
|
+
return fileError;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Handle network errors
|
|
217
|
+
* @param {string} operation - Network operation that failed
|
|
218
|
+
* @param {string} url - URL involved
|
|
219
|
+
* @param {Error} error - Original error
|
|
220
|
+
*/
|
|
221
|
+
async handleNetworkError(operation, url, error) {
|
|
222
|
+
const networkError = new StigmergyError(
|
|
223
|
+
`Network error during ${operation}: ${url}`,
|
|
224
|
+
ERROR_TYPES.NETWORK,
|
|
225
|
+
error.code,
|
|
226
|
+
{
|
|
227
|
+
operation,
|
|
228
|
+
url,
|
|
229
|
+
originalError: error.message,
|
|
230
|
+
stack: error.stack,
|
|
231
|
+
},
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
await this.logError(networkError, LOG_LEVELS.ERROR, 'NETWORK');
|
|
235
|
+
return networkError;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Handle validation errors
|
|
240
|
+
* @param {string} field - Field that failed validation
|
|
241
|
+
* @param {string} value - Value that failed validation
|
|
242
|
+
* @param {string} reason - Reason for validation failure
|
|
243
|
+
*/
|
|
244
|
+
createValidationError(field, value, reason) {
|
|
245
|
+
const validationError = new StigmergyError(
|
|
246
|
+
`Validation failed for ${field}: ${reason}`,
|
|
247
|
+
ERROR_TYPES.VALIDATION,
|
|
248
|
+
'VALIDATION_FAILED',
|
|
249
|
+
{
|
|
250
|
+
field,
|
|
251
|
+
value,
|
|
252
|
+
reason,
|
|
253
|
+
},
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return validationError;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Wrap an async function with error handling
|
|
261
|
+
* @param {Function} fn - Async function to wrap
|
|
262
|
+
* @param {string} context - Context for error logging
|
|
263
|
+
* @returns {Function} Wrapped function
|
|
264
|
+
*/
|
|
265
|
+
wrapAsync(fn, context) {
|
|
266
|
+
return async (...args) => {
|
|
267
|
+
try {
|
|
268
|
+
return await fn(...args);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
if (error instanceof StigmergyError) {
|
|
271
|
+
await this.logError(error, LOG_LEVELS.ERROR, context);
|
|
272
|
+
throw error;
|
|
273
|
+
} else {
|
|
274
|
+
const wrappedError = new StigmergyError(
|
|
275
|
+
error.message || 'Unknown error occurred',
|
|
276
|
+
ERROR_TYPES.UNKNOWN,
|
|
277
|
+
null,
|
|
278
|
+
{ originalError: error.message, stack: error.stack },
|
|
279
|
+
);
|
|
280
|
+
await this.logError(wrappedError, LOG_LEVELS.ERROR, context);
|
|
281
|
+
throw wrappedError;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Get error statistics from log file
|
|
289
|
+
*/
|
|
290
|
+
async getErrorStats() {
|
|
291
|
+
try {
|
|
292
|
+
const data = await fs.readFile(this.logFile, 'utf8');
|
|
293
|
+
const lines = data.split('\n').filter((line) => line.trim());
|
|
294
|
+
|
|
295
|
+
const stats = {
|
|
296
|
+
totalErrors: lines.length,
|
|
297
|
+
byType: {},
|
|
298
|
+
byLevel: {},
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
for (const line of lines) {
|
|
302
|
+
try {
|
|
303
|
+
const entry = JSON.parse(line);
|
|
304
|
+
stats.byType[entry.type] = (stats.byType[entry.type] || 0) + 1;
|
|
305
|
+
stats.byLevel[entry.level] = (stats.byLevel[entry.level] || 0) + 1;
|
|
306
|
+
} catch (parseError) {
|
|
307
|
+
// Skip malformed entries
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return stats;
|
|
312
|
+
} catch (error) {
|
|
313
|
+
return { totalErrors: 0, byType: {}, byLevel: {} };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Generate a formatted error report
|
|
319
|
+
* @param {Object} stats - Error statistics from getErrorStats()
|
|
320
|
+
* @returns {string} Formatted error report
|
|
321
|
+
*/
|
|
322
|
+
generateErrorReport(stats) {
|
|
323
|
+
let report = '\n=== Stigmergy CLI Error Report ===\n';
|
|
324
|
+
report += `Total Errors: ${stats.totalErrors}\n\n`;
|
|
325
|
+
|
|
326
|
+
report += 'Errors by Type:\n';
|
|
327
|
+
for (const [type, count] of Object.entries(stats.byType)) {
|
|
328
|
+
report += ` ${type}: ${count}\n`;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
report += '\nErrors by Level:\n';
|
|
332
|
+
for (const [level, count] of Object.entries(stats.byLevel)) {
|
|
333
|
+
report += ` ${level}: ${count}\n`;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
report += '==================================\n';
|
|
337
|
+
return report;
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* Print error report to console
|
|
342
|
+
*/
|
|
343
|
+
async printErrorReport() {
|
|
344
|
+
const stats = await this.getErrorStats();
|
|
345
|
+
const report = this.generateErrorReport(stats);
|
|
346
|
+
console.log(report);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
// Set up global error handlers within the error handler module
|
|
351
|
+
function setupGlobalErrorHandlers() {
|
|
352
|
+
// Only set up handlers if they haven't been set up already
|
|
353
|
+
if (!process.listenerCount('unhandledRejection')) {
|
|
354
|
+
process.on('unhandledRejection', async (reason, promise) => {
|
|
355
|
+
console.error(
|
|
356
|
+
'[FATAL] Global Unhandled Rejection at:',
|
|
357
|
+
promise,
|
|
358
|
+
'reason:',
|
|
359
|
+
reason,
|
|
360
|
+
);
|
|
361
|
+
|
|
362
|
+
// Log the error using our error handler
|
|
363
|
+
const error =
|
|
364
|
+
reason instanceof Error ? reason : new Error(String(reason));
|
|
365
|
+
await errorHandler.logError(
|
|
366
|
+
error,
|
|
367
|
+
LOG_LEVELS.ERROR,
|
|
368
|
+
'global_unhandledRejection',
|
|
369
|
+
);
|
|
370
|
+
|
|
371
|
+
// Exit gracefully after a short delay to allow logging
|
|
372
|
+
setTimeout(() => process.exit(1), 100);
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (!process.listenerCount('uncaughtException')) {
|
|
377
|
+
process.on('uncaughtException', async (error) => {
|
|
378
|
+
console.error('[FATAL] Global Uncaught Exception:', error);
|
|
379
|
+
|
|
380
|
+
// Log the error using our error handler
|
|
381
|
+
await errorHandler.logError(
|
|
382
|
+
error,
|
|
383
|
+
LOG_LEVELS.ERROR,
|
|
384
|
+
'global_uncaughtException',
|
|
385
|
+
);
|
|
386
|
+
|
|
387
|
+
// Exit gracefully after a short delay to allow logging
|
|
388
|
+
setTimeout(() => process.exit(1), 100);
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
// Set up global handlers by default
|
|
394
|
+
setupGlobalErrorHandlers();
|
|
395
|
+
|
|
396
|
+
// Export singleton instance
|
|
397
|
+
const errorHandler = new ErrorHandler();
|
|
398
|
+
|
|
399
|
+
module.exports = {
|
|
400
|
+
ErrorHandler,
|
|
401
|
+
errorHandler,
|
|
402
|
+
StigmergyError,
|
|
403
|
+
ERROR_TYPES,
|
|
404
|
+
LOG_LEVELS,
|
|
405
|
+
setupGlobalErrorHandlers,
|
|
406
|
+
};
|