UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

797 lines (792 loc) 37.3 kB
import { getEpicService } from './epic-service.js'; import { getTaskOperations } from '../core/operations/task-operations.js'; import { getDependencyOperations } from '../core/operations/dependency-operations.js'; import { DependencyValidator } from './dependency-validator.js'; import logger from '../../../logger.js'; const DEFAULT_EPIC_CONFIG = { minDependencyStrength: 0.3, maxEpicDepth: 5, autoGeneratePhases: true, enableParallelization: true, minTasksPerEpic: 2, enableIntelligentDiscovery: true, enableFilePathAnalysis: true, enableSemanticAnalysis: true }; export class EpicDependencyManager { config; dependencyValidator; constructor(config = {}) { this.config = { ...DEFAULT_EPIC_CONFIG, ...config }; this.dependencyValidator = new DependencyValidator(); } async analyzeEpicDependencies(projectId) { const startTime = Date.now(); try { logger.info({ projectId }, 'Starting epic dependency analysis'); const epicService = getEpicService(); const taskOps = getTaskOperations(); const dependencyOps = getDependencyOperations(); const epicsResult = await epicService.listEpics({ projectId }); if (!epicsResult.success) { throw new Error(`Failed to get epics: ${epicsResult.error}`); } const tasksResult = await taskOps.listTasks({ projectId }); if (!tasksResult.success) { throw new Error(`Failed to get tasks: ${tasksResult.error}`); } const epics = epicsResult.data || []; const tasks = tasksResult.data || []; const allTaskDependencies = []; for (const task of tasks) { const taskDepsResult = await dependencyOps.getDependenciesForTask(task.id); if (taskDepsResult.success && taskDepsResult.data) { allTaskDependencies.push(...taskDepsResult.data); } } const epicDependencies = await this.deriveEpicDependencies(epics, tasks, allTaskDependencies); const epicExecutionOrder = await this.calculateEpicExecutionOrder(epics, epicDependencies); const phases = this.config.autoGeneratePhases ? await this.generateProjectPhases(epics, epicDependencies, epicExecutionOrder) : []; const conflicts = await this.detectEpicConflicts(epics, epicDependencies, tasks); const recommendations = await this.generateEpicRecommendations(epics, epicDependencies, tasks, allTaskDependencies); const analysisTime = Date.now() - startTime; const analysis = { epicDependencies, epicExecutionOrder, phases, conflicts, recommendations, metadata: { analyzedAt: new Date(), projectId, totalEpics: epics.length, totalTaskDependencies: allTaskDependencies.length, analysisTime } }; logger.info({ projectId, epicDependencies: epicDependencies.length, phases: phases.length, conflicts: conflicts.length, recommendations: recommendations.length, analysisTime }, 'Epic dependency analysis completed'); return { success: true, data: analysis, metadata: { filePath: 'epic-dependency-manager', operation: 'analyze_epic_dependencies', timestamp: new Date() } }; } catch (error) { const analysisTime = Date.now() - startTime; logger.error({ err: error, projectId, analysisTime }, 'Epic dependency analysis failed'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'epic-dependency-manager', operation: 'analyze_epic_dependencies', timestamp: new Date() } }; } } async createEpicDependency(fromEpicId, toEpicId, taskDependencies, createdBy = 'system') { try { logger.info({ fromEpicId, toEpicId, taskDependencyCount: taskDependencies.length, createdBy }, 'Creating epic dependency'); const validationResult = await this.validateEpicDependency(fromEpicId, toEpicId); if (!validationResult.isValid) { return { success: false, error: `Epic dependency validation failed: ${validationResult.errors.map((e) => e.message).join(', ')}`, metadata: { filePath: 'epic-dependency-manager', operation: 'create_epic_dependency', timestamp: new Date() } }; } const strength = await this.calculateDependencyStrength(fromEpicId, toEpicId, taskDependencies); const epicDependency = { id: `epic-dep-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, fromEpicId, toEpicId, type: strength > 0.7 ? 'blocks' : strength > 0.5 ? 'requires' : 'suggests', description: `Epic dependency derived from ${taskDependencies.length} task dependencies`, critical: strength > 0.7, strength, metadata: { createdAt: new Date(), createdBy, reason: `Derived from task dependencies with strength ${strength.toFixed(2)}`, taskDependencies } }; await this.updateEpicDependencyLists(fromEpicId, toEpicId, epicDependency.id); logger.info({ epicDependencyId: epicDependency.id, fromEpicId, toEpicId, strength }, 'Epic dependency created successfully'); return { success: true, data: epicDependency, metadata: { filePath: 'epic-dependency-manager', operation: 'create_epic_dependency', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, fromEpicId, toEpicId }, 'Failed to create epic dependency'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'epic-dependency-manager', operation: 'create_epic_dependency', timestamp: new Date() } }; } } async deriveEpicDependencies(epics, tasks, taskDependencies) { const epicDependencies = []; const epicTaskMap = new Map(); epics.forEach(epic => { epicTaskMap.set(epic.id, epic.taskIds); }); const epicDependencyMap = new Map(); for (const taskDep of taskDependencies) { const fromTask = tasks.find(t => t.id === taskDep.fromTaskId); const toTask = tasks.find(t => t.id === taskDep.toTaskId); if (!fromTask || !toTask || !fromTask.epicId || !toTask.epicId) continue; if (fromTask.epicId === toTask.epicId) continue; const epicPairKey = `${fromTask.epicId}->${toTask.epicId}`; if (!epicDependencyMap.has(epicPairKey)) { epicDependencyMap.set(epicPairKey, []); } epicDependencyMap.get(epicPairKey).push(taskDep.id); } for (const [epicPairKey, taskDepIds] of epicDependencyMap) { const [fromEpicId, toEpicId] = epicPairKey.split('->'); const strength = await this.calculateDependencyStrength(fromEpicId, toEpicId, taskDepIds); if (strength >= this.config.minDependencyStrength) { const epicDependency = { id: `epic-dep-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`, fromEpicId, toEpicId, type: strength > 0.7 ? 'blocks' : strength > 0.5 ? 'requires' : 'suggests', description: `Derived from ${taskDepIds.length} task dependencies (strength: ${strength.toFixed(2)})`, critical: strength > 0.7, strength, metadata: { createdAt: new Date(), createdBy: 'system', reason: `Auto-derived from task dependency analysis`, taskDependencies: taskDepIds } }; epicDependencies.push(epicDependency); } } return epicDependencies; } async calculateDependencyStrength(fromEpicId, toEpicId, taskDependencies) { try { const epicService = getEpicService(); const fromEpicResult = await epicService.getEpic(fromEpicId); const toEpicResult = await epicService.getEpic(toEpicId); if (!fromEpicResult.success || !toEpicResult.success) { return 0; } const fromEpic = fromEpicResult.data; const toEpic = toEpicResult.data; const fromTaskCount = fromEpic.taskIds.length; const toTaskCount = toEpic.taskIds.length; const dependencyCount = taskDependencies.length; const maxPossibleDependencies = fromTaskCount * toTaskCount; const densityStrength = maxPossibleDependencies > 0 ? dependencyCount / maxPossibleDependencies : 0; const proportionStrength = Math.min(dependencyCount / Math.max(fromTaskCount, toTaskCount), 1); const strength = (densityStrength * 0.4) + (proportionStrength * 0.6); return Math.min(strength, 1); } catch (error) { logger.warn({ err: error, fromEpicId, toEpicId }, 'Failed to calculate dependency strength'); return 0; } } async calculateEpicExecutionOrder(epics, epicDependencies) { const inDegree = new Map(); const adjacencyList = new Map(); epics.forEach(epic => { inDegree.set(epic.id, 0); adjacencyList.set(epic.id, []); }); epicDependencies.forEach(dep => { adjacencyList.get(dep.fromEpicId)?.push(dep.toEpicId); inDegree.set(dep.toEpicId, (inDegree.get(dep.toEpicId) || 0) + 1); }); const queue = []; const result = []; for (const [epicId, degree] of inDegree) { if (degree === 0) { queue.push(epicId); } } while (queue.length > 0) { const epicId = queue.shift(); result.push(epicId); const dependents = adjacencyList.get(epicId) || []; for (const dependent of dependents) { const newDegree = (inDegree.get(dependent) || 0) - 1; inDegree.set(dependent, newDegree); if (newDegree === 0) { queue.push(dependent); } } } return result; } async generateProjectPhases(epics, epicDependencies, executionOrder) { const phases = []; const epicToPhase = new Map(); let currentPhase = 0; const processedEpics = new Set(); while (processedEpics.size < epics.length) { const currentPhaseEpics = []; for (const epicId of executionOrder) { if (processedEpics.has(epicId)) continue; const dependencies = epicDependencies.filter(dep => dep.toEpicId === epicId); const allDependenciesSatisfied = dependencies.every(dep => processedEpics.has(dep.fromEpicId)); if (allDependenciesSatisfied) { currentPhaseEpics.push(epicId); epicToPhase.set(epicId, currentPhase); } } if (currentPhaseEpics.length === 0) { for (const epicId of executionOrder) { if (!processedEpics.has(epicId)) { currentPhaseEpics.push(epicId); epicToPhase.set(epicId, currentPhase); } } } const phaseEpics = epics.filter(epic => currentPhaseEpics.includes(epic.id)); const estimatedDuration = Math.max(...phaseEpics.map(epic => epic.estimatedHours)); const phase = { id: `phase-${currentPhase + 1}`, name: `Phase ${currentPhase + 1}`, description: `Project phase containing ${currentPhaseEpics.length} epic(s)`, epicIds: currentPhaseEpics, order: currentPhase, estimatedDuration, canRunInParallel: currentPhaseEpics.length > 1, prerequisites: currentPhase > 0 ? [`phase-${currentPhase}`] : [] }; phases.push(phase); currentPhaseEpics.forEach(epicId => processedEpics.add(epicId)); currentPhase++; } return phases; } async detectEpicConflicts(epics, epicDependencies, tasks) { const conflicts = []; const circularDeps = await this.detectCircularEpicDependencies(epics, epicDependencies); conflicts.push(...circularDeps); const priorityConflicts = await this.detectPriorityConflicts(epics, epicDependencies); conflicts.push(...priorityConflicts); const resourceConflicts = await this.detectResourceConflicts(epics, tasks); conflicts.push(...resourceConflicts); return conflicts; } async generateEpicRecommendations(epics, epicDependencies, tasks, _taskDependencies) { const recommendations = []; if (this.config.enableParallelization) { const parallelizationRecs = await this.identifyParallelizationOpportunities(epics, epicDependencies); recommendations.push(...parallelizationRecs); } const splittingRecs = await this.identifyEpicSplittingOpportunities(epics, tasks); recommendations.push(...splittingRecs); const mergingRecs = await this.identifyEpicMergingOpportunities(epics, epicDependencies); recommendations.push(...mergingRecs); return recommendations; } async validateEpicDependency(fromEpicId, toEpicId) { return await this.dependencyValidator.validateDependencyBeforeCreation(fromEpicId, toEpicId, 'project-id'); } async updateEpicDependencyLists(fromEpicId, toEpicId, dependencyId) { try { const epicService = getEpicService(); const toEpicResult = await epicService.getEpic(toEpicId); if (toEpicResult.success) { const toEpic = toEpicResult.data; if (!toEpic.dependencies.includes(fromEpicId)) { toEpic.dependencies.push(fromEpicId); await epicService.updateEpic(toEpicId, { dependencies: toEpic.dependencies }); } } } catch (error) { logger.warn({ err: error, fromEpicId, toEpicId, dependencyId }, 'Failed to update epic dependency lists'); } } async detectCircularEpicDependencies(epics, epicDependencies) { const conflicts = []; const visited = new Set(); const recursionStack = new Set(); const adjacencyList = new Map(); epics.forEach(epic => adjacencyList.set(epic.id, [])); epicDependencies.forEach(dep => { const dependents = adjacencyList.get(dep.fromEpicId) || []; dependents.push(dep.toEpicId); adjacencyList.set(dep.fromEpicId, dependents); }); const dfs = (epicId, path) => { if (recursionStack.has(epicId)) { const cycleStart = path.indexOf(epicId); const cycle = path.slice(cycleStart).concat([epicId]); conflicts.push({ type: 'circular_dependency', severity: 'critical', description: `Circular epic dependency detected: ${cycle.join(' → ')}`, affectedEpics: cycle, resolutionOptions: [{ type: 'reorder', description: 'Reorder epics to break the circular dependency', complexity: 'medium' }] }); return true; } if (visited.has(epicId)) { return false; } visited.add(epicId); recursionStack.add(epicId); path.push(epicId); const dependents = adjacencyList.get(epicId) || []; for (const dependent of dependents) { if (dfs(dependent, [...path])) { } } recursionStack.delete(epicId); return false; }; for (const epic of epics) { if (!visited.has(epic.id)) { dfs(epic.id, []); } } return conflicts; } async detectPriorityConflicts(epics, epicDependencies) { const conflicts = []; const priorityOrder = { 'critical': 4, 'high': 3, 'medium': 2, 'low': 1 }; for (const dep of epicDependencies) { const fromEpic = epics.find(e => e.id === dep.fromEpicId); const toEpic = epics.find(e => e.id === dep.toEpicId); if (!fromEpic || !toEpic) continue; const fromPriority = priorityOrder[fromEpic.priority] || 0; const toPriority = priorityOrder[toEpic.priority] || 0; if (fromPriority < toPriority) { conflicts.push({ type: 'priority_mismatch', severity: 'medium', description: `Lower priority epic "${fromEpic.title}" blocks higher priority epic "${toEpic.title}"`, affectedEpics: [fromEpic.id, toEpic.id], resolutionOptions: [{ type: 'adjust_priority', description: 'Adjust epic priorities to match dependency order', complexity: 'low' }] }); } } return conflicts; } async detectResourceConflicts(epics, tasks) { const conflicts = []; for (let i = 0; i < epics.length; i++) { for (let j = i + 1; j < epics.length; j++) { const epic1 = epics[i]; const epic2 = epics[j]; const epic1Tasks = tasks.filter(t => t.epicId === epic1.id); const epic2Tasks = tasks.filter(t => t.epicId === epic2.id); const epic1Files = new Set(epic1Tasks.flatMap(t => t.filePaths)); const epic2Files = new Set(epic2Tasks.flatMap(t => t.filePaths)); const commonFiles = [...epic1Files].filter(file => epic2Files.has(file)); if (commonFiles.length > 0) { conflicts.push({ type: 'resource_conflict', severity: 'low', description: `Epics "${epic1.title}" and "${epic2.title}" modify common files: ${commonFiles.join(', ')}`, affectedEpics: [epic1.id, epic2.id], resolutionOptions: [{ type: 'reorder', description: 'Ensure epics that modify common files are properly sequenced', complexity: 'medium' }] }); } } } return conflicts; } async identifyParallelizationOpportunities(epics, epicDependencies) { const recommendations = []; const dependencyMap = new Map(); epicDependencies.forEach(dep => { if (!dependencyMap.has(dep.toEpicId)) { dependencyMap.set(dep.toEpicId, []); } dependencyMap.get(dep.toEpicId).push(dep.fromEpicId); }); const independentEpics = epics.filter(epic => !dependencyMap.has(epic.id)); if (independentEpics.length > 1) { recommendations.push({ type: 'parallelization', description: `${independentEpics.length} epics can be executed in parallel`, affectedEpics: independentEpics.map(e => e.id), estimatedBenefit: 'Reduced overall project timeline', implementationComplexity: 'low', priority: 'high' }); } return recommendations; } async identifyEpicSplittingOpportunities(epics, tasks) { const recommendations = []; for (const epic of epics) { const epicTasks = tasks.filter(t => t.epicId === epic.id); if (epicTasks.length > 10) { recommendations.push({ type: 'splitting', description: `Epic "${epic.title}" has ${epicTasks.length} tasks and could be split for better management`, affectedEpics: [epic.id], estimatedBenefit: 'Better task organization and parallel execution', implementationComplexity: 'medium', priority: 'medium' }); } } return recommendations; } async identifyEpicMergingOpportunities(epics, epicDependencies) { const recommendations = []; for (const dep of epicDependencies) { if (dep.strength > 0.8 && dep.critical) { const fromEpic = epics.find(e => e.id === dep.fromEpicId); const toEpic = epics.find(e => e.id === dep.toEpicId); if (fromEpic && toEpic && fromEpic.taskIds.length < 5 && toEpic.taskIds.length < 5) { recommendations.push({ type: 'merging', description: `Epics "${fromEpic.title}" and "${toEpic.title}" have strong dependency and could be merged`, affectedEpics: [fromEpic.id, toEpic.id], estimatedBenefit: 'Simplified project structure and reduced coordination overhead', implementationComplexity: 'high', priority: 'low' }); } } } return recommendations; } async discoverIntelligentRelationships(projectId, epics, tasks) { try { logger.info({ projectId, epicsCount: epics.length }, 'Starting intelligent relationship discovery'); const discoveredDependencies = []; const semanticRelationships = []; const filePathRelationships = []; const reasoning = []; if (this.config.enableFilePathAnalysis) { const filePathResults = await this.analyzeFilePathRelationships(epics, tasks); filePathRelationships.push(...filePathResults.relationships); reasoning.push(...filePathResults.reasoning); for (const fpRel of filePathResults.relationships) { if (fpRel.severity === 'high') { discoveredDependencies.push({ id: `fp-${fpRel.fromEpicId}-${fpRel.toEpicId}`, fromEpicId: fpRel.fromEpicId, toEpicId: fpRel.toEpicId, type: 'requires', description: `File path dependency: ${fpRel.sharedFilePaths.join(', ')}`, critical: true, strength: 0.8, metadata: { createdAt: new Date(), createdBy: 'intelligent-discovery', reason: `File path conflict analysis: ${fpRel.resolutionSuggestion}`, taskDependencies: [] } }); reasoning.push(`Created dependency from file path analysis: ${fpRel.resolutionSuggestion}`); } } } if (this.config.enableSemanticAnalysis) { const semanticResults = await this.analyzeSemanticRelationships(epics, tasks); semanticRelationships.push(...semanticResults.relationships); reasoning.push(...semanticResults.reasoning); for (const semRel of semanticResults.relationships) { if (semRel.confidence > 0.7 && semRel.strength > 0.6) { discoveredDependencies.push({ id: `sem-${semRel.fromEpicId}-${semRel.toEpicId}`, fromEpicId: semRel.fromEpicId, toEpicId: semRel.toEpicId, type: semRel.relationshipType === 'temporal' ? 'blocks' : 'enables', description: semRel.description, critical: semRel.confidence > 0.8, strength: semRel.strength, metadata: { createdAt: new Date(), createdBy: 'semantic-analysis', reason: `Semantic relationship analysis: ${semRel.description}`, taskDependencies: [] } }); reasoning.push(`Created dependency from semantic analysis: ${semRel.description}`); } } } const confidence = this.calculateDiscoveryConfidence(discoveredDependencies.length, semanticRelationships.length, filePathRelationships.length); const result = { discoveredDependencies, semanticRelationships, filePathRelationships, confidence, reasoning }; logger.info({ projectId, discoveredCount: discoveredDependencies.length, semanticCount: semanticRelationships.length, filePathCount: filePathRelationships.length, confidence }, 'Completed intelligent relationship discovery'); return result; } catch (error) { logger.error({ err: error, projectId }, 'Failed to discover intelligent relationships'); return { discoveredDependencies: [], semanticRelationships: [], filePathRelationships: [], confidence: 0, reasoning: [`Error during discovery: ${error}`] }; } } async analyzeFilePathRelationships(epics, tasks) { const relationships = []; const reasoning = []; const epicTaskMap = new Map(); epics.forEach(epic => { epicTaskMap.set(epic.id, tasks.filter(task => task.epicId === epic.id)); }); for (const [fromEpicId, fromTasks] of epicTaskMap.entries()) { for (const [toEpicId, toTasks] of epicTaskMap.entries()) { if (fromEpicId === toEpicId) continue; const sharedPaths = []; const conflictTypes = []; fromTasks.forEach(fromTask => { fromTask.filePaths.forEach(fromPath => { toTasks.forEach(toTask => { if (toTask.filePaths.includes(fromPath)) { if (!sharedPaths.includes(fromPath)) { sharedPaths.push(fromPath); if (fromTask.type === 'development' && toTask.type === 'development') { conflictTypes.push('modification'); } else if (fromTask.type === 'testing') { conflictTypes.push('read_dependency'); } else { conflictTypes.push('creation'); } } } }); }); }); if (sharedPaths.length > 0) { const severity = sharedPaths.length > 3 ? 'high' : sharedPaths.length > 1 ? 'medium' : 'low'; const primaryConflictType = conflictTypes[0] || 'modification'; const fromEpic = epics.find(e => e.id === fromEpicId); const toEpic = epics.find(e => e.id === toEpicId); const resolutionSuggestion = severity === 'high' ? `Consider sequencing "${fromEpic?.title}" before "${toEpic?.title}" due to significant file overlap` : `Monitor coordination between "${fromEpic?.title}" and "${toEpic?.title}" for file conflicts`; relationships.push({ fromEpicId, toEpicId, sharedFilePaths: sharedPaths, conflictType: primaryConflictType, severity: severity, resolutionSuggestion }); reasoning.push(`File path analysis: ${sharedPaths.length} shared paths between ${fromEpic?.title} and ${toEpic?.title}`); } } } return { relationships, reasoning }; } async analyzeSemanticRelationships(epics, tasks) { const relationships = []; const reasoning = []; try { const { performFormatAwareLlmCall } = await import('../../../utils/llmHelper.js'); const { OpenRouterConfigManager } = await import('../../../utils/openrouter-config-manager.js'); const configManager = OpenRouterConfigManager.getInstance(); const config = await configManager.getOpenRouterConfig(); for (let i = 0; i < epics.length; i++) { for (let j = i + 1; j < epics.length; j++) { const epicA = epics[i]; const epicB = epics[j]; const epicATitle = epicA.title; const epicBTitle = epicB.title; const epicADesc = epicA.description; const epicBDesc = epicB.description; const epicATasks = tasks.filter(t => t.epicId === epicA.id); const epicBTasks = tasks.filter(t => t.epicId === epicB.id); const epicATaskTitles = epicATasks.map(t => t.title).join(', '); const epicBTaskTitles = epicBTasks.map(t => t.title).join(', '); const prompt = `Analyze the relationship between these two project epics: Epic A: "${epicATitle}" Description: ${epicADesc} Tasks: ${epicATaskTitles} Epic B: "${epicBTitle}" Description: ${epicBDesc} Tasks: ${epicBTaskTitles} Determine if there's a meaningful relationship between these epics. Consider: 1. Conceptual relationships (similar domain/functionality) 2. Functional relationships (one provides services to the other) 3. Temporal relationships (one should happen before the other) 4. Hierarchical relationships (one is a component of the other) Respond with JSON: { "hasRelationship": boolean, "relationshipType": "conceptual|functional|temporal|hierarchical", "direction": "A_to_B|B_to_A|bidirectional", "strength": number (0-1), "confidence": number (0-1), "description": "brief explanation" }`; try { const response = await performFormatAwareLlmCall(prompt, 'You are an expert software architect analyzing project dependencies.', config, 'epic_relationship_analysis', 'json'); const analysis = JSON.parse(response); if (analysis.hasRelationship && analysis.confidence > 0.5) { const fromEpicId = analysis.direction === 'B_to_A' ? epicB.id : epicA.id; const toEpicId = analysis.direction === 'B_to_A' ? epicA.id : epicB.id; relationships.push({ fromEpicId, toEpicId, relationshipType: analysis.relationshipType, strength: analysis.strength, description: analysis.description, confidence: analysis.confidence }); reasoning.push(`Semantic analysis found ${analysis.relationshipType} relationship between ${epicATitle} and ${epicBTitle}: ${analysis.description}`); if (analysis.direction === 'bidirectional') { relationships.push({ fromEpicId: toEpicId, toEpicId: fromEpicId, relationshipType: analysis.relationshipType, strength: analysis.strength, description: analysis.description, confidence: analysis.confidence }); } } } catch (llmError) { logger.debug({ err: llmError, epicA: epicATitle, epicB: epicBTitle }, 'LLM analysis failed for epic pair'); reasoning.push(`LLM analysis failed for ${epicATitle} - ${epicBTitle}: ${llmError}`); } } } } catch (error) { logger.error({ err: error }, 'Failed to perform semantic relationship analysis'); reasoning.push(`Semantic analysis error: ${error}`); } return { relationships, reasoning }; } calculateDiscoveryConfidence(dependencyCount, semanticCount, filePathCount) { let confidence = 0.3; if (dependencyCount > 0) confidence += 0.3; if (semanticCount > 0) confidence += 0.2; if (filePathCount > 0) confidence += 0.2; return Math.min(confidence, 1.0); } async validateDiscoveredRelationships(discoveredDependencies, existingDependencies) { const validatedDependencies = []; const conflicts = []; const optimizations = []; for (const discovered of discoveredDependencies) { const isDuplicate = existingDependencies.some(existing => existing.fromEpicId === discovered.fromEpicId && existing.toEpicId === discovered.toEpicId && existing.type === discovered.type); if (!isDuplicate) { validatedDependencies.push(discovered); } } const allDependencies = [...existingDependencies, ...validatedDependencies]; const circularConflicts = await this.detectCircularEpicDependencies([], allDependencies); conflicts.push(...circularConflicts); if (validatedDependencies.length > 0) { optimizations.push({ type: 'reordering', description: `Consider reordering epics based on ${validatedDependencies.length} newly discovered dependencies`, affectedEpics: [...new Set(validatedDependencies.flatMap(d => [d.fromEpicId, d.toEpicId]))], estimatedBenefit: 'Improved project flow and reduced blocking', implementationComplexity: 'medium', priority: 'high' }); } return { validatedDependencies, conflicts, optimizations }; } }