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
JavaScript
/**
* 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
};