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.

378 lines (373 loc) 13.8 kB
/** * Patch Generator * Part of Task 5.1: Edge Case Analyzer & Skill Patcher * * Generates simple patch templates for common failure patterns. * Phase 1 supports basic patches: error handling, null checks, type validation, timeouts, file checks. * * Features: * - Simple patch template generation * - Confidence calculation (≥0.85 threshold) * - Patch preview generation * - Integration with DatabaseService * - PENDING_UPDATE status for manual approval * - Performance optimized (<1s for patch generation) * * Usage: * const generator = new PatchGenerator({ dbPath: './patches.db' }); * const patch = generator.generatePatch(failure, category); * if (patch.confidence >= 0.85) { * const proposal = await generator.createPatchProposal(patch); * } */ import * as crypto from 'crypto'; import Database from 'better-sqlite3'; import { createLogger } from '../lib/logging.js'; import { ErrorCode, createError } from '../lib/errors.js'; import { FailureCategory } from './edge-case-analyzer.js'; const logger = createLogger('patch-generator'); /** * Patch type classification */ export var PatchType = /*#__PURE__*/ function(PatchType) { PatchType["ADD_ERROR_HANDLING"] = "ADD_ERROR_HANDLING"; PatchType["ADD_NULL_CHECK"] = "ADD_NULL_CHECK"; PatchType["ADD_TYPE_VALIDATION"] = "ADD_TYPE_VALIDATION"; PatchType["ADD_TIMEOUT"] = "ADD_TIMEOUT"; PatchType["ADD_FILE_CHECK"] = "ADD_FILE_CHECK"; return PatchType; }({}); /** * Patch status */ export var PatchStatus = /*#__PURE__*/ function(PatchStatus) { PatchStatus["PENDING_UPDATE"] = "PENDING_UPDATE"; PatchStatus["APPROVED"] = "APPROVED"; PatchStatus["DEPLOYED"] = "DEPLOYED"; PatchStatus["REJECTED"] = "REJECTED"; PatchStatus["ROLLED_BACK"] = "ROLLED_BACK"; return PatchStatus; }({}); /** * Patch Generator Service */ export class PatchGenerator { db; confidenceThreshold; constructor(config){ this.db = new Database(config.dbPath); this.confidenceThreshold = config.confidenceThreshold || 0.85; this.initializeDatabase(); } /** * Initialize database schema */ initializeDatabase() { this.db.exec(` CREATE TABLE IF NOT EXISTS skill_patches ( id TEXT PRIMARY KEY, skill_id TEXT NOT NULL, failure_id TEXT NOT NULL, category TEXT NOT NULL, patch_content TEXT NOT NULL, confidence REAL NOT NULL, status TEXT DEFAULT 'PENDING_UPDATE', created_at TEXT DEFAULT CURRENT_TIMESTAMP, approved_by TEXT, deployed_at TEXT, success INTEGER, rollback_reason TEXT ); CREATE INDEX IF NOT EXISTS idx_skill_patches_skill ON skill_patches(skill_id); CREATE INDEX IF NOT EXISTS idx_skill_patches_status ON skill_patches(status); CREATE INDEX IF NOT EXISTS idx_skill_patches_confidence ON skill_patches(confidence); `); } /** * Parse target file and line from stack trace */ parseStackTrace(stackTrace) { // Try to extract file and line from stack trace // Formats: // - at functionName (file.ts:42:10) // - at /absolute/path/file.ts:42:10 // - at functionName (/absolute/path/file.ts:42:10) const patterns = [ /at\s+.*?\(([^:]+):(\d+):\d+\)/, /at\s+([^:]+):(\d+):\d+/, /\(([^:]+):(\d+):\d+\)/ ]; for (const pattern of patterns){ const match = stackTrace.match(pattern); if (match) { const file = match[1].trim(); const line = parseInt(match[2], 10); return { file, line }; } } return { file: 'unknown', line: 0 }; } /** * Generate patch for failure * * Performance target: <1s */ generatePatch(failure, category) { const startTime = Date.now(); const { file, line } = this.parseStackTrace(failure.stackTrace); let patchType; let content; // Determine patch type and generate content based on failure category if (category === FailureCategory.TIMEOUT) { patchType = "ADD_TIMEOUT"; const timeout = failure.context.timeout || 5000; content = `const result = await withTimeout(operation(), ${timeout});`; } else if (category === FailureCategory.VALIDATION_ERROR && (failure.errorMessage.includes('null') || failure.errorMessage.includes('undefined'))) { patchType = "ADD_NULL_CHECK"; const variableName = this.extractVariableName(failure.errorMessage); content = `if (${variableName} === null || ${variableName} === undefined) { throw new StandardError('NULL_VALUE', '${variableName} cannot be null or undefined'); }`; } else if (category === FailureCategory.VALIDATION_ERROR && (failure.errorMessage.includes('Expected') || failure.errorMessage.includes('type'))) { patchType = "ADD_TYPE_VALIDATION"; const expectedType = failure.context.expectedType || 'string'; const variableName = this.extractVariableName(failure.errorMessage); content = `if (typeof ${variableName} !== '${expectedType}') { throw new StandardError('INVALID_TYPE', 'Expected ${expectedType} but got ' + typeof ${variableName}); }`; } else if (category === FailureCategory.LOGIC_ERROR && failure.errorMessage.includes('File not found')) { patchType = "ADD_FILE_CHECK"; const filePath = failure.context.filePath || 'filePath'; content = `if (!fs.existsSync(${filePath})) { throw new StandardError('FILE_NOT_FOUND', \`File not found: \${${filePath}}\`); }`; } else { // Default to error handling patchType = "ADD_ERROR_HANDLING"; const operation = failure.context.operation || 'operation'; content = `try { // existing code for ${operation} } catch (error) { logger.error('${operation} failed', error); throw new StandardError('OPERATION_FAILED', '${operation} failed', {}, error); }`; } const patch = { id: crypto.randomUUID(), failureId: failure.id, skillId: failure.skillId, type: patchType, category, content, targetFile: file, targetLine: line, confidence: 0, similarFailureCount: 0 }; logger.debug('Patch generated', { patchType, targetFile: file, targetLine: line, durationMs: Date.now() - startTime }); return patch; } /** * Extract variable name from error message */ extractVariableName(errorMessage) { // Try to extract variable name from error messages like: // - "Cannot read property 'x' of null" // - "userData is null" // - "Expected string but got number for field" const patterns = [ /Cannot read property.*?of (\w+)/, /(\w+) is (null|undefined)/, /for (\w+)/, /(\w+) cannot be/ ]; for (const pattern of patterns){ const match = errorMessage.match(pattern); if (match && match[1] !== 'null' && match[1] !== 'undefined') { return match[1]; } } return 'value'; } /** * Calculate patch confidence * * Factors: * - Number of similar failures (higher = more confident) * - Patch type (some types are more reliable) * - Category (some categories are easier to fix) * * Threshold: ≥0.85 for auto-approval consideration */ calculatePatchConfidence(patch) { let confidence = 0.5; // Base confidence // Similar failure count boost (up to +0.4) const failureBoost = Math.min(0.4, patch.similarFailureCount * 0.04); confidence += failureBoost; // Patch type boost if (patch.type === "ADD_ERROR_HANDLING" || patch.type === "ADD_NULL_CHECK") { confidence += 0.1; // These are generally safe } if (patch.type === "ADD_FILE_CHECK" || patch.type === "ADD_TIMEOUT") { confidence += 0.05; // Moderately safe } // Category boost if (patch.category === FailureCategory.VALIDATION_ERROR) { confidence += 0.05; // Validation fixes are usually straightforward } return Math.min(0.98, confidence); } /** * Create patch proposal (requires confidence ≥ threshold) */ async createPatchProposal(patch) { const startTime = Date.now(); // Calculate confidence if not set if (patch.confidence === 0) { patch.confidence = this.calculatePatchConfidence(patch); } // Enforce confidence threshold if (patch.confidence < this.confidenceThreshold) { throw createError(ErrorCode.VALIDATION_FAILED, `Patch confidence ${patch.confidence.toFixed(2)} below threshold ${this.confidenceThreshold}`, { patchId: patch.id, confidence: patch.confidence }); } // Generate preview const preview = this.generatePatchPreview(patch); // Store in database this.db.prepare(` INSERT INTO skill_patches (id, skill_id, failure_id, category, patch_content, confidence, status) VALUES (?, ?, ?, ?, ?, ?, ?) `).run(patch.id, patch.skillId, patch.failureId, patch.category, patch.content, patch.confidence, "PENDING_UPDATE"); const proposal = { patch, status: "PENDING_UPDATE", createdAt: new Date(), preview }; logger.info('Patch proposal created', { patchId: patch.id, confidence: patch.confidence, status: "PENDING_UPDATE", durationMs: Date.now() - startTime }); return proposal; } /** * Generate human-readable patch preview */ generatePatchPreview(patch) { return ` Patch Preview ============= ID: ${patch.id} Type: ${patch.type} Category: ${patch.category} Skill: ${patch.skillId} Confidence: ${patch.confidence.toFixed(2)} Target: File: ${patch.targetFile} Line: ${patch.targetLine} Patch Content: ${patch.content.split('\n').map((line)=>' ' + line).join('\n')} Similar Failures: ${patch.similarFailureCount} Status: Pending Manual Approval `.trim(); } /** * Get patch proposal by ID */ getPatchProposal(patchId) { const stmt = this.db.prepare('SELECT * FROM skill_patches WHERE id = ?'); const row = stmt.get(patchId); if (!row) { return undefined; } const patch = { id: row.id, failureId: row.failure_id, skillId: row.skill_id, type: row.category, category: row.category, content: row.patch_content, targetFile: '', targetLine: 0, confidence: row.confidence, similarFailureCount: 0 }; const proposal = { patch, status: row.status, createdAt: new Date(row.created_at), preview: this.generatePatchPreview(patch), approvedBy: row.approved_by, deployedAt: row.deployed_at ? new Date(row.deployed_at) : undefined, success: row.success !== null ? Boolean(row.success) : undefined, rollbackReason: row.rollback_reason }; return proposal; } /** * Get pending patches (optionally filtered by skill) */ getPendingPatches(skillId) { let query = ` SELECT * FROM skill_patches WHERE status = 'PENDING_UPDATE' `; const params = []; if (skillId) { query += ' AND skill_id = ?'; params.push(skillId); } query += ' ORDER BY confidence DESC'; const stmt = this.db.prepare(query); const rows = stmt.all(...params); return rows.map((row)=>{ const patch = { id: row.id, failureId: row.failure_id, skillId: row.skill_id, type: this.inferPatchType(row.patch_content), category: row.category, content: row.patch_content, targetFile: '', targetLine: 0, confidence: row.confidence, similarFailureCount: 0 }; return { patch, status: row.status, createdAt: new Date(row.created_at), preview: this.generatePatchPreview(patch), approvedBy: row.approved_by, deployedAt: row.deployed_at ? new Date(row.deployed_at) : undefined, success: row.success !== null ? Boolean(row.success) : undefined, rollbackReason: row.rollback_reason }; }); } /** * Infer patch type from content */ inferPatchType(content) { if (content.includes('withTimeout')) { return "ADD_TIMEOUT"; } if (content.includes('null') || content.includes('undefined')) { return "ADD_NULL_CHECK"; } if (content.includes('typeof')) { return "ADD_TYPE_VALIDATION"; } if (content.includes('fs.existsSync')) { return "ADD_FILE_CHECK"; } return "ADD_ERROR_HANDLING"; } /** * Close database connection */ close() { this.db.close(); } } //# sourceMappingURL=patch-generator.js.map