UNPKG

claude-flow-novice

Version:

Claude Flow Novice - Advanced orchestration platform for multi-agent AI workflows with CFN Loop architecture Includes CodeSearch (hybrid SQLite + pgvector), mem0/memgraph specialists, and all CFN skills.

312 lines (311 loc) 10.8 kB
/** * Skill Output Parser * Task 5.4: Eliminate Bash Output Parsing * * Parses structured JSON outputs from skill execution with legacy text fallback * * @version 1.0.0 */ // ============================================================================ // Parser Class // ============================================================================ /** * SkillOutputParser: Parses skill execution outputs * * Primary path: JSON parsing with schema validation * Fallback path: Legacy text parsing */ export class SkillOutputParser { config; constructor(config = {}){ this.config = { enableLegacyParsing: config.enableLegacyParsing ?? true, defaultConfidence: config.defaultConfidence ?? 0.5, strictValidation: config.strictValidation ?? false }; } // ========================================================================== // Public API // ========================================================================== /** * Parse skill output (JSON or legacy text) * * @param output - Raw skill output string * @returns Parse result */ parse(output) { const errors = []; const warnings = []; // Handle empty input if (!output || output.trim().length === 0) { warnings.push('Empty input received'); return this.createLegacyParseResult(output, warnings); } // Try JSON parsing first const jsonResult = this.tryParseJSON(output); if (jsonResult.success && jsonResult.data) { // Validate JSON schema const validationResult = this.validateSkillOutput(jsonResult.data); if (validationResult.valid) { return { success: true, output: jsonResult.data, parseMethod: 'json', confidence: 0.95, errors: [], warnings: this.config.strictValidation ? validationResult.warnings : [] }; } else { // JSON parsed but validation failed - don't fall back to legacy // because JSON structure exists, it's just invalid errors.push(...validationResult.errors); return { success: false, output: this.createDefaultOutput(), parseMethod: 'validation_failed', confidence: 0.0, errors, warnings }; } } // JSON parsing failed if (this.config.enableLegacyParsing) { warnings.push('Using legacy text parsing (consider migrating to JSON output)'); return this.createLegacyParseResult(output, warnings); } else { errors.push('JSON parsing failed and legacy parsing disabled'); return { success: false, output: this.createDefaultOutput(), parseMethod: 'validation_failed', confidence: 0.0, errors, warnings }; } } /** * Parse multiple skill outputs in batch * * @param outputs - Array of raw skill output strings * @returns Array of parse results */ parseBatch(outputs) { return outputs.map((output)=>this.parse(output)); } // ========================================================================== // JSON Parsing // ========================================================================== /** * Try to parse output as JSON * * @param output - Raw output string * @returns Parse result with success flag and data */ tryParseJSON(output) { try { // Try to extract JSON from mixed output const jsonMatch = output.match(/\{[\s\S]*\}/); if (jsonMatch) { const data = JSON.parse(jsonMatch[0]); return { success: true, data }; } // Try parsing entire output const data = JSON.parse(output); return { success: true, data }; } catch (error) { return { success: false }; } } /** * Validate skill output against schema * * @param data - Parsed JSON data * @returns Validation result */ validateSkillOutput(data) { const errors = []; const warnings = []; // Required fields if (typeof data.success !== 'boolean') { errors.push('Missing required field: success (must be boolean)'); } if (typeof data.confidence !== 'number') { errors.push('Missing required field: confidence (must be number)'); } else if (data.confidence < 0.0 || data.confidence > 1.0) { errors.push('Confidence must be between 0.0 and 1.0'); } if (!Array.isArray(data.deliverables)) { errors.push('Deliverables must be an array'); } if (typeof data.metrics !== 'object' || data.metrics === null || Array.isArray(data.metrics)) { errors.push('Metrics must be an object'); } if (!Array.isArray(data.errors)) { errors.push('Errors must be an array'); } // Strict mode: check for unexpected fields if (this.config.strictValidation) { const allowedFields = new Set([ 'success', 'confidence', 'deliverables', 'metrics', 'errors' ]); const actualFields = Object.keys(data); const unexpectedFields = actualFields.filter((field)=>!allowedFields.has(field)); if (unexpectedFields.length > 0) { warnings.push(`Unexpected fields in output: ${unexpectedFields.join(', ')}`); } } return { valid: errors.length === 0, errors, warnings }; } // ========================================================================== // Legacy Parsing // ========================================================================== /** * Parse legacy text output * * @param output - Raw text output * @param warnings - Existing warnings to include * @returns Parse result */ createLegacyParseResult(output, warnings = []) { // Special handling for empty input const isEmpty = !output || output.trim().length === 0; const parsedOutput = { success: isEmpty ? false : this.detectSuccess(output), confidence: isEmpty ? 0.0 : this.extractConfidence(output), deliverables: this.extractDeliverables(output), metrics: this.extractMetrics(output), errors: this.extractErrors(output) }; return { success: true, output: parsedOutput, parseMethod: 'legacy', confidence: parsedOutput.confidence, errors: [], warnings }; } /** * Detect success from text patterns */ detectSuccess(output) { const successPatterns = [ /SUCCESS/i, /complete(d)?/i, /passed/i, /\[OK\]/i, /✓/ ]; const failurePatterns = [ /ERROR/i, /FAILED/i, /ABORT/i, /\[FAIL\]/i, /✗/ ]; // Check for explicit failure first for (const pattern of failurePatterns){ if (pattern.test(output)) { return false; } } // Check for success patterns for (const pattern of successPatterns){ if (pattern.test(output)) { return true; } } // Default: assume success if no clear failure return output.trim().length > 0; } /** * Extract confidence from text */ extractConfidence(output) { // Look for explicit confidence marker const confidenceMatch = output.match(/confidence:?\s*(0?\.\d+|1\.0|0|1)/i); if (confidenceMatch) { return parseFloat(confidenceMatch[1]); } // Return default confidence return this.config.defaultConfidence; } /** * Extract deliverables from text */ extractDeliverables(output) { const deliverables = []; // Look for file patterns const patterns = [ /(?:created|modified|updated|generated):?\s+([^\s\n]+)/gi, /(?:file|path):?\s+([^\s\n]+)/gi ]; for (const pattern of patterns){ let match; while((match = pattern.exec(output)) !== null){ const file = match[1].trim(); if (file && !deliverables.includes(file)) { deliverables.push(file); } } } return deliverables; } /** * Extract metrics from text */ extractMetrics(output) { const metrics = {}; // Extract execution time const timeMatch = output.match(/(?:execution\s+time|duration|took):?\s*(\d+)\s*ms/i); if (timeMatch) { metrics.execution_time_ms = parseInt(timeMatch[1], 10); } // Extract file count const fileCountMatch = output.match(/(?:modified|created|updated)\s+(\d+)\s+files?/i); if (fileCountMatch) { metrics.files_modified = parseInt(fileCountMatch[1], 10); } return metrics; } /** * Extract errors from text */ extractErrors(output) { const errors = []; // Look for error patterns const errorMatches = output.matchAll(/ERROR:?\s*([^\n]+)/gi); for (const match of errorMatches){ errors.push({ code: 'LEGACY_ERROR', message: match[1].trim() }); } return errors; } // ========================================================================== // Utilities // ========================================================================== /** * Create default empty output */ createDefaultOutput() { return { success: false, confidence: 0.0, deliverables: [], metrics: {}, errors: [] }; } } // ============================================================================ // Exports // ============================================================================ export default SkillOutputParser; //# sourceMappingURL=skill-output-parser.js.map