@paulduvall/claude-dev-toolkit 0.0.1-alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,432 @@
1
+ /**
2
+ * Error Handler Utilities
3
+ *
4
+ * Shared error handling utilities to eliminate duplication and provide
5
+ * consistent error handling across the application.
6
+ *
7
+ * Features:
8
+ * - Standardized error creation and formatting
9
+ * - Error categorization and classification
10
+ * - Recovery suggestion generation
11
+ * - Error context enhancement
12
+ */
13
+
14
+ class ErrorHandlerUtils {
15
+ constructor() {
16
+ this.config = {
17
+ errorCategories: {
18
+ PERMISSION: 'permission',
19
+ VALIDATION: 'validation',
20
+ NETWORK: 'network',
21
+ SYSTEM: 'system',
22
+ DEPENDENCY: 'dependency',
23
+ CONFIGURATION: 'configuration'
24
+ },
25
+ errorCodes: {
26
+ // Permission errors
27
+ EACCES: 'EACCES',
28
+ EPERM: 'EPERM',
29
+ ENOENT: 'ENOENT',
30
+
31
+ // Validation errors
32
+ VALIDATION_ERROR: 'VALIDATION_ERROR',
33
+ INVALID_INPUT: 'INVALID_INPUT',
34
+ INVALID_CONFIGURATION: 'INVALID_CONFIGURATION',
35
+ MISSING_REQUIRED_FIELD: 'MISSING_REQUIRED_FIELD',
36
+
37
+ // Dependency errors
38
+ NOT_FOUND: 'NOT_FOUND',
39
+ VERSION_MISMATCH: 'VERSION_MISMATCH',
40
+ DEPENDENCY_CONFLICT: 'DEPENDENCY_CONFLICT',
41
+
42
+ // Network errors
43
+ ENOTFOUND: 'ENOTFOUND',
44
+ TIMEOUT: 'TIMEOUT',
45
+ ECONNREFUSED: 'ECONNREFUSED',
46
+
47
+ // System errors
48
+ SYSTEM_FAILURE: 'SYSTEM_FAILURE',
49
+ CORRUPTION: 'CORRUPTION',
50
+ INSUFFICIENT_RESOURCES: 'INSUFFICIENT_RESOURCES',
51
+
52
+ // Installation errors
53
+ INSTALLATION_FAILED: 'INSTALLATION_FAILED',
54
+ BACKUP_FAILED: 'BACKUP_FAILED',
55
+ ROLLBACK_FAILED: 'ROLLBACK_FAILED',
56
+
57
+ // Generic errors
58
+ UNKNOWN_ERROR: 'UNKNOWN_ERROR',
59
+ OPERATION_FAILED: 'OPERATION_FAILED',
60
+ STRING_ERROR: 'STRING_ERROR'
61
+ },
62
+ recoverySuggestionTemplates: this._createRecoverySuggestionTemplates()
63
+ };
64
+ }
65
+
66
+ /**
67
+ * Create recovery suggestion templates
68
+ * @returns {Object} Recovery suggestion templates
69
+ * @private
70
+ */
71
+ _createRecoverySuggestionTemplates() {
72
+ return {
73
+ permission: {
74
+ immediate: [
75
+ 'Try: Check file and directory permissions',
76
+ 'Solution: Run command with elevated privileges'
77
+ ],
78
+ alternative: [
79
+ 'Use alternative installation location',
80
+ 'Install to user directory instead of system'
81
+ ],
82
+ troubleshooting: [
83
+ 'Check system logs for permission issues',
84
+ 'Verify user has necessary access rights',
85
+ 'Contact system administrator if needed'
86
+ ]
87
+ },
88
+ dependency: {
89
+ immediate: [
90
+ 'Try: Install missing dependency',
91
+ 'Solution: Update system package manager'
92
+ ],
93
+ alternative: [
94
+ 'Use alternative package manager',
95
+ 'Download and install manually'
96
+ ],
97
+ troubleshooting: [
98
+ 'Check internet connectivity',
99
+ 'Verify package repository configuration',
100
+ 'Clear package manager cache'
101
+ ]
102
+ },
103
+ validation: {
104
+ immediate: [
105
+ 'Try: Verify input parameters',
106
+ 'Solution: Check data format and types'
107
+ ],
108
+ alternative: [
109
+ 'Use default values where applicable',
110
+ 'Regenerate configuration if corrupted'
111
+ ],
112
+ troubleshooting: [
113
+ 'Enable debug mode for detailed errors',
114
+ 'Check application logs',
115
+ 'Review input validation requirements'
116
+ ]
117
+ }
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Create enhanced error object with additional context
123
+ * @param {Error|Object} originalError - Original error or error-like object
124
+ * @param {Object} context - Additional context information
125
+ * @returns {Object} Enhanced error object
126
+ */
127
+ createEnhancedError(originalError, context = {}) {
128
+ const baseError = this._extractBaseErrorInfo(originalError);
129
+
130
+ return {
131
+ ...baseError,
132
+ category: this._categorizeError(baseError),
133
+ context: context,
134
+ timestamp: new Date().toISOString(),
135
+ handled: false,
136
+ recoverable: this._isRecoverableError(baseError),
137
+ severity: this._determineSeverity(baseError, context)
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Extract base error information from various error types
143
+ * @param {Error|Object} error - Error to extract from
144
+ * @returns {Object} Base error information
145
+ * @private
146
+ */
147
+ _extractBaseErrorInfo(error) {
148
+ if (!error) {
149
+ return {
150
+ code: this.config.errorCodes.INVALID_INPUT,
151
+ message: 'Unknown error occurred',
152
+ type: 'unknown'
153
+ };
154
+ }
155
+
156
+ // Handle Error objects
157
+ if (error instanceof Error) {
158
+ return {
159
+ code: error.code || 'UNKNOWN_ERROR',
160
+ message: error.message,
161
+ type: error.constructor.name,
162
+ stack: error.stack,
163
+ path: error.path,
164
+ errno: error.errno
165
+ };
166
+ }
167
+
168
+ // Handle error-like objects
169
+ if (typeof error === 'object') {
170
+ return {
171
+ code: error.code || 'UNKNOWN_ERROR',
172
+ message: error.message || 'Unknown error occurred',
173
+ type: error.type || 'object',
174
+ ...error
175
+ };
176
+ }
177
+
178
+ // Handle string errors
179
+ return {
180
+ code: 'STRING_ERROR',
181
+ message: String(error),
182
+ type: 'string'
183
+ };
184
+ }
185
+
186
+ /**
187
+ * Categorize error based on code and type
188
+ * @param {Object} errorInfo - Error information
189
+ * @returns {string} Error category
190
+ * @private
191
+ */
192
+ _categorizeError(errorInfo) {
193
+ const code = errorInfo.code;
194
+
195
+ // Permission errors
196
+ if (['EACCES', 'EPERM', 'ENOENT'].includes(code)) {
197
+ return this.config.errorCategories.PERMISSION;
198
+ }
199
+
200
+ // Network errors
201
+ if (['ENOTFOUND', 'TIMEOUT', 'ECONNREFUSED'].includes(code)) {
202
+ return this.config.errorCategories.NETWORK;
203
+ }
204
+
205
+ // Validation errors
206
+ if (['VALIDATION_ERROR', 'INVALID_INPUT'].includes(code)) {
207
+ return this.config.errorCategories.VALIDATION;
208
+ }
209
+
210
+ // Dependency errors
211
+ if (['NOT_FOUND', 'VERSION_MISMATCH'].includes(code)) {
212
+ return this.config.errorCategories.DEPENDENCY;
213
+ }
214
+
215
+ return this.config.errorCategories.SYSTEM;
216
+ }
217
+
218
+ /**
219
+ * Determine if error is recoverable
220
+ * @param {Object} errorInfo - Error information
221
+ * @returns {boolean} True if error is recoverable
222
+ * @private
223
+ */
224
+ _isRecoverableError(errorInfo) {
225
+ const recoverableCodes = [
226
+ 'EACCES', 'EPERM', 'NOT_FOUND', 'VERSION_MISMATCH',
227
+ 'TIMEOUT', 'VALIDATION_ERROR'
228
+ ];
229
+
230
+ return recoverableCodes.includes(errorInfo.code);
231
+ }
232
+
233
+ /**
234
+ * Determine error severity
235
+ * @param {Object} errorInfo - Error information
236
+ * @param {Object} context - Error context
237
+ * @returns {string} Severity level (low, medium, high, critical)
238
+ * @private
239
+ */
240
+ _determineSeverity(errorInfo, context) {
241
+ // Critical errors that prevent core functionality
242
+ if (['SYSTEM_FAILURE', 'CORRUPTION'].includes(errorInfo.code)) {
243
+ return 'critical';
244
+ }
245
+
246
+ // High severity for system-level issues
247
+ if (context.scope === 'system' || ['EPERM', 'EACCES'].includes(errorInfo.code)) {
248
+ return 'high';
249
+ }
250
+
251
+ // Medium severity for dependency and validation issues
252
+ if (['NOT_FOUND', 'VERSION_MISMATCH', 'VALIDATION_ERROR'].includes(errorInfo.code)) {
253
+ return 'medium';
254
+ }
255
+
256
+ // Low severity for non-blocking issues
257
+ return 'low';
258
+ }
259
+
260
+ /**
261
+ * Generate recovery suggestions for error
262
+ * @param {Object} enhancedError - Enhanced error object
263
+ * @returns {Object} Recovery suggestions
264
+ */
265
+ generateRecoverySuggestions(enhancedError) {
266
+ const category = enhancedError.category;
267
+ const template = this.config.recoverySuggestionTemplates[category] ||
268
+ this.config.recoverySuggestionTemplates.validation;
269
+
270
+ const suggestions = {
271
+ immediate: [...template.immediate],
272
+ alternative: [...template.alternative],
273
+ troubleshooting: [...template.troubleshooting]
274
+ };
275
+
276
+ // Add context-specific suggestions
277
+ this._addContextSpecificSuggestions(suggestions, enhancedError);
278
+
279
+ return suggestions;
280
+ }
281
+
282
+ /**
283
+ * Add context-specific suggestions to recovery options
284
+ * @param {Object} suggestions - Suggestions object to modify
285
+ * @param {Object} enhancedError - Enhanced error object
286
+ * @private
287
+ */
288
+ _addContextSpecificSuggestions(suggestions, enhancedError) {
289
+ const { context, code } = enhancedError;
290
+
291
+ // Add path-specific suggestions
292
+ if (enhancedError.path) {
293
+ suggestions.immediate.push(`Check permissions for path: ${enhancedError.path}`);
294
+ }
295
+
296
+ // Add operation-specific suggestions
297
+ if (context.operation) {
298
+ suggestions.alternative.push(`Retry ${context.operation} with different parameters`);
299
+ }
300
+
301
+ // Add dependency-specific suggestions
302
+ if (context.dependency) {
303
+ suggestions.immediate.push(`Install dependency: ${context.dependency}`);
304
+ }
305
+
306
+ // Add command-specific suggestions
307
+ if (context.command) {
308
+ suggestions.troubleshooting.push(`Debug command: ${context.command}`);
309
+ }
310
+ }
311
+
312
+ /**
313
+ * Create error handling result object
314
+ * @param {Object} enhancedError - Enhanced error object
315
+ * @param {boolean} handled - Whether error was handled
316
+ * @param {Object} recovery - Recovery information
317
+ * @returns {Object} Error handling result
318
+ */
319
+ createErrorHandlingResult(enhancedError, handled = false, recovery = null) {
320
+ return {
321
+ error: enhancedError,
322
+ handled,
323
+ recovery,
324
+ suggestions: this.generateRecoverySuggestions(enhancedError),
325
+ timestamp: new Date().toISOString(),
326
+ actionable: enhancedError.recoverable,
327
+ contextAware: Boolean(enhancedError.context && Object.keys(enhancedError.context).length > 0)
328
+ };
329
+ }
330
+
331
+ /**
332
+ * Format error for user display
333
+ * @param {Object} enhancedError - Enhanced error object
334
+ * @param {Object} options - Formatting options
335
+ * @returns {string} Formatted error message
336
+ */
337
+ formatErrorForDisplay(enhancedError, options = {}) {
338
+ const {
339
+ includeCode = true,
340
+ includeContext = true,
341
+ includeStack = false,
342
+ maxLength = 200
343
+ } = options;
344
+
345
+ let message = enhancedError.message;
346
+
347
+ if (includeCode && enhancedError.code) {
348
+ message = `[${enhancedError.code}] ${message}`;
349
+ }
350
+
351
+ if (includeContext && enhancedError.context && enhancedError.context.operation) {
352
+ message = `${message} (during ${enhancedError.context.operation})`;
353
+ }
354
+
355
+ if (includeStack && enhancedError.stack) {
356
+ message += `\n\nStack trace:\n${enhancedError.stack}`;
357
+ }
358
+
359
+ // Truncate if too long
360
+ if (message.length > maxLength) {
361
+ message = message.substring(0, maxLength - 3) + '...';
362
+ }
363
+
364
+ return message;
365
+ }
366
+
367
+ /**
368
+ * Check if error matches specific criteria
369
+ * @param {Object} enhancedError - Enhanced error object
370
+ * @param {Object} criteria - Matching criteria
371
+ * @returns {boolean} True if error matches criteria
372
+ */
373
+ matchesErrorCriteria(enhancedError, criteria) {
374
+ const {
375
+ code,
376
+ category,
377
+ severity,
378
+ recoverable,
379
+ contextOperation
380
+ } = criteria;
381
+
382
+ if (code && enhancedError.code !== code) return false;
383
+ if (category && enhancedError.category !== category) return false;
384
+ if (severity && enhancedError.severity !== severity) return false;
385
+ if (recoverable !== undefined && enhancedError.recoverable !== recoverable) return false;
386
+ if (contextOperation && enhancedError.context?.operation !== contextOperation) return false;
387
+
388
+ return true;
389
+ }
390
+
391
+ /**
392
+ * Log error with context information
393
+ * @param {Object} enhancedError - Enhanced error object
394
+ * @param {Object} context - Additional logging context
395
+ */
396
+ logError(enhancedError, context = {}) {
397
+ const logLevel = this._getLogLevel(enhancedError.severity);
398
+ const logMessage = this.formatErrorForDisplay(enhancedError, {
399
+ includeCode: true,
400
+ includeContext: true,
401
+ includeStack: false
402
+ });
403
+
404
+ console[logLevel](`[${enhancedError.category}] ${logMessage}`);
405
+
406
+ if (context.attempt) {
407
+ console.warn(` Attempt ${context.attempt}/${context.maxAttempts || 'unknown'}`);
408
+ }
409
+
410
+ if (context.operationId) {
411
+ console.debug(` Operation ID: ${context.operationId}`);
412
+ }
413
+ }
414
+
415
+ /**
416
+ * Get appropriate console log level for error severity
417
+ * @param {string} severity - Error severity level
418
+ * @returns {string} Console method name
419
+ * @private
420
+ */
421
+ _getLogLevel(severity) {
422
+ switch (severity) {
423
+ case 'critical': return 'error';
424
+ case 'high': return 'error';
425
+ case 'medium': return 'warn';
426
+ case 'low': return 'info';
427
+ default: return 'log';
428
+ }
429
+ }
430
+ }
431
+
432
+ module.exports = ErrorHandlerUtils;