UNPKG

shipdeck

Version:

Ship MVPs in 48 hours. Fix bugs in 30 seconds. The command deck for developers who ship.

1,373 lines (1,169 loc) 44.7 kB
/** * Result Synthesizer Agent for Shipdeck Ultimate * Combines outputs from parallel agent executions with intelligent merging, * conflict resolution, and quality assessment using LLM-powered analysis. */ const { BaseAgent } = require('./base-agent'); const fs = require('fs'); const path = require('path'); /** * Synthesis strategies for different types of content */ const SYNTHESIS_STRATEGIES = { UNION: 'union', // Combine all unique elements INTERSECTION: 'intersection', // Keep only common elements BEST_OF: 'best-of', // Select best solution using LLM judging HIERARCHICAL: 'hierarchical', // Merge based on priority/complexity SEMANTIC: 'semantic', // Merge based on semantic similarity CHRONOLOGICAL: 'chronological' // Merge based on execution order }; /** * Content types for intelligent processing */ const CONTENT_TYPES = { CODE: 'code', CONFIG: 'config', DOCUMENTATION: 'documentation', DATA: 'data', SCHEMA: 'schema', TEMPLATE: 'template', MIXED: 'mixed' }; /** * Conflict severity levels */ const CONFLICT_SEVERITY = { NONE: 'none', MINOR: 'minor', // Can be auto-resolved MODERATE: 'moderate', // Requires LLM guidance MAJOR: 'major', // Requires human oversight CRITICAL: 'critical' // Cannot be automatically resolved }; /** * Result Synthesizer Agent * Merges and synthesizes results from parallel agent executions */ class ResultSynthesizerAgent extends BaseAgent { constructor(options = {}) { super({ name: 'ResultSynthesizer', description: 'Combines outputs from parallel agent executions with intelligent merging, conflict resolution, and quality assessment', version: '1.0.0', ...options }); // Extended configuration for synthesis operations this.config = { ...this.config, defaultStrategy: options.defaultStrategy || SYNTHESIS_STRATEGIES.SEMANTIC, enableLLMJudging: options.enableLLMJudging !== false, conflictThreshold: options.conflictThreshold || 0.7, semanticSimilarityThreshold: options.semanticSimilarityThreshold || 0.8, maxVariantsToCompare: options.maxVariantsToCompare || 5, enableDeduplication: options.enableDeduplication !== false, preserveMetadata: options.preserveMetadata !== false, validationEnabled: options.validationEnabled !== false, outputFormat: options.outputFormat || 'structured' }; // Synthesis state tracking this.synthesisHistory = new Map(); this.conflictPatterns = new Map(); this.qualityMetrics = new Map(); // Performance tracking this.stats = { totalSyntheses: 0, conflictsResolved: 0, llmJudgingUsed: 0, averageSynthesisTime: 0, successRate: 0, qualityScore: 0 }; } /** * Get agent capabilities * @returns {Array<string>} Agent capabilities */ getCapabilities() { return [ 'merge', 'conflict-resolution', 'synthesis', 'validation', 'llm-judging', 'quality-assessment', 'deduplication', 'semantic-analysis' ]; } /** * Execute synthesis task - main entry point * @param {Object} task - Synthesis task configuration * @param {Object} context - Execution context * @returns {Promise<Object>} Synthesis result */ async execute(task, context = {}) { this.validateTask(task); const startTime = Date.now(); const synthesisId = this.generateSynthesisId(); try { this.log('info', `Starting synthesis: ${synthesisId}`, { resultCount: task.results?.length, strategy: task.strategy }); // Validate and prepare inputs const { results, metadata } = await this.prepareInputs(task, context); // Detect conflicts and choose strategy const conflicts = await this.detectConflicts(results); const strategy = this.selectOptimalStrategy(task.strategy, conflicts, results); // Perform synthesis based on strategy const synthesizedResult = await this.performSynthesis(results, strategy, conflicts, { ...context, synthesisId, metadata }); // Validate synthesized result if (this.config.validationEnabled) { await this.validateSynthesizedResult(synthesizedResult, results); } // Track performance metrics const duration = Date.now() - startTime; this.updateStats(duration, conflicts.length > 0, strategy); // Store synthesis history this.synthesisHistory.set(synthesisId, { inputs: results.map(r => ({ id: r.id, type: r.type })), strategy, conflicts, duration, timestamp: new Date().toISOString() }); return { success: true, result: synthesizedResult, metadata: { synthesisId, strategy, conflicts: conflicts.map(c => ({ type: c.type, severity: c.severity, resolved: c.resolved })), duration, qualityScore: synthesizedResult.qualityScore || 0, inputCount: results.length } }; } catch (error) { this.log('error', `Synthesis failed: ${error.message}`, { synthesisId, error: error.stack }); return { success: false, error: error.message, synthesisId, metadata: { duration: Date.now() - startTime, inputCount: task.results?.length || 0 } }; } } /** * Prepare and validate input results * @param {Object} task - Task configuration * @param {Object} context - Execution context * @returns {Promise<Object>} Prepared inputs with metadata */ async prepareInputs(task, context) { const { results = [], metadata = {} } = task; if (!Array.isArray(results) || results.length === 0) { throw new Error('Results array is required and must contain at least one result'); } // Normalize result format and add metadata const normalizedResults = results.map((result, index) => ({ id: result.id || `result-${index}`, content: result.content || result.data || result, type: this.detectContentType(result.content || result.data || result), source: result.source || result.agent || `agent-${index}`, timestamp: result.timestamp || new Date().toISOString(), metadata: result.metadata || {}, executionTime: result.executionTime || result.duration || 0, quality: result.quality || this.assessInitialQuality(result) })); // Validate each result for (const result of normalizedResults) { if (!result.content) { throw new Error(`Result ${result.id} has no content to synthesize`); } } this.log('info', `Prepared ${normalizedResults.length} results for synthesis`, { types: [...new Set(normalizedResults.map(r => r.type))], sources: [...new Set(normalizedResults.map(r => r.source))] }); return { results: normalizedResults, metadata }; } /** * Detect conflicts between results * @param {Array<Object>} results - Results to analyze * @returns {Promise<Array<Object>>} Detected conflicts */ async detectConflicts(results) { const conflicts = []; this.log('info', 'Analyzing results for conflicts'); // Compare each pair of results for (let i = 0; i < results.length - 1; i++) { for (let j = i + 1; j < results.length; j++) { const result1 = results[i]; const result2 = results[j]; const conflict = await this.analyzeConflictBetween(result1, result2); if (conflict.severity !== CONFLICT_SEVERITY.NONE) { conflicts.push({ ...conflict, results: [result1.id, result2.id], index: conflicts.length }); } } } // Detect multi-way conflicts const multiWayConflicts = await this.detectMultiWayConflicts(results); conflicts.push(...multiWayConflicts); this.log('info', `Detected ${conflicts.length} conflicts`, { severities: conflicts.reduce((acc, c) => { acc[c.severity] = (acc[c.severity] || 0) + 1; return acc; }, {}) }); return conflicts; } /** * Analyze conflict between two specific results * @param {Object} result1 - First result * @param {Object} result2 - Second result * @returns {Promise<Object>} Conflict analysis */ async analyzeConflictBetween(result1, result2) { // Quick checks for obvious conflicts if (result1.type !== result2.type) { return { type: 'type-mismatch', severity: CONFLICT_SEVERITY.MINOR, description: `Type mismatch: ${result1.type} vs ${result2.type}`, autoResolvable: true }; } // Content-based conflict detection const similarity = await this.calculateSimilarity(result1.content, result2.content); if (similarity < this.config.conflictThreshold) { // Use LLM to analyze semantic conflicts for code/complex content if (result1.type === CONTENT_TYPES.CODE || result1.type === CONTENT_TYPES.MIXED) { return await this.llmAnalyzeConflict(result1, result2); } return { type: 'content-divergence', severity: CONFLICT_SEVERITY.MODERATE, similarity, description: `Content similarity below threshold (${similarity.toFixed(3)} < ${this.config.conflictThreshold})`, autoResolvable: false }; } return { type: 'none', severity: CONFLICT_SEVERITY.NONE, similarity, autoResolvable: true }; } /** * Use LLM to analyze complex conflicts * @param {Object} result1 - First result * @param {Object} result2 - Second result * @returns {Promise<Object>} LLM conflict analysis */ async llmAnalyzeConflict(result1, result2) { const prompt = `Analyze these two results for conflicts: Result 1 (from ${result1.source}): ${this.formatContentForAnalysis(result1.content)} Result 2 (from ${result2.source}): ${this.formatContentForAnalysis(result2.content)} Please provide a JSON response with: { "conflictExists": boolean, "conflictType": "functional|structural|stylistic|logical|none", "severity": "minor|moderate|major|critical", "description": "detailed explanation", "autoResolvable": boolean, "suggestedResolution": "approach to resolve conflict" }`; try { const response = await this.sendMessage(prompt, { temperature: 0.3, maxTokens: 1024 }); if (response.success) { const analysis = this.parseResponse(response.content); if (typeof analysis === 'object' && analysis.conflictExists !== undefined) { return { type: analysis.conflictType || 'unknown', severity: analysis.severity || CONFLICT_SEVERITY.MODERATE, description: analysis.description || 'LLM-detected conflict', autoResolvable: analysis.autoResolvable || false, suggestedResolution: analysis.suggestedResolution, llmAnalyzed: true }; } } } catch (error) { this.log('warn', `LLM conflict analysis failed: ${error.message}`); } // Fallback to heuristic analysis return { type: 'complex-divergence', severity: CONFLICT_SEVERITY.MODERATE, description: 'Complex content differences detected (LLM analysis unavailable)', autoResolvable: false }; } /** * Detect multi-way conflicts (more than 2 results) * @param {Array<Object>} results - Results to analyze * @returns {Promise<Array<Object>>} Multi-way conflicts */ async detectMultiWayConflicts(results) { if (results.length <= 2) return []; const conflicts = []; const contentGroups = new Map(); // Group results by content similarity for (const result of results) { let assigned = false; for (const [representative, group] of contentGroups) { const similarity = await this.calculateSimilarity(result.content, representative.content); if (similarity >= this.config.semanticSimilarityThreshold) { group.push(result); assigned = true; break; } } if (!assigned) { contentGroups.set(result, [result]); } } // Conflicts exist when we have multiple distinct groups if (contentGroups.size > 1) { const groups = Array.from(contentGroups.values()); conflicts.push({ type: 'multi-way-divergence', severity: groups.length > 3 ? CONFLICT_SEVERITY.MAJOR : CONFLICT_SEVERITY.MODERATE, description: `Results split into ${groups.length} distinct approaches`, results: results.map(r => r.id), groups: groups.map(group => group.map(r => r.id)), autoResolvable: groups.length === 2 }); } return conflicts; } /** * Calculate content similarity between two pieces of content * @param {*} content1 - First content * @param {*} content2 - Second content * @returns {Promise<number>} Similarity score (0-1) */ async calculateSimilarity(content1, content2) { // Convert to strings for comparison const str1 = typeof content1 === 'string' ? content1 : JSON.stringify(content1); const str2 = typeof content2 === 'string' ? content2 : JSON.stringify(content2); if (str1 === str2) return 1.0; // Jaccard similarity for quick comparison const words1 = new Set(str1.toLowerCase().match(/\w+/g) || []); const words2 = new Set(str2.toLowerCase().match(/\w+/g) || []); const intersection = new Set([...words1].filter(w => words2.has(w))); const union = new Set([...words1, ...words2]); return union.size === 0 ? 0 : intersection.size / union.size; } /** * Select optimal synthesis strategy based on conflicts and content * @param {string} requestedStrategy - User-requested strategy * @param {Array<Object>} conflicts - Detected conflicts * @param {Array<Object>} results - Results to synthesize * @returns {string} Selected strategy */ selectOptimalStrategy(requestedStrategy, conflicts, results) { // Honor user's explicit strategy choice if (requestedStrategy && Object.values(SYNTHESIS_STRATEGIES).includes(requestedStrategy)) { this.log('info', `Using requested strategy: ${requestedStrategy}`); return requestedStrategy; } // Select strategy based on conflict analysis const hasConflicts = conflicts.some(c => c.severity !== CONFLICT_SEVERITY.NONE); const hasMajorConflicts = conflicts.some(c => [CONFLICT_SEVERITY.MAJOR, CONFLICT_SEVERITY.CRITICAL].includes(c.severity) ); // Strategy selection logic if (hasMajorConflicts) { return SYNTHESIS_STRATEGIES.BEST_OF; // Let LLM judge } if (hasConflicts) { return SYNTHESIS_STRATEGIES.SEMANTIC; // Smart merge } if (results.every(r => r.type === CONTENT_TYPES.CODE)) { return SYNTHESIS_STRATEGIES.UNION; // Combine code segments } if (results.length <= 2) { return SYNTHESIS_STRATEGIES.SEMANTIC; // Simple semantic merge } return this.config.defaultStrategy; } /** * Perform synthesis using selected strategy * @param {Array<Object>} results - Results to synthesize * @param {string} strategy - Synthesis strategy * @param {Array<Object>} conflicts - Detected conflicts * @param {Object} context - Execution context * @returns {Promise<Object>} Synthesized result */ async performSynthesis(results, strategy, conflicts, context) { this.log('info', `Performing synthesis using strategy: ${strategy}`); let synthesizedResult; switch (strategy) { case SYNTHESIS_STRATEGIES.UNION: synthesizedResult = await this.performUnionSynthesis(results, conflicts, context); break; case SYNTHESIS_STRATEGIES.INTERSECTION: synthesizedResult = await this.performIntersectionSynthesis(results, conflicts, context); break; case SYNTHESIS_STRATEGIES.BEST_OF: synthesizedResult = await this.performBestOfSynthesis(results, conflicts, context); break; case SYNTHESIS_STRATEGIES.HIERARCHICAL: synthesizedResult = await this.performHierarchicalSynthesis(results, conflicts, context); break; case SYNTHESIS_STRATEGIES.SEMANTIC: synthesizedResult = await this.performSemanticSynthesis(results, conflicts, context); break; case SYNTHESIS_STRATEGIES.CHRONOLOGICAL: synthesizedResult = await this.performChronologicalSynthesis(results, conflicts, context); break; default: throw new Error(`Unknown synthesis strategy: ${strategy}`); } // Add synthesis metadata synthesizedResult.synthesisMetadata = { strategy, inputCount: results.length, conflictsResolved: conflicts.filter(c => c.resolved).length, timestamp: new Date().toISOString(), synthesisId: context.synthesisId }; return synthesizedResult; } /** * Union synthesis - combine all unique elements * @param {Array<Object>} results - Results to synthesize * @param {Array<Object>} conflicts - Conflicts to resolve * @param {Object} context - Execution context * @returns {Promise<Object>} Union result */ async performUnionSynthesis(results, conflicts, context) { const combined = { type: 'union', content: {}, sources: results.map(r => r.source), quality: 0 }; // Combine content based on type if (results.every(r => r.type === CONTENT_TYPES.CODE)) { combined.content = await this.unionCodeResults(results); } else if (results.every(r => r.type === CONTENT_TYPES.DATA)) { combined.content = await this.unionDataResults(results); } else if (results.every(r => r.type === CONTENT_TYPES.CONFIG)) { combined.content = await this.unionConfigResults(results); } else { // Mixed content - organize by type combined.content = await this.unionMixedResults(results); } // Mark conflicts as resolved through inclusion conflicts.forEach(conflict => { if (conflict.autoResolvable) { conflict.resolved = true; conflict.resolution = 'included-all-variants'; } }); // Calculate union quality score combined.quality = this.calculateUnionQuality(results, conflicts); return combined; } /** * Best-of synthesis using LLM judging * @param {Array<Object>} results - Results to judge * @param {Array<Object>} conflicts - Conflicts to consider * @param {Object} context - Execution context * @returns {Promise<Object>} Best result */ async performBestOfSynthesis(results, conflicts, context) { if (!this.config.enableLLMJudging) { // Fallback to quality-based selection return this.selectBestByQuality(results, conflicts); } // Limit results for comparison to avoid context overflow const resultsToCompare = results.slice(0, this.config.maxVariantsToCompare); const prompt = this.createJudgingPrompt(resultsToCompare, conflicts, context); try { const response = await this.sendMessage(prompt, { temperature: 0.2, maxTokens: 2048 }); if (response.success) { const judgment = this.parseJudgmentResponse(response.content); const selectedResult = this.applyJudgment(resultsToCompare, judgment, conflicts); this.stats.llmJudgingUsed++; return selectedResult; } } catch (error) { this.log('warn', `LLM judging failed, falling back to quality selection: ${error.message}`); } return this.selectBestByQuality(results, conflicts); } /** * Semantic synthesis - intelligent content merging * @param {Array<Object>} results - Results to merge * @param {Array<Object>} conflicts - Conflicts to resolve * @param {Object} context - Execution context * @returns {Promise<Object>} Semantically merged result */ async performSemanticSynthesis(results, conflicts, context) { const prompt = `Perform intelligent semantic synthesis of these ${results.length} results: ${results.map((result, i) => ` Result ${i + 1} (from ${result.source}): ${this.formatContentForAnalysis(result.content)} `).join('\n')} Conflicts detected: ${conflicts.map(c => `- ${c.type}: ${c.description}`).join('\n') || 'None'} Please provide a synthesized result that: 1. Combines the best aspects of each input 2. Resolves conflicts intelligently 3. Maintains consistency and quality 4. Preserves important functionality Return as JSON: { "synthesizedContent": "the merged result", "rationale": "explanation of synthesis decisions", "conflictsResolved": ["list of resolved conflicts"], "qualityScore": number_between_0_and_1 }`; try { const response = await this.sendMessage(prompt, { temperature: 0.4, maxTokens: 4096 }); if (response.success) { const synthesis = this.parseResponse(response.content); if (typeof synthesis === 'object' && synthesis.synthesizedContent) { // Mark resolved conflicts conflicts.forEach(conflict => { if (synthesis.conflictsResolved?.includes(conflict.type)) { conflict.resolved = true; conflict.resolution = 'semantic-merge'; } }); return { type: 'semantic', content: synthesis.synthesizedContent, rationale: synthesis.rationale, sources: results.map(r => r.source), quality: synthesis.qualityScore || 0.8, llmSynthesized: true }; } } } catch (error) { this.log('warn', `Semantic synthesis failed, falling back to union: ${error.message}`); } // Fallback to union strategy return this.performUnionSynthesis(results, conflicts, context); } /** * Hierarchical synthesis - merge based on priority/quality * @param {Array<Object>} results - Results to merge * @param {Array<Object>} conflicts - Conflicts to consider * @param {Object} context - Execution context * @returns {Promise<Object>} Hierarchically merged result */ async performHierarchicalSynthesis(results, conflicts, context) { // Sort by quality/priority const sortedResults = [...results].sort((a, b) => { const priorityA = this.calculateResultPriority(a); const priorityB = this.calculateResultPriority(b); return priorityB - priorityA; }); // Start with highest priority result as base const baseResult = { ...sortedResults[0] }; baseResult.type = 'hierarchical'; // Progressively merge lower priority results for (let i = 1; i < sortedResults.length; i++) { const resultToMerge = sortedResults[i]; baseResult.content = await this.hierarchicalMergeContent( baseResult.content, resultToMerge.content, baseResult.type || CONTENT_TYPES.MIXED, context ); baseResult.sources.push(resultToMerge.source); } // Mark auto-resolvable conflicts as resolved conflicts.forEach(conflict => { if (conflict.autoResolvable) { conflict.resolved = true; conflict.resolution = 'hierarchical-priority'; } }); baseResult.quality = this.calculateHierarchicalQuality(sortedResults, conflicts); return baseResult; } /** * Chronological synthesis - merge by execution order * @param {Array<Object>} results - Results to merge * @param {Array<Object>} conflicts - Conflicts to consider * @param {Object} context - Execution context * @returns {Promise<Object>} Chronologically merged result */ async performChronologicalSynthesis(results, conflicts, context) { // Sort by timestamp const sortedResults = [...results].sort((a, b) => new Date(a.timestamp) - new Date(b.timestamp) ); const merged = { type: 'chronological', content: {}, timeline: [], sources: [], quality: 0 }; // Build chronological content history for (const result of sortedResults) { merged.timeline.push({ timestamp: result.timestamp, source: result.source, content: result.content, changes: this.detectChanges(merged.content, result.content) }); // Apply changes chronologically merged.content = await this.applyChronologicalChange( merged.content, result.content, result.type ); merged.sources.push(result.source); } // Chronological merge resolves conflicts by latest wins conflicts.forEach(conflict => { conflict.resolved = true; conflict.resolution = 'latest-wins'; }); merged.quality = this.calculateChronologicalQuality(sortedResults, conflicts); return merged; } /** * Intersection synthesis - keep only common elements * @param {Array<Object>} results - Results to intersect * @param {Array<Object>} conflicts - Conflicts to consider * @param {Object} context - Execution context * @returns {Promise<Object>} Intersection result */ async performIntersectionSynthesis(results, conflicts, context) { const intersection = { type: 'intersection', content: {}, sources: results.map(r => r.source), quality: 0 }; if (results.every(r => r.type === CONTENT_TYPES.CODE)) { intersection.content = await this.intersectCodeResults(results); } else if (results.every(r => r.type === CONTENT_TYPES.DATA)) { intersection.content = await this.intersectDataResults(results); } else { intersection.content = await this.intersectGenericResults(results); } // Intersection resolves conflicts by keeping only agreements conflicts.forEach(conflict => { conflict.resolved = true; conflict.resolution = 'kept-common-elements'; }); intersection.quality = this.calculateIntersectionQuality(results, conflicts); return intersection; } /** * Union synthesis for code results * @param {Array<Object>} results - Code results to union * @returns {Promise<*>} Combined code content */ async unionCodeResults(results) { const combined = { functions: [], classes: [], imports: [], constants: [], comments: [], raw: [] }; for (const result of results) { const content = result.content; if (typeof content === 'string') { combined.raw.push(`// From ${result.source}\n${content}`); } else if (typeof content === 'object') { // Merge structured code objects Object.keys(combined).forEach(key => { if (content[key] && Array.isArray(content[key])) { combined[key] = [...combined[key], ...content[key]]; } }); } } // Deduplicate if enabled if (this.config.enableDeduplication) { Object.keys(combined).forEach(key => { combined[key] = this.deduplicateArray(combined[key]); }); } return combined.raw.length > 0 ? combined.raw.join('\n\n') : combined; } /** * Union synthesis for data results * @param {Array<Object>} results - Data results to union * @returns {Promise<*>} Combined data content */ async unionDataResults(results) { const combined = {}; for (const result of results) { const content = result.content; if (Array.isArray(content)) { combined[result.source] = content; } else if (typeof content === 'object') { Object.assign(combined, content); } else { combined[result.source] = content; } } return combined; } /** * Union synthesis for configuration results * @param {Array<Object>} results - Config results to union * @returns {Promise<*>} Combined configuration */ async unionConfigResults(results) { const combined = {}; for (const result of results) { const content = result.content; if (typeof content === 'object') { // Deep merge configurations this.deepMerge(combined, content); } } return combined; } /** * Deep merge objects * @param {Object} target - Target object * @param {Object} source - Source object to merge * @returns {Object} Merged object */ deepMerge(target, source) { for (const key in source) { if (source.hasOwnProperty(key)) { if (typeof source[key] === 'object' && source[key] !== null && !Array.isArray(source[key])) { if (!target[key] || typeof target[key] !== 'object') { target[key] = {}; } this.deepMerge(target[key], source[key]); } else { target[key] = source[key]; } } } return target; } /** * Validate synthesized result against inputs * @param {Object} synthesizedResult - Result to validate * @param {Array<Object>} originalResults - Original input results * @returns {Promise<boolean>} Validation success */ async validateSynthesizedResult(synthesizedResult, originalResults) { this.log('info', 'Validating synthesized result'); // Basic validation checks if (!synthesizedResult || !synthesizedResult.content) { throw new Error('Synthesized result has no content'); } // Content type validation const expectedType = this.detectContentType(synthesizedResult.content); if (expectedType === CONTENT_TYPES.MIXED && originalResults.length === 1) { this.log('warn', 'Single input produced mixed content type - potential synthesis issue'); } // Quality validation if (synthesizedResult.quality !== undefined && synthesizedResult.quality < 0.3) { this.log('warn', `Low quality score: ${synthesizedResult.quality}`, { threshold: 0.3 }); } // Source validation if (!synthesizedResult.sources || synthesizedResult.sources.length === 0) { synthesizedResult.sources = originalResults.map(r => r.source); } return true; } /** * Detect content type of a given content * @param {*} content - Content to analyze * @returns {string} Detected content type */ detectContentType(content) { if (typeof content === 'string') { // Check for code patterns if (content.includes('function') || content.includes('class') || content.includes('import') || content.includes('=>')) { return CONTENT_TYPES.CODE; } // Check for config patterns if (content.includes('{') && (content.includes('"') || content.includes("'"))) { return CONTENT_TYPES.CONFIG; } // Check for markdown/doc patterns if (content.includes('#') || content.includes('**') || content.includes('*')) { return CONTENT_TYPES.DOCUMENTATION; } return CONTENT_TYPES.TEMPLATE; } if (Array.isArray(content)) { return CONTENT_TYPES.DATA; } if (typeof content === 'object') { // Check for schema patterns if (content.type || content.properties || content.schema) { return CONTENT_TYPES.SCHEMA; } return CONTENT_TYPES.CONFIG; } return CONTENT_TYPES.MIXED; } /** * Assess initial quality of a result * @param {Object} result - Result to assess * @returns {number} Quality score (0-1) */ assessInitialQuality(result) { let score = 0.5; // Base score // Content length factor const contentLength = JSON.stringify(result.content || result).length; if (contentLength > 100) score += 0.1; if (contentLength > 1000) score += 0.1; // Execution time factor (faster is better for similar content) if (result.executionTime && result.executionTime < 5000) score += 0.1; // Metadata presence if (result.metadata && Object.keys(result.metadata).length > 0) score += 0.1; // Source information if (result.source && result.source !== 'unknown') score += 0.1; return Math.min(score, 1.0); } /** * Calculate result priority for hierarchical synthesis * @param {Object} result - Result to evaluate * @returns {number} Priority score */ calculateResultPriority(result) { let priority = result.quality || 0.5; // Boost priority for successful executions if (result.metadata?.success === true) priority += 0.2; // Boost priority for detailed/comprehensive results const contentSize = JSON.stringify(result.content).length; if (contentSize > 500) priority += 0.1; if (contentSize > 2000) priority += 0.1; // Boost priority for well-structured sources if (result.source && !result.source.includes('unknown')) priority += 0.1; return Math.min(priority, 1.0); } /** * Format content for LLM analysis * @param {*} content - Content to format * @returns {string} Formatted content */ formatContentForAnalysis(content) { if (typeof content === 'string') { return content.length > 2000 ? content.substring(0, 2000) + '...' : content; } const jsonString = JSON.stringify(content, null, 2); return jsonString.length > 2000 ? jsonString.substring(0, 2000) + '...' : jsonString; } /** * Create judging prompt for LLM-based result selection * @param {Array<Object>} results - Results to compare * @param {Array<Object>} conflicts - Conflicts to consider * @param {Object} context - Execution context * @returns {string} LLM judging prompt */ createJudgingPrompt(results, conflicts, context) { return `You are an expert code and solution judge. Please analyze these ${results.length} different results and select the best one. ${results.map((result, i) => ` === RESULT ${i + 1} === Source: ${result.source} Type: ${result.type} Quality Score: ${result.quality} Execution Time: ${result.executionTime}ms Content: ${this.formatContentForAnalysis(result.content)} `).join('\n')} Conflicts Detected: ${conflicts.map(c => `- ${c.type}: ${c.description} (Severity: ${c.severity})`).join('\n') || 'None'} Context: ${context.metadata ? JSON.stringify(context.metadata) : 'None provided'} Please evaluate based on: 1. Code quality and best practices 2. Completeness and functionality 3. Performance and efficiency 4. Maintainability and readability 5. Conflict resolution capability Return JSON: { "selectedIndex": number_from_0_to_${results.length - 1}, "rationale": "detailed explanation of choice", "qualityAssessment": { "codeQuality": number_0_to_1, "completeness": number_0_to_1, "performance": number_0_to_1, "maintainability": number_0_to_1 }, "improvementSuggestions": ["list of suggestions"] }`; } /** * Parse LLM judgment response * @param {string} response - Raw LLM response * @returns {Object} Parsed judgment */ parseJudgmentResponse(response) { try { const judgment = this.parseResponse(response); if (typeof judgment === 'object' && judgment.selectedIndex !== undefined) { return judgment; } } catch (error) { this.log('warn', `Failed to parse judgment response: ${error.message}`); } // Return default judgment return { selectedIndex: 0, rationale: 'Default selection due to parsing failure', qualityAssessment: { codeQuality: 0.5, completeness: 0.5, performance: 0.5, maintainability: 0.5 }, improvementSuggestions: [] }; } /** * Apply LLM judgment to select best result * @param {Array<Object>} results - Results to choose from * @param {Object} judgment - LLM judgment * @param {Array<Object>} conflicts - Conflicts to mark resolved * @returns {Object} Selected and enhanced result */ applyJudgment(results, judgment, conflicts) { const selectedIndex = Math.max(0, Math.min(judgment.selectedIndex, results.length - 1)); const selectedResult = { ...results[selectedIndex] }; // Enhance with judgment metadata selectedResult.type = 'best-of'; selectedResult.judgment = { rationale: judgment.rationale, qualityAssessment: judgment.qualityAssessment, improvementSuggestions: judgment.improvementSuggestions || [], alternativesConsidered: results.length, selectedFrom: results.map(r => r.source) }; // Update quality based on assessment if (judgment.qualityAssessment) { const avgQuality = Object.values(judgment.qualityAssessment) .reduce((sum, score) => sum + score, 0) / Object.keys(judgment.qualityAssessment).length; selectedResult.quality = Math.max(selectedResult.quality, avgQuality); } // Mark conflicts as resolved through selection conflicts.forEach(conflict => { conflict.resolved = true; conflict.resolution = 'llm-judged-best'; }); return selectedResult; } /** * Select best result by quality score (fallback method) * @param {Array<Object>} results - Results to choose from * @param {Array<Object>} conflicts - Conflicts to mark resolved * @returns {Object} Best result by quality */ selectBestByQuality(results, conflicts) { const sortedByQuality = [...results].sort((a, b) => b.quality - a.quality); const bestResult = { ...sortedByQuality[0] }; bestResult.type = 'quality-selected'; bestResult.selectionReason = `Highest quality score: ${bestResult.quality}`; // Mark conflicts as resolved conflicts.forEach(conflict => { conflict.resolved = true; conflict.resolution = 'quality-based-selection'; }); return bestResult; } /** * Calculate quality score for union synthesis * @param {Array<Object>} results - Original results * @param {Array<Object>} conflicts - Conflicts considered * @returns {number} Quality score */ calculateUnionQuality(results, conflicts) { const avgInputQuality = results.reduce((sum, r) => sum + r.quality, 0) / results.length; const conflictPenalty = conflicts.length * 0.1; return Math.max(0, Math.min(1, avgInputQuality - conflictPenalty + 0.1)); // Union bonus } /** * Calculate quality score for hierarchical synthesis * @param {Array<Object>} results - Original results (sorted by priority) * @param {Array<Object>} conflicts - Conflicts considered * @returns {number} Quality score */ calculateHierarchicalQuality(results, conflicts) { // Weight towards highest priority results const weightedQuality = results.reduce((sum, result, index) => { const weight = 1 / (index + 1); // Diminishing weight return sum + (result.quality * weight); }, 0) / results.length; const conflictPenalty = conflicts.filter(c => !c.resolved).length * 0.05; return Math.max(0, Math.min(1, weightedQuality - conflictPenalty)); } /** * Calculate quality score for chronological synthesis * @param {Array<Object>} results - Original results (sorted by time) * @param {Array<Object>} conflicts - Conflicts considered * @returns {number} Quality score */ calculateChronologicalQuality(results, conflicts) { // Later results are generally better (more recent) const latestResult = results[results.length - 1]; const evolutionBonus = results.length > 2 ? 0.1 : 0; // Bonus for evolution const conflictPenalty = conflicts.length * 0.05; // Minor penalty return Math.max(0, Math.min(1, latestResult.quality + evolutionBonus - conflictPenalty)); } /** * Calculate quality score for intersection synthesis * @param {Array<Object>} results - Original results * @param {Array<Object>} conflicts - Conflicts considered * @returns {number} Quality score */ calculateIntersectionQuality(results, conflicts) { const avgInputQuality = results.reduce((sum, r) => sum + r.quality, 0) / results.length; const agreementBonus = 0.2; // Bonus for agreement const contentReductionPenalty = 0.1; // Penalty for potentially losing content return Math.max(0, Math.min(1, avgInputQuality + agreementBonus - contentReductionPenalty)); } /** * Deduplicate an array of items * @param {Array} array - Array to deduplicate * @returns {Array} Deduplicated array */ deduplicateArray(array) { if (!Array.isArray(array)) return array; return array.filter((item, index, arr) => { const itemStr = typeof item === 'object' ? JSON.stringify(item) : String(item); return arr.findIndex(otherItem => { const otherStr = typeof otherItem === 'object' ? JSON.stringify(otherItem) : String(otherItem); return itemStr === otherStr; }) === index; }); } /** * Generate unique synthesis ID * @returns {string} Synthesis ID */ generateSynthesisId() { return `synthesis-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; } /** * Update performance statistics * @param {number} duration - Synthesis duration in ms * @param {boolean} hadConflicts - Whether conflicts were present * @param {string} strategy - Strategy used */ updateStats(duration, hadConflicts, strategy) { this.stats.totalSyntheses++; if (hadConflicts) { this.stats.conflictsResolved++; } // Update average synthesis time this.stats.averageSynthesisTime = (this.stats.averageSynthesisTime * (this.stats.totalSyntheses - 1) + duration) / this.stats.totalSyntheses; // Update success rate (assume success if no error thrown) this.stats.successRate = this.stats.totalSyntheses / this.stats.totalSyntheses; // Will be adjusted for failures this.log('info', `Synthesis completed in ${duration}ms using ${strategy}`, { totalSyntheses: this.stats.totalSyntheses, hadConflicts }); } /** * Get comprehensive statistics * @returns {Object} Current statistics */ getStats() { return { ...this.stats, synthesisHistory: this.synthesisHistory.size, conflictPatterns: this.conflictPatterns.size, qualityMetrics: this.qualityMetrics.size }; } /** * Get system prompt for the agent * @returns {string} System prompt */ getSystemPrompt() { return `You are the Result Synthesizer Agent for Shipdeck Ultimate. Your role is to intelligently combine outputs from parallel agent executions with sophisticated merging, conflict resolution, and quality assessment. Key capabilities: - Smart conflict detection and resolution between different agent outputs - Multiple synthesis strategies (union, intersection, best-of, hierarchical, semantic, chronological) - LLM-powered quality assessment and intelligent merging decisions - Code deduplication and semantic analysis for improved results - Production-ready error handling and comprehensive validation You excel at: - Analyzing conflicts between different approaches and solutions - Selecting optimal merge strategies based on content type and conflict patterns - Using LLM judging to select best solutions from multiple variants - Performing semantic merging that preserves functionality while improving quality - Resolving conflicts through intelligent compromise and quality-based decisions When synthesizing results, you automatically: 1. Analyze input results for conflicts, similarity, and quality 2. Select optimal synthesis strategy based on content and conflict analysis 3. Apply advanced merging techniques appropriate for the content type 4. Resolve conflicts using LLM-powered analysis when needed 5. Validate synthesized results for quality and consistency 6. Provide detailed metadata about synthesis decisions and conflict resolutions Always prioritize result quality, functional correctness, and maintainability in your synthesis decisions.`; } /** * Cleanup resources on shutdown */ cleanup() { this.log('info', 'Starting result synthesizer cleanup'); // Clear state this.synthesisHistory.clear(); this.conflictPatterns.clear(); this.qualityMetrics.clear(); this.log('info', 'Result synthesizer cleanup completed', { totalSyntheses: this.stats.totalSyntheses, conflictsResolved: this.stats.conflictsResolved }); super.cleanup(); } } module.exports = { ResultSynthesizerAgent, SYNTHESIS_STRATEGIES, CONTENT_TYPES, CONFLICT_SEVERITY };