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.

321 lines (319 loc) 12 kB
/** * Edge Case Analyzer * Part of Task 5.1: Edge Case Analyzer & Skill Patcher * * Analyzes skill execution failures to identify patterns and categorize edge cases. * Supports pattern matching, confidence scoring, and failure statistics. * * Features: * - Automatic failure categorization (syntax, logic, timeout, validation, unknown) * - Pattern matching for similar failures * - Confidence scoring based on failure frequency * - Integration with DatabaseService * - Performance optimized (<500ms for pattern matching) * * Usage: * const analyzer = new EdgeCaseAnalyzer({ dbPath: './edge-cases.db' }); * const pattern = await analyzer.analyzeFailure(error, { skillId: 'my-skill' }); * console.log(`Detected ${pattern.category} with ${pattern.confidence} confidence`); */ import * as crypto from 'crypto'; import Database from 'better-sqlite3'; import { createLogger } from '../lib/logging.js'; import { StandardError, ErrorCode } from '../lib/errors.js'; const logger = createLogger('edge-case-analyzer'); /** * Failure category classification */ export var FailureCategory = /*#__PURE__*/ function(FailureCategory) { FailureCategory["SYNTAX_ERROR"] = "SYNTAX_ERROR"; FailureCategory["LOGIC_ERROR"] = "LOGIC_ERROR"; FailureCategory["TIMEOUT"] = "TIMEOUT"; FailureCategory["VALIDATION_ERROR"] = "VALIDATION_ERROR"; FailureCategory["UNKNOWN"] = "UNKNOWN"; return FailureCategory; }({}); /** * Edge Case Analyzer Service */ export class EdgeCaseAnalyzer { db; constructor(config){ this.db = new Database(config.dbPath); this.initializeDatabase(); } /** * Initialize database schema */ initializeDatabase() { this.db.exec(` CREATE TABLE IF NOT EXISTS edge_cases ( id TEXT PRIMARY KEY, skill_id TEXT NOT NULL, category TEXT NOT NULL, error_message TEXT, stack_trace TEXT, context TEXT, detected_at TEXT DEFAULT CURRENT_TIMESTAMP, pattern_hash TEXT, confidence REAL ); CREATE INDEX IF NOT EXISTS idx_edge_cases_skill ON edge_cases(skill_id); CREATE INDEX IF NOT EXISTS idx_edge_cases_category ON edge_cases(category); CREATE INDEX IF NOT EXISTS idx_edge_cases_pattern ON edge_cases(pattern_hash); `); } /** * Categorize failure based on error type and context * * Performance target: <200ms */ categorizeFailure(error, context) { const startTime = Date.now(); try { // Check for syntax errors if (error instanceof SyntaxError) { logger.debug('Categorized as SYNTAX_ERROR', { error: error.message }); return "SYNTAX_ERROR"; } // Check for timeout errors if (error instanceof StandardError && (error.code === ErrorCode.OPERATION_TIMEOUT || error.code === ErrorCode.DB_TIMEOUT)) { logger.debug('Categorized as TIMEOUT', { error: error.message }); return "TIMEOUT"; } if (error.message && /timeout|timed out/i.test(error.message)) { logger.debug('Categorized as TIMEOUT from message', { error: error.message }); return "TIMEOUT"; } // Check for validation errors if (error instanceof StandardError && (error.code === ErrorCode.VALIDATION_FAILED || error.code === ErrorCode.INVALID_INPUT || error.code === ErrorCode.DB_VALIDATION_FAILED)) { logger.debug('Categorized as VALIDATION_ERROR', { error: error.message }); return "VALIDATION_ERROR"; } // Check for null/undefined errors (validation) if (error instanceof TypeError && (error.message.includes('null') || error.message.includes('undefined') || error.message.includes('Cannot read property'))) { logger.debug('Categorized as VALIDATION_ERROR (null/undefined)', { error: error.message }); return "VALIDATION_ERROR"; } // Check for logic errors (file not found, reference errors, etc.) if (error instanceof ReferenceError) { logger.debug('Categorized as LOGIC_ERROR (ReferenceError)', { error: error.message }); return "LOGIC_ERROR"; } if (error instanceof StandardError && (error.code === ErrorCode.FILE_NOT_FOUND || error.code === ErrorCode.FILE_WRITE_FAILED)) { logger.debug('Categorized as LOGIC_ERROR (file operation)', { error: error.message }); return "LOGIC_ERROR"; } // Default to unknown logger.debug('Categorized as UNKNOWN', { error: error.message }); return "UNKNOWN"; } finally{ const duration = Date.now() - startTime; logger.debug('Categorization completed', { durationMs: duration }); } } /** * Find similar failures by skill and category * * Performance target: <500ms */ findSimilarFailures(failure) { const startTime = Date.now(); try { const stmt = this.db.prepare(` SELECT * FROM edge_cases WHERE skill_id = ? AND category = ? AND id != ? ORDER BY detected_at DESC LIMIT 10 `); const rows = stmt.all(failure.skillId, failure.category, failure.id); const similar = rows.map((row)=>({ id: row.id, skillId: row.skill_id, category: row.category, errorMessage: row.error_message, stackTrace: row.stack_trace, context: JSON.parse(row.context || '{}'), detectedAt: new Date(row.detected_at), patternHash: row.pattern_hash, confidence: row.confidence })); logger.debug('Found similar failures', { count: similar.length, durationMs: Date.now() - startTime }); return similar; } catch (error) { logger.error('Failed to find similar failures', error); return []; } } /** * Calculate confidence score based on failure frequency * * Formula: * - 0 failures: 0.0 * - 1 failure: 0.5 * - 2-3 failures: 0.6-0.7 * - 4-6 failures: 0.75-0.85 * - 7-10 failures: 0.85-0.95 * - 10+ failures: 0.95+ */ calculatePatternConfidence(failures) { if (failures.length === 0) { return 0; } if (failures.length === 1) { return 0.5; } if (failures.length <= 3) { return 0.6 + failures.length * 0.05; } if (failures.length <= 6) { return 0.75 + (failures.length - 3) * 0.03; } if (failures.length <= 10) { return 0.85 + (failures.length - 6) * 0.025; } return Math.min(0.98, 0.95 + (failures.length - 10) * 0.01); } /** * Generate pattern hash from error message and category */ generatePatternHash(category, errorMessage) { // Normalize error message (remove numbers, paths, etc.) const normalized = errorMessage.replace(/\d+/g, 'N') // Replace numbers .replace(/\/[^\s]*/g, '/PATH') // Replace file paths .replace(/at [^\n]*/g, 'at LOCATION') // Replace stack locations .toLowerCase(); const hashInput = `${category}:${normalized}`; return crypto.createHash('sha256').update(hashInput).digest('hex').substring(0, 16); } /** * Analyze failure and detect patterns * * Returns failure pattern with confidence score */ async analyzeFailure(error, context) { const startTime = Date.now(); // Categorize the failure const category = this.categorizeFailure(error, context); // Generate pattern hash const patternHash = this.generatePatternHash(category, error.message); // Create failure record const failure = { id: crypto.randomUUID(), skillId: context.skillId || 'unknown', category, errorMessage: error.message, stackTrace: error.stack || '', context, detectedAt: new Date(), patternHash }; // Find similar failures const similar = this.findSimilarFailures(failure); // Calculate confidence const allFailures = [ failure, ...similar ]; const confidence = this.calculatePatternConfidence(allFailures); // Store failure this.db.prepare(` INSERT INTO edge_cases (id, skill_id, category, error_message, stack_trace, context, pattern_hash, confidence) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `).run(failure.id, failure.skillId, failure.category, failure.errorMessage, failure.stackTrace, JSON.stringify(failure.context), failure.patternHash, confidence); // Build pattern const skillIds = Array.from(new Set([ failure.skillId, ...similar.map((f)=>f.skillId) ])); const pattern = { category, patternHash, failureCount: allFailures.length, confidence, firstSeenAt: similar.length > 0 ? similar[similar.length - 1].detectedAt : failure.detectedAt, lastSeenAt: failure.detectedAt, skillIds }; logger.info('Failure analyzed', { category, confidence, failureCount: allFailures.length, durationMs: Date.now() - startTime }); return pattern; } /** * Get failure statistics */ getFailureStats(skillId) { const whereClause = skillId ? 'WHERE skill_id = ?' : ''; const params = skillId ? [ skillId ] : []; // Total failures const totalStmt = this.db.prepare(`SELECT COUNT(*) as count FROM edge_cases ${whereClause}`); const totalResult = totalStmt.get(...params); const totalFailures = totalResult.count; // By category const categoryStmt = this.db.prepare(` SELECT category, COUNT(*) as count FROM edge_cases ${whereClause} GROUP BY category `); const categoryResults = categoryStmt.all(...params); const byCategory = {}; for (const row of categoryResults){ byCategory[row.category] = row.count; } // Unique patterns const patternsStmt = this.db.prepare(` SELECT COUNT(DISTINCT pattern_hash) as count FROM edge_cases ${whereClause} `); const patternsResult = patternsStmt.get(...params); const uniquePatterns = patternsResult.count; // Top patterns const topStmt = this.db.prepare(` SELECT pattern_hash, category, COUNT(*) as count FROM edge_cases ${whereClause} GROUP BY pattern_hash, category ORDER BY count DESC LIMIT 5 `); const topResults = topStmt.all(...params); const topPatterns = topResults.map((row)=>({ patternHash: row.pattern_hash, count: row.count, category: row.category })); return { totalFailures, byCategory, uniquePatterns, topPatterns }; } /** * Close database connection */ close() { this.db.close(); } } //# sourceMappingURL=edge-case-analyzer.js.map