UNPKG

design-agent

Version:

Universal AI Design Review Agent - CLI tool for scanning code for design drift

284 lines (240 loc) 7.08 kB
/** * Error handling utilities for design agent * Provides comprehensive error handling and recovery */ export class DesignAgentError extends Error { constructor(message, code, context = {}) { super(message); this.name = 'DesignAgentError'; this.code = code; this.context = context; this.timestamp = new Date().toISOString(); } } export class ValidationError extends DesignAgentError { constructor(message, field, value) { super(message, 'VALIDATION_ERROR', { field, value }); this.name = 'ValidationError'; } } export class AdapterError extends DesignAgentError { constructor(message, adapter, file) { super(message, 'ADAPTER_ERROR', { adapter, file }); this.name = 'AdapterError'; } } export class ConfigError extends DesignAgentError { constructor(message, configPath) { super(message, 'CONFIG_ERROR', { configPath }); this.name = 'ConfigError'; } } export function handleError(error, context = {}) { const errorInfo = { name: error.name, message: error.message, code: error.code || 'UNKNOWN_ERROR', timestamp: new Date().toISOString(), context: { ...context, ...error.context }, stack: error.stack }; // Log error based on severity if (error instanceof ValidationError) { console.error('❌ Validation Error:', error.message); if (error.context.field) { console.error(` Field: ${error.context.field}`); } } else if (error instanceof AdapterError) { console.error('❌ Adapter Error:', error.message); if (error.context.adapter) { console.error(` Adapter: ${error.context.adapter}`); } } else if (error instanceof ConfigError) { console.error('❌ Config Error:', error.message); if (error.context.configPath) { console.error(` Config: ${error.context.configPath}`); } } else { console.error('❌ Unexpected Error:', error.message); } return errorInfo; } export function createErrorHandler(options = {}) { const { logErrors = true, throwOnCritical = true, maxRetries = 3, retryDelay = 1000 } = options; return { async executeWithRetry(fn, context = {}) { let lastError; for (let attempt = 1; attempt <= maxRetries; attempt++) { try { return await fn(); } catch (error) { lastError = error; if (logErrors) { console.warn(`⚠️ Attempt ${attempt} failed:`, error.message); } if (attempt < maxRetries) { await new Promise(resolve => setTimeout(resolve, retryDelay * attempt)); } } } if (throwOnCritical) { throw lastError; } return null; }, async executeWithFallback(fn, fallbackFn, context = {}) { try { return await fn(); } catch (error) { if (logErrors) { console.warn('⚠️ Primary function failed, using fallback:', error.message); } try { return await fallbackFn(); } catch (fallbackError) { if (logErrors) { console.error('❌ Fallback function also failed:', fallbackError.message); } throw fallbackError; } } } }; } export function validateAndThrow(condition, message, code, context) { if (!condition) { throw new DesignAgentError(message, code, context); } } export function safeExecute(fn, defaultValue = null) { try { return fn(); } catch (error) { console.warn('⚠️ Safe execution failed:', error.message); return defaultValue; } } export function createTimeoutPromise(timeoutMs, message = 'Operation timed out') { return new Promise((_, reject) => { setTimeout(() => { reject(new DesignAgentError(message, 'TIMEOUT_ERROR')); }, timeoutMs); }); } export function withTimeout(fn, timeoutMs, message) { return Promise.race([ fn(), createTimeoutPromise(timeoutMs, message) ]); } export function createCircuitBreaker(options = {}) { const { failureThreshold = 5, resetTimeout = 60000, monitoringPeriod = 10000 } = options; let failures = 0; let lastFailureTime = 0; let state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN return { async execute(fn) { if (state === 'OPEN') { if (Date.now() - lastFailureTime > resetTimeout) { state = 'HALF_OPEN'; } else { throw new DesignAgentError('Circuit breaker is OPEN', 'CIRCUIT_BREAKER_OPEN'); } } try { const result = await fn(); if (state === 'HALF_OPEN') { state = 'CLOSED'; failures = 0; } return result; } catch (error) { failures++; lastFailureTime = Date.now(); if (failures >= failureThreshold) { state = 'OPEN'; } throw error; } }, getState() { return { state, failures, lastFailureTime }; } }; } export function createRateLimiter(options = {}) { const { maxRequests = 100, windowMs = 60000, blockDuration = 300000 } = options; const requests = []; let blockedUntil = 0; return { async execute(fn) { const now = Date.now(); if (now < blockedUntil) { throw new DesignAgentError('Rate limit exceeded', 'RATE_LIMIT_EXCEEDED'); } // Clean old requests const cutoff = now - windowMs; while (requests.length > 0 && requests[0] < cutoff) { requests.shift(); } if (requests.length >= maxRequests) { blockedUntil = now + blockDuration; throw new DesignAgentError('Rate limit exceeded', 'RATE_LIMIT_EXCEEDED'); } requests.push(now); return await fn(); }, getStats() { const now = Date.now(); const cutoff = now - windowMs; const recentRequests = requests.filter(time => time > cutoff); return { requestsInWindow: recentRequests.length, maxRequests, windowMs, blockedUntil: Math.max(0, blockedUntil - now) }; } }; } export function generateErrorReport(errors) { if (errors.length === 0) { return '## Error Report\n\n✅ No errors occurred during execution.\n'; } let report = '## Error Report\n\n'; report += `**Total Errors:** ${errors.length}\n\n`; const byType = {}; errors.forEach(error => { const type = error.name || 'Unknown'; if (!byType[type]) byType[type] = []; byType[type].push(error); }); for (const [type, typeErrors] of Object.entries(byType)) { report += `### ${type} (${typeErrors.length})\n\n`; typeErrors.forEach(error => { report += `- **${error.timestamp}** - ${error.message}\n`; if (error.context && Object.keys(error.context).length > 0) { report += ` - Context: ${JSON.stringify(error.context)}\n`; } }); report += '\n'; } return report; }