UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes Local RuVector Accelerator and all CFN skills for complete functionality.

337 lines (336 loc) 13.1 kB
/** * Edge Case Detector Service * * Detects and categorizes skill execution failures for continuous improvement. * Part of Task 1.5: MVP Edge Case Feedback Loop * * Features: * - Automatic failure detection from skill executions * - Error categorization (syntax, runtime, validation, timeout, dependency) * - Severity calculation based on frequency and impact * - Context capture (input, output, stack trace) * - Integration with edge case deduplicator * * Usage: * const detector = new EdgeCaseDetector(dbService, logger); * const edgeCase = await detector.detectFailure(execution); */ import { createLogger } from '../lib/logging.js'; import { createError, ErrorCode, wrapError } from '../lib/errors.js'; import { generateShortCorrelationId } from '../lib/correlation.js'; import { EdgeCaseDeduplicator } from './edge-case-deduplicator.js'; /** * Error categories for skill execution failures */ export var ErrorCategory = /*#__PURE__*/ function(ErrorCategory) { ErrorCategory["SYNTAX"] = "syntax"; ErrorCategory["RUNTIME"] = "runtime"; ErrorCategory["VALIDATION"] = "validation"; ErrorCategory["TIMEOUT"] = "timeout"; ErrorCategory["DEPENDENCY"] = "dependency"; ErrorCategory["UNKNOWN"] = "unknown"; return ErrorCategory; }({}); /** * Severity levels for edge cases */ export var Severity = /*#__PURE__*/ function(Severity) { Severity["LOW"] = "low"; Severity["MEDIUM"] = "medium"; Severity["HIGH"] = "high"; Severity["CRITICAL"] = "critical"; return Severity; }({}); /** * Edge case detector service */ export class EdgeCaseDetector { dbService; logger; deduplicator; config; constructor(dbService, logger, config){ this.dbService = dbService; this.logger = logger || createLogger('edge-case-detector'); this.deduplicator = new EdgeCaseDeduplicator(dbService, this.logger); // Set default config this.config = { enableDeduplication: config?.enableDeduplication ?? true, minSeverity: config?.minSeverity ?? "low", customRules: config?.customRules ?? [] }; } /** * Detect failure from skill execution * * @param execution - Skill execution context * @returns Edge case if failure detected, null otherwise */ async detectFailure(execution) { try { // 1. Check if execution failed if (execution.success) { this.logger.debug('Execution succeeded, no edge case detected', { skill_id: execution.skill_id }); return null; } if (!execution.error) { this.logger.warn('Execution marked as failed but no error provided', { skill_id: execution.skill_id }); return null; } // 2. Extract error information const error = execution.error; const category = this.categorizeError(error); const severity = this.calculateSeverity(category, execution); // 3. Check minimum severity threshold if (this.shouldIgnoreSeverity(severity)) { this.logger.debug('Edge case below minimum severity threshold', { skill_id: execution.skill_id, severity, minSeverity: this.config.minSeverity }); return null; } // 4. Extract error details const errorMessage = error instanceof Error ? error.message : String(error); const stackTrace = error instanceof Error ? error.stack : undefined; // 5. Capture context const edgeCase = { id: `edge-${generateShortCorrelationId()}`, skill_id: execution.skill_id, error_type: category, severity, error_message: errorMessage, stack_trace: stackTrace, input_context: JSON.stringify(execution.input), output_context: execution.output, first_seen: execution.timestamp, last_seen: execution.timestamp, occurrence_count: 1, status: 'new', metadata: { agent_id: execution.agent_id, task_id: execution.task_id, duration_ms: execution.duration_ms, timestamp: execution.timestamp.toISOString() } }; this.logger.info('Edge case detected', { edge_case_id: edgeCase.id, skill_id: edgeCase.skill_id, error_type: category, severity }); // 6. Deduplicate if enabled if (this.config.enableDeduplication) { const isDuplicate = await this.deduplicator.deduplicateEdgeCase(edgeCase); if (isDuplicate) { this.logger.debug('Edge case is duplicate, incrementing occurrence count', { edge_case_id: edgeCase.id }); return null; // Already tracked } } // 7. Store new edge case await this.storeEdgeCase(edgeCase); return edgeCase; } catch (error) { this.logger.error('Failed to detect edge case', error, { skill_id: execution.skill_id }); throw wrapError(error, 'EDGE_CASE_DETECTION_FAILED'); } } /** * Categorize error into predefined categories * * @param error - Error to categorize * @returns Error category */ categorizeError(error) { const errorMessage = error.message.toLowerCase(); const errorName = error.name.toLowerCase(); // Check custom rules first for (const rule of this.config.customRules){ if (rule.pattern.test(error.message)) { this.logger.debug('Error matched custom rule', { pattern: rule.pattern.toString(), category: rule.category }); return rule.category; } } // Syntax errors if (errorName.includes('syntax') || errorMessage.includes('syntax error') || errorMessage.includes('parse error') || errorMessage.includes('unexpected token')) { return "syntax"; } // Validation errors if (errorName.includes('validation') || errorMessage.includes('invalid input') || errorMessage.includes('validation failed') || errorMessage.includes('required field') || errorMessage.includes('expected')) { return "validation"; } // Timeout errors if (errorName.includes('timeout') || errorMessage.includes('timeout') || errorMessage.includes('timed out') || errorMessage.includes('deadline exceeded')) { return "timeout"; } // Dependency errors if (errorMessage.includes('cannot find module') || errorMessage.includes('module not found') || errorMessage.includes('enoent') || errorMessage.includes('not found') || errorMessage.includes('missing dependency')) { return "dependency"; } // Runtime errors (default for most execution failures) if (errorName.includes('error') || errorMessage.includes('runtime') || errorMessage.includes('execution failed')) { return "runtime"; } // Unknown return "unknown"; } /** * Calculate severity based on error category and execution context * * @param category - Error category * @param execution - Skill execution context * @returns Severity level */ calculateSeverity(category, execution) { // Base severity by category let severity; switch(category){ case "syntax": severity = "high"; // Syntax errors block execution break; case "validation": severity = "medium"; // Validation errors are fixable break; case "timeout": severity = "medium"; // Timeouts may be transient break; case "dependency": severity = "high"; // Missing dependencies block execution break; case "runtime": severity = "medium"; // Runtime errors vary in impact break; case "unknown": severity = "low"; // Unknown errors need investigation break; } // Upgrade severity if error message indicates critical issue if (execution.error) { const message = execution.error.message.toLowerCase(); if (message.includes('critical') || message.includes('fatal') || message.includes('segmentation fault') || message.includes('out of memory')) { severity = "critical"; } } return severity; } /** * Check if severity should be ignored based on threshold */ shouldIgnoreSeverity(severity) { const severityOrder = [ "low", "medium", "high", "critical" ]; const currentIndex = severityOrder.indexOf(severity); const minIndex = severityOrder.indexOf(this.config.minSeverity); return currentIndex < minIndex; } /** * Store edge case in database */ async storeEdgeCase(edgeCase) { const sqlite = this.dbService.getAdapter('sqlite'); const result = await sqlite.insert('edge_cases', { id: edgeCase.id, skill_id: edgeCase.skill_id, error_type: edgeCase.error_type, severity: edgeCase.severity, error_message: edgeCase.error_message, stack_trace: edgeCase.stack_trace, input_context: edgeCase.input_context, output_context: edgeCase.output_context, first_seen: edgeCase.first_seen.toISOString(), last_seen: edgeCase.last_seen.toISOString(), occurrence_count: edgeCase.occurrence_count, status: edgeCase.status, metadata: JSON.stringify(edgeCase.metadata) }); if (!result.success) { throw createError(ErrorCode.DB_QUERY_FAILED, 'Failed to store edge case', { edge_case_id: edgeCase.id }, result.error); } this.logger.info('Edge case stored successfully', { edge_case_id: edgeCase.id }); } /** * Get edge case by ID */ async getEdgeCase(id) { const sqlite = this.dbService.getAdapter('sqlite'); const query = 'SELECT * FROM edge_cases WHERE id = ? LIMIT 1'; const rows = await sqlite.raw(query, [ id ]); if (!Array.isArray(rows) || rows.length === 0) { return null; } return this.mapRowToEdgeCase(rows[0]); } /** * List all edge cases with optional filtering */ async listEdgeCases(filters) { const sqlite = this.dbService.getAdapter('sqlite'); const queryFilters = []; if (filters?.skill_id) { queryFilters.push({ field: 'skill_id', operator: 'eq', value: filters.skill_id }); } if (filters?.error_type) { queryFilters.push({ field: 'error_type', operator: 'eq', value: filters.error_type }); } if (filters?.severity) { queryFilters.push({ field: 'severity', operator: 'eq', value: filters.severity }); } if (filters?.status) { queryFilters.push({ field: 'status', operator: 'eq', value: filters.status }); } const rows = await sqlite.list('edge_cases', { filters: queryFilters, limit: filters?.limit || 100, orderBy: 'last_seen', order: 'desc' }); return rows.map((row)=>this.mapRowToEdgeCase(row)); } /** * Map database row to EdgeCase object */ mapRowToEdgeCase(row) { return { id: row.id, skill_id: row.skill_id, error_type: row.error_type, severity: row.severity, error_message: row.error_message, stack_trace: row.stack_trace, input_context: row.input_context, output_context: row.output_context, first_seen: new Date(row.first_seen), last_seen: new Date(row.last_seen), occurrence_count: row.occurrence_count, status: row.status, metadata: row.metadata ? JSON.parse(row.metadata) : {} }; } } //# sourceMappingURL=edge-case-detector.js.map