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.

1,262 lines 81.7 kB
import logger from '../../../logger.js'; export class OptimizedDependencyGraph { projectId; adjacencyList = new Map(); reverseIndex = new Map(); nodes = new Map(); edges = new Map(); topologicalOrder = []; criticalPath = []; parallelBatches = []; isDirty = false; metrics = { totalNodes: 0, totalEdges: 0, maxDepth: 0, criticalPathLength: 0, parallelBatches: 0, cycleCount: 0, orphanedNodes: 0, averageDegree: 0 }; constructor(projectId) { this.projectId = projectId; logger.debug({ projectId }, 'Initializing optimized dependency graph'); } addTask(task) { const node = { taskId: task.id, title: task.title, status: task.status, estimatedHours: task.estimatedHours, priority: task.priority, dependencies: [], dependents: [], depth: 0, criticalPath: false }; this.nodes.set(task.id, node); if (!this.adjacencyList.has(task.id)) { this.adjacencyList.set(task.id, new Set()); } if (!this.reverseIndex.has(task.id)) { this.reverseIndex.set(task.id, new Set()); } this.markDirty(); logger.debug({ taskId: task.id, title: task.title }, 'Added task to dependency graph'); } addDependency(dependentTaskId, dependencyTaskId, type = 'task', weight = 1, critical = false, description) { if (!this.nodes.has(dependentTaskId) || !this.nodes.has(dependencyTaskId)) { logger.warn({ dependentTaskId, dependencyTaskId }, 'Cannot add dependency: one or both tasks not found'); return false; } if (this.wouldCreateCycle(dependencyTaskId, dependentTaskId)) { logger.warn({ dependentTaskId, dependencyTaskId }, 'Cannot add dependency: would create cycle'); return false; } const edgeId = `${dependentTaskId}->${dependencyTaskId}`; const edge = { from: dependentTaskId, to: dependencyTaskId, type, weight, critical, description }; this.adjacencyList.get(dependencyTaskId).add(dependentTaskId); this.reverseIndex.get(dependentTaskId).add(dependencyTaskId); this.edges.set(edgeId, edge); const dependentNode = this.nodes.get(dependentTaskId); const dependencyNode = this.nodes.get(dependencyTaskId); dependentNode.dependencies.push(dependencyTaskId); dependencyNode.dependents.push(dependentTaskId); this.markDirty(); logger.debug({ dependentTaskId, dependencyTaskId, type }, 'Added dependency edge'); return true; } removeDependency(dependentTaskId, dependencyTaskId) { const edgeId = `${dependentTaskId}->${dependencyTaskId}`; if (!this.edges.has(edgeId)) { return false; } this.adjacencyList.get(dependencyTaskId)?.delete(dependentTaskId); this.reverseIndex.get(dependentTaskId)?.delete(dependencyTaskId); this.edges.delete(edgeId); const dependentNode = this.nodes.get(dependentTaskId); const dependencyNode = this.nodes.get(dependencyTaskId); if (dependentNode) { dependentNode.dependencies = dependentNode.dependencies.filter(id => id !== dependencyTaskId); } if (dependencyNode) { dependencyNode.dependents = dependencyNode.dependents.filter(id => id !== dependentTaskId); } this.markDirty(); logger.debug({ dependentTaskId, dependencyTaskId }, 'Removed dependency edge'); return true; } getReadyTasks() { const readyTasks = []; for (const [taskId, node] of this.nodes) { if (node.status === 'pending' && this.hasNoPendingDependencies(taskId)) { readyTasks.push(taskId); } } return readyTasks; } detectCycles() { const cycles = []; const visited = new Set(); const recursionStack = new Set(); const path = []; const dfs = (nodeId) => { visited.add(nodeId); recursionStack.add(nodeId); path.push(nodeId); const neighbors = this.adjacencyList.get(nodeId) || new Set(); for (const neighbor of neighbors) { if (!visited.has(neighbor)) { if (dfs(neighbor)) { return true; } } else if (recursionStack.has(neighbor)) { const cycleStart = path.indexOf(neighbor); const cycle = path.slice(cycleStart); cycle.push(neighbor); cycles.push([...cycle]); return true; } } recursionStack.delete(nodeId); path.pop(); return false; }; for (const nodeId of this.nodes.keys()) { if (!visited.has(nodeId)) { dfs(nodeId); } } return cycles; } getTopologicalOrder() { if (!this.topologicalOrder) { this.topologicalOrder = []; } if (!this.isDirty && this.topologicalOrder.length > 0) { return [...this.topologicalOrder]; } const cycles = this.detectCycles(); if (cycles.length > 0) { logger.warn({ cycleCount: cycles.length }, 'Cannot create topological order - graph contains cycles'); this.topologicalOrder = []; return []; } const inDegree = new Map(); const queue = []; const result = []; for (const nodeId of this.nodes.keys()) { inDegree.set(nodeId, this.reverseIndex.get(nodeId)?.size || 0); if (inDegree.get(nodeId) === 0) { queue.push(nodeId); } } while (queue.length > 0) { const current = queue.shift(); result.push(current); const neighbors = this.adjacencyList.get(current) || new Set(); for (const neighbor of neighbors) { const newInDegree = inDegree.get(neighbor) - 1; inDegree.set(neighbor, newInDegree); if (newInDegree === 0) { queue.push(neighbor); } } } if (result.length !== this.nodes.size) { logger.warn({ expected: this.nodes.size, actual: result.length }, 'Topological sort incomplete - graph contains cycles'); return []; } this.topologicalOrder = result; return [...result]; } getCriticalPath() { if (!this.criticalPath) { this.criticalPath = []; } if (!this.isDirty && this.criticalPath.length > 0) { return [...this.criticalPath]; } const topOrder = this.getTopologicalOrder(); if (!topOrder || topOrder.length === 0) { return []; } const distances = new Map(); const predecessors = new Map(); for (const nodeId of this.nodes.keys()) { distances.set(nodeId, 0); } for (const nodeId of topOrder) { const node = this.nodes.get(nodeId); const currentDistance = distances.get(nodeId); const neighbors = this.adjacencyList.get(nodeId) || new Set(); for (const neighbor of neighbors) { const edge = this.edges.get(`${nodeId}->${neighbor}`); const newDistance = currentDistance + (node.estimatedHours * (edge?.weight || 1)); if (newDistance > distances.get(neighbor)) { distances.set(neighbor, newDistance); predecessors.set(neighbor, nodeId); } } } let maxDistance = 0; let endNode = ''; for (const [nodeId, distance] of distances) { if (distance > maxDistance) { maxDistance = distance; endNode = nodeId; } } const path = []; let current = endNode; while (current) { path.unshift(current); current = predecessors.get(current) || ''; } for (const nodeId of this.nodes.keys()) { const node = this.nodes.get(nodeId); node.criticalPath = path.includes(nodeId); } this.criticalPath = path; return [...path]; } getParallelBatches() { if (!this.parallelBatches) { this.parallelBatches = []; } if (!this.isDirty && this.parallelBatches.length > 0) { return [...this.parallelBatches]; } const topOrder = this.getTopologicalOrder(); if (!topOrder || topOrder.length === 0) { return []; } const batches = []; const processed = new Set(); let batchId = 0; while (processed.size < this.nodes.size) { const currentBatch = []; for (const taskId of topOrder) { if (processed.has(taskId)) continue; const dependencies = this.reverseIndex.get(taskId) || new Set(); const hasUnprocessedDeps = Array.from(dependencies).some(dep => !processed.has(dep)); if (!hasUnprocessedDeps) { currentBatch.push(taskId); } } if (currentBatch.length === 0) { logger.warn('No tasks can be processed - possible cycle or error'); break; } const estimatedDuration = Math.max(...currentBatch.map(taskId => this.nodes.get(taskId)?.estimatedHours || 0)); const dependencies = Array.from(new Set(currentBatch.flatMap(taskId => Array.from(this.reverseIndex.get(taskId) || new Set())))); const canStartAfter = batches.length > 0 ? [batches.length - 1] : []; batches.push({ batchId, taskIds: currentBatch, estimatedDuration, dependencies, canStartAfter }); currentBatch.forEach(taskId => processed.add(taskId)); batchId++; } this.parallelBatches = batches; return [...batches]; } wouldCreateCycle(fromTaskId, toTaskId) { return this.hasPath(toTaskId, fromTaskId); } hasPath(from, to) { if (from === to) return true; const visited = new Set(); const stack = [from]; while (stack.length > 0) { const current = stack.pop(); if (visited.has(current)) continue; visited.add(current); const neighbors = this.adjacencyList.get(current) || new Set(); for (const neighbor of neighbors) { if (neighbor === to) return true; if (!visited.has(neighbor)) { stack.push(neighbor); } } } return false; } hasNoPendingDependencies(taskId) { const dependencies = this.reverseIndex.get(taskId) || new Set(); for (const depId of dependencies) { const depNode = this.nodes.get(depId); if (depNode && depNode.status !== 'completed') { return false; } } return true; } markDirty() { this.isDirty = true; this.updateMetrics(); } updateMetrics() { this.metrics = { totalNodes: this.nodes.size, totalEdges: this.edges.size, maxDepth: this.calculateMaxDepth(), criticalPathLength: this.criticalPath.length, parallelBatches: this.parallelBatches.length, cycleCount: this.detectCycles().length, orphanedNodes: this.getOrphanedNodes().length, averageDegree: this.calculateAverageDegree() }; } calculateMaxDepth() { let maxDepth = 0; for (const node of this.nodes.values()) { maxDepth = Math.max(maxDepth, node.depth); } return maxDepth; } getOrphanedNodes() { const orphaned = []; for (const [nodeId, node] of this.nodes) { if (node.dependencies.length === 0 && node.dependents.length === 0) { orphaned.push(nodeId); } } return orphaned; } calculateAverageDegree() { if (this.nodes.size === 0) return 0; let totalDegree = 0; for (const node of this.nodes.values()) { totalDegree += node.dependencies.length + node.dependents.length; } return totalDegree / this.nodes.size; } getMetrics() { if (this.isDirty) { this.updateMetrics(); } return { ...this.metrics }; } getNodes() { return new Map(this.nodes); } getEdges() { return new Map(this.edges); } clearCache() { this.topologicalOrder = []; this.criticalPath = []; this.parallelBatches = []; this.isDirty = true; } reset() { this.nodes.clear(); this.edges.clear(); this.adjacencyList.clear(); this.reverseIndex.clear(); this.clearCache(); this.metrics = { totalNodes: 0, totalEdges: 0, maxDepth: 0, criticalPathLength: 0, parallelBatches: 0, cycleCount: 0, orphanedNodes: 0, averageDegree: 0 }; logger.debug({ projectId: this.projectId }, 'Dependency graph reset to empty state'); } getSize() { return { nodes: this.nodes.size, edges: this.edges.size }; } analyzeCriticalPath() { const topOrder = this.getTopologicalOrder(); if (topOrder.length === 0) { return { paths: [], longestPath: [], totalDuration: 0, resourceWeightedDuration: 0, bottleneckTasks: [] }; } const allPaths = this.findAllCriticalPaths(); const pathDurations = allPaths.map(path => ({ path, duration: this.calculatePathDuration(path), resourceWeightedDuration: this.calculateResourceWeightedDuration(path) })); const longestByDuration = pathDurations.reduce((max, current) => current.duration > max.duration ? current : max); const longestByResourceWeight = pathDurations.reduce((max, current) => current.resourceWeightedDuration > max.resourceWeightedDuration ? current : max); const taskFrequency = new Map(); allPaths.forEach(path => { path.forEach(taskId => { taskFrequency.set(taskId, (taskFrequency.get(taskId) || 0) + 1); }); }); const bottleneckTasks = Array.from(taskFrequency.entries()) .filter(([_, frequency]) => frequency > 1) .sort((a, b) => b[1] - a[1]) .map(([taskId]) => taskId); return { paths: allPaths, longestPath: longestByDuration.path, totalDuration: longestByDuration.duration, resourceWeightedDuration: longestByResourceWeight.resourceWeightedDuration, bottleneckTasks }; } analyzeDependencyImpact(taskId) { if (!this.nodes.has(taskId)) { throw new Error(`Task ${taskId} not found in graph`); } const directDependents = Array.from(this.adjacencyList.get(taskId) || new Set()); const indirectDependents = this.findIndirectDependents(taskId); const propagationChains = this.findPropagationChains(taskId); const impactRadius = propagationChains.length > 0 ? Math.max(...propagationChains.map(chain => chain.length - 1)) : 0; const totalAffected = new Set([...directDependents, ...indirectDependents]).size; const riskLevel = this.calculateRiskLevel(impactRadius, totalAffected); return { taskId, directDependents, indirectDependents, impactRadius, riskLevel, propagationChain: propagationChains }; } detectBottlenecks() { const bottlenecks = []; const criticalPathAnalysis = this.analyzeCriticalPath(); for (const [taskId, node] of this.nodes) { const analysis = this.analyzeTaskBottleneck(taskId, node, criticalPathAnalysis); if (analysis.severity > 0.3) { bottlenecks.push(analysis); } } return bottlenecks.sort((a, b) => b.severity - a.severity); } optimizeResourceAllocation() { const currentBatches = this.getParallelBatches(); const optimizedBatches = this.optimizeParallelBatches(currentBatches); const totalTasks = this.nodes.size; const parallelTasks = optimizedBatches.reduce((sum, batch) => sum + batch.taskIds.length, 0); const resourceUtilization = parallelTasks / totalTasks; const originalTime = currentBatches.reduce((sum, batch) => sum + batch.estimatedDuration, 0); const optimizedTime = optimizedBatches.reduce((sum, batch) => sum + batch.estimatedDuration, 0); const timeReduction = (originalTime - optimizedTime) / originalTime; const parallelismOpportunities = this.identifyParallelismOpportunities(); return { optimalBatches: optimizedBatches, resourceUtilization, timeReduction, parallelismOpportunities }; } findAllCriticalPaths() { const sources = this.findSourceNodes(); const sinks = this.findSinkNodes(); const allPaths = []; for (const source of sources) { for (const sink of sinks) { const paths = this.findAllPathsBetween(source, sink); allPaths.push(...paths); } } return allPaths; } findSourceNodes() { return Array.from(this.nodes.keys()).filter(nodeId => (this.reverseIndex.get(nodeId)?.size || 0) === 0); } findSinkNodes() { return Array.from(this.nodes.keys()).filter(nodeId => (this.adjacencyList.get(nodeId)?.size || 0) === 0); } findAllPathsBetween(start, end) { const paths = []; const visited = new Set(); const dfs = (current, path) => { if (current === end) { paths.push([...path, current]); return; } if (visited.has(current)) return; visited.add(current); const neighbors = this.adjacencyList.get(current) || new Set(); for (const neighbor of neighbors) { dfs(neighbor, [...path, current]); } visited.delete(current); }; dfs(start, []); return paths; } calculatePathDuration(path) { return path.reduce((total, taskId) => { const node = this.nodes.get(taskId); return total + (node?.estimatedHours || 0); }, 0); } calculateResourceWeightedDuration(path) { return path.reduce((total, taskId) => { const node = this.nodes.get(taskId); const baseHours = node?.estimatedHours || 0; let weight = 1.0; if (node?.priority === 'high') weight *= 1.5; if (node?.priority === 'critical') weight *= 2.0; return total + (baseHours * weight); }, 0); } findIndirectDependents(taskId) { const visited = new Set(); const indirectDependents = new Set(); const dfs = (current, depth) => { if (visited.has(current) || depth > 10) return; visited.add(current); const dependents = this.adjacencyList.get(current) || new Set(); for (const dependent of dependents) { if (dependent !== taskId) { indirectDependents.add(dependent); dfs(dependent, depth + 1); } } }; const directDependents = this.adjacencyList.get(taskId) || new Set(); for (const dependent of directDependents) { dfs(dependent, 1); } for (const direct of directDependents) { indirectDependents.delete(direct); } return Array.from(indirectDependents); } findPropagationChains(taskId) { const chains = []; const visited = new Set(); const dfs = (current, chain) => { if (visited.has(current) || chain.length > 10) return; const dependents = this.adjacencyList.get(current) || new Set(); if (dependents.size === 0) { chains.push([...chain, current]); return; } visited.add(current); for (const dependent of dependents) { dfs(dependent, [...chain, current]); } visited.delete(current); }; dfs(taskId, []); return chains; } calculateRiskLevel(impactRadius, totalAffected) { const riskScore = (impactRadius * 0.6) + (totalAffected * 0.4); if (riskScore >= 8) return 'critical'; if (riskScore >= 5) return 'high'; if (riskScore >= 2) return 'medium'; return 'low'; } analyzeTaskBottleneck(taskId, node, criticalPathAnalysis) { let severity = 0; let bottleneckType = 'dependency'; const affectedTasks = []; const recommendations = []; if (criticalPathAnalysis.longestPath.includes(taskId)) { severity += 0.4; bottleneckType = 'critical-path'; affectedTasks.push(...criticalPathAnalysis.longestPath); recommendations.push('Task is on critical path - prioritize completion'); } const dependentCount = this.adjacencyList.get(taskId)?.size || 0; if (dependentCount > 3) { severity += 0.4; bottleneckType = 'dependency'; affectedTasks.push(...Array.from(this.adjacencyList.get(taskId) || new Set())); recommendations.push(`High dependency fan-out (${dependentCount} dependents) - consider breaking down task`); } if (node.estimatedHours > 8) { severity += 0.2; if (bottleneckType === 'dependency') { } else { bottleneckType = 'resource'; } recommendations.push('High time estimate - consider parallel execution or task breakdown'); } const parallelConstraints = this.checkParallelConstraints(taskId); if (parallelConstraints > 0) { severity += 0.1; if (bottleneckType === 'dependency' || bottleneckType === 'resource') { } else { bottleneckType = 'parallel-constraint'; } recommendations.push('Limited parallelization opportunities - review dependencies'); } return { taskId, bottleneckType, severity: Math.min(severity, 1.0), affectedTasks: [...new Set(affectedTasks)], recommendations }; } checkParallelConstraints(taskId) { const dependencies = this.reverseIndex.get(taskId)?.size || 0; const dependents = this.adjacencyList.get(taskId)?.size || 0; return dependencies + dependents; } optimizeParallelBatches(currentBatches) { const optimizedBatches = []; for (const batch of currentBatches) { const optimizedBatch = this.balanceBatch(batch); optimizedBatches.push(optimizedBatch); } return optimizedBatches; } balanceBatch(batch) { const sortedTasks = batch.taskIds.sort((a, b) => { const nodeA = this.nodes.get(a); const nodeB = this.nodes.get(b); return (nodeB?.estimatedHours || 0) - (nodeA?.estimatedHours || 0); }); const estimatedDuration = Math.max(...sortedTasks.map(taskId => this.nodes.get(taskId)?.estimatedHours || 0)); return { ...batch, taskIds: sortedTasks, estimatedDuration }; } identifyParallelismOpportunities() { const opportunities = []; for (const [taskId] of this.nodes) { const canRunWith = this.findParallelizableTasks(taskId); if (canRunWith.length > 0) { const estimatedSavings = this.calculateParallelSavings(taskId, canRunWith); opportunities.push({ taskId, canRunWith, estimatedSavings }); } } return opportunities.sort((a, b) => b.estimatedSavings - a.estimatedSavings); } findParallelizableTasks(taskId) { const parallelizable = []; const taskDependencies = this.reverseIndex.get(taskId) || new Set(); const taskDependents = this.adjacencyList.get(taskId) || new Set(); for (const [otherTaskId] of this.nodes) { if (otherTaskId === taskId) continue; const otherDependencies = this.reverseIndex.get(otherTaskId) || new Set(); const otherDependents = this.adjacencyList.get(otherTaskId) || new Set(); const hasDirectDependency = taskDependencies.has(otherTaskId) || taskDependents.has(otherTaskId) || otherDependencies.has(taskId) || otherDependents.has(taskId); if (!hasDirectDependency) { parallelizable.push(otherTaskId); } } return parallelizable; } calculateParallelSavings(taskId, parallelTasks) { const taskDuration = this.nodes.get(taskId)?.estimatedHours || 0; const parallelDurations = parallelTasks.map(id => this.nodes.get(id)?.estimatedHours || 0); const sequentialTime = taskDuration + parallelDurations.reduce((sum, duration) => sum + duration, 0); const parallelTime = Math.max(taskDuration, ...parallelDurations); return sequentialTime - parallelTime; } validateDependencies() { const errors = []; const warnings = []; const suggestions = []; const typeValidation = this.validateDependencyTypes(); errors.push(...typeValidation.errors); warnings.push(...typeValidation.warnings); const conflicts = this.detectDependencyConflicts(); conflicts.forEach(conflict => { errors.push({ type: 'conflict', severity: conflict.severity === 'critical' ? 'error' : 'warning', message: conflict.description, affectedTasks: conflict.involvedTasks, suggestedFix: conflict.resolutionOptions[0]?.description }); }); const autoSuggestions = this.generateDependencySuggestions(); suggestions.push(...autoSuggestions); const redundancyCheck = this.detectRedundantDependencies(); warnings.push(...redundancyCheck); return { isValid: errors.filter(e => e.severity === 'error').length === 0, errors, warnings, suggestions }; } detectDependencyConflicts() { const conflicts = []; const cycles = this.detectCycles(); cycles.forEach(cycle => { conflicts.push({ conflictType: 'circular', description: `Circular dependency detected: ${cycle.join(' -> ')}`, involvedTasks: cycle, involvedDependencies: this.getCycleDependencies(cycle), severity: 'critical', resolutionOptions: [ { strategy: 'remove-dependency', description: `Remove dependency between ${cycle[cycle.length - 1]} and ${cycle[0]}`, impact: 'May require task reordering', effort: 'low' }, { strategy: 'reorder-tasks', description: 'Reorder tasks to eliminate circular dependency', impact: 'Changes execution order', effort: 'medium' } ] }); }); const typeConflicts = this.detectIncompatibleTypes(); conflicts.push(...typeConflicts); const resourceConflicts = this.detectResourceContention(); conflicts.push(...resourceConflicts); const timingConflicts = this.detectTimingConflicts(); conflicts.push(...timingConflicts); return conflicts; } generateDependencySuggestions() { const suggestions = []; const missingSuggestions = this.suggestMissingDependencies(); suggestions.push(...missingSuggestions); const typeSuggestions = this.suggestDependencyTypeImprovements(); suggestions.push(...typeSuggestions); const parallelSuggestions = this.suggestParallelizationOpportunities(); suggestions.push(...parallelSuggestions); return suggestions.sort((a, b) => b.confidence - a.confidence); } validateDependencyBeforeAdd(dependentTaskId, dependencyTaskId, type) { const errors = []; const warnings = []; const suggestions = []; if (!this.nodes.has(dependentTaskId)) { errors.push({ type: 'missing-task', severity: 'error', message: `Task ${dependentTaskId} does not exist`, affectedTasks: [dependentTaskId], suggestedFix: 'Create the task before adding dependencies' }); } if (!this.nodes.has(dependencyTaskId)) { errors.push({ type: 'missing-task', severity: 'error', message: `Task ${dependencyTaskId} does not exist`, affectedTasks: [dependencyTaskId], suggestedFix: 'Create the task before adding dependencies' }); } if (dependentTaskId === dependencyTaskId) { errors.push({ type: 'self-dependency', severity: 'error', message: 'A task cannot depend on itself', affectedTasks: [dependentTaskId], suggestedFix: 'Remove self-dependency' }); } if (this.wouldCreateCycle(dependencyTaskId, dependentTaskId)) { errors.push({ type: 'cycle', severity: 'error', message: `Adding this dependency would create a circular dependency`, affectedTasks: [dependentTaskId, dependencyTaskId], suggestedFix: 'Reorder tasks or remove conflicting dependencies' }); } const validTypes = ['task', 'package', 'framework', 'tool', 'import', 'environment']; if (!validTypes.includes(type)) { errors.push({ type: 'invalid-type', severity: 'error', message: `Invalid dependency type: ${type}`, affectedTasks: [dependentTaskId, dependencyTaskId], suggestedFix: `Use one of: ${validTypes.join(', ')}` }); } const existingEdge = this.edges.get(`${dependentTaskId}->${dependencyTaskId}`); if (existingEdge) { warnings.push({ type: 'redundant', message: `Dependency already exists between ${dependentTaskId} and ${dependencyTaskId}`, affectedTasks: [dependentTaskId, dependencyTaskId], recommendation: 'Consider updating the existing dependency instead' }); } return { isValid: errors.length === 0, errors, warnings, suggestions }; } validateDependencyTypes() { const errors = []; const warnings = []; for (const [edgeId, edge] of this.edges) { const validTypes = ['task', 'package', 'framework', 'tool', 'import', 'environment']; if (!validTypes.includes(edge.type)) { errors.push({ type: 'invalid-type', severity: 'error', message: `Invalid dependency type '${edge.type}' in edge ${edgeId}`, affectedTasks: [edge.from, edge.to], suggestedFix: `Change to one of: ${validTypes.join(', ')}` }); } const compatibility = this.checkTypeCompatibility(edge.from, edge.to, edge.type); if (!compatibility.compatible) { warnings.push({ type: 'potential-issue', message: compatibility.reason, affectedTasks: [edge.from, edge.to], recommendation: compatibility.suggestion }); } } return { errors, warnings }; } checkTypeCompatibility(fromTaskId, toTaskId, type) { const fromNode = this.nodes.get(fromTaskId); const toNode = this.nodes.get(toTaskId); if (!fromNode || !toNode) { return { compatible: false, reason: 'One or both tasks not found' }; } if (type === 'task' && fromNode.priority === 'low' && toNode.priority === 'critical') { return { compatible: false, reason: `Low priority task ${fromTaskId} depending on critical task ${toTaskId} may indicate incorrect prioritization`, suggestion: 'Review task priorities or dependency direction' }; } if (type === 'framework' || type === 'package') { if (fromNode.estimatedHours > toNode.estimatedHours * 3) { return { compatible: false, reason: `Large task depending on much smaller ${type} task may indicate missing breakdown`, suggestion: 'Consider breaking down the larger task' }; } } return { compatible: true, reason: 'Compatible' }; } detectIncompatibleTypes() { const conflicts = []; const taskDependencies = new Map(); for (const [, edge] of this.edges) { if (!taskDependencies.has(edge.from)) { taskDependencies.set(edge.from, []); } taskDependencies.get(edge.from).push(edge.type); } for (const [taskId, types] of taskDependencies) { const uniqueTypes = [...new Set(types)]; if (uniqueTypes.includes('framework') && uniqueTypes.includes('package') && uniqueTypes.length > 2) { conflicts.push({ conflictType: 'incompatible-types', description: `Task ${taskId} has conflicting dependency types: framework and package dependencies with others`, involvedTasks: [taskId], involvedDependencies: this.getTaskDependencyIds(taskId), severity: 'medium', resolutionOptions: [ { strategy: 'change-type', description: 'Consolidate dependency types or split task', impact: 'May require task restructuring', effort: 'medium' } ] }); } } return conflicts; } detectResourceContention() { const conflicts = []; const parallelBatches = this.getParallelBatches(); for (const batch of parallelBatches) { if (batch.taskIds.length > 1) { const resourceConflicts = this.checkBatchResourceConflicts(batch.taskIds); conflicts.push(...resourceConflicts); } } return conflicts; } checkBatchResourceConflicts(taskIds) { const conflicts = []; const highResourceTasks = taskIds.filter(taskId => { const node = this.nodes.get(taskId); return node && node.estimatedHours > 6; }); if (highResourceTasks.length > 1) { conflicts.push({ conflictType: 'resource-contention', description: `Multiple high-resource tasks scheduled in parallel: ${highResourceTasks.join(', ')}`, involvedTasks: highResourceTasks, involvedDependencies: [], severity: 'medium', resolutionOptions: [ { strategy: 'reorder-tasks', description: 'Stagger high-resource tasks across different batches', impact: 'May increase total project time but reduce resource pressure', effort: 'low' }, { strategy: 'split-task', description: 'Break down large tasks into smaller components', impact: 'Increases task count but improves parallelization', effort: 'high' } ] }); } return conflicts; } detectTimingConflicts() { const conflicts = []; for (const [edgeId, edge] of this.edges) { const fromNode = this.nodes.get(edge.from); const toNode = this.nodes.get(edge.to); if (fromNode && toNode) { const timeRatio = fromNode.estimatedHours / toNode.estimatedHours; if (timeRatio > 10) { conflicts.push({ conflictType: 'timing-conflict', description: `Large time disparity: ${edge.from} (${fromNode.estimatedHours}h) depends on ${edge.to} (${toNode.estimatedHours}h)`, involvedTasks: [edge.from, edge.to], involvedDependencies: [edgeId], severity: 'low', resolutionOptions: [ { strategy: 'split-task', description: `Consider breaking down ${edge.from} into smaller tasks`, impact: 'Better parallelization and progress tracking', effort: 'medium' } ] }); } } } return conflicts; } getCycleDependencies(cycle) { const dependencies = []; for (let i = 0; i < cycle.length; i++) { const from = cycle[i]; const to = cycle[(i + 1) % cycle.length]; const edgeId = `${from}->${to}`; if (this.edges.has(edgeId)) { dependencies.push(edgeId); } } return dependencies; } getTaskDependencyIds(taskId) { const dependencies = []; for (const [edgeId, edge] of this.edges) { if (edge.from === taskId || edge.to === taskId) { dependencies.push(edgeId); } } return dependencies; } suggestMissingDependencies() { const suggestions = []; for (const [taskId, node] of this.nodes) { const potentialDeps = this.analyzePotentialDependencies(taskId, node); suggestions.push(...potentialDeps); } return suggestions; } analyzePotentialDependencies(taskId, node) { const suggestions = []; const taskTitle = node.title.toLowerCase(); const existingDeps = new Set(node.dependencies); for (const [otherTaskId, otherNode] of this.nodes) { if (otherTaskId === taskId || existingDeps.has(otherTaskId)) continue; const otherTitle = otherNode.title.toLowerCase(); let confidence = 0; let reason = ''; let dependencyType = 'task'; if (taskTitle.includes('implement') && otherTitle.includes('setup')) { confidence = 0.8; reason = 'Implementation tasks typically depend on setup tasks'; dependencyType = 'task'; } if (taskTitle.includes('install') && (otherTitle.includes('configure') || otherTitle.includes('setup'))) { confidence = 0.7; reason = 'Installation tasks often depend on configuration'; dependencyType = 'package'; } if (taskTitle.includes('test') && otherTitle.includes('implement')) { confidence = 0.9; reason = 'Tests depend on implementation'; dependencyType = 'task'; } if (taskTitle.includes('component') && otherTitle.includes('framework')) { confidence = 0.6; reason = 'Components typically depend on framework setup'; dependencyType = 'framework'; } if (confidence > 0.5) { suggestions.push({ type: 'add', fromTaskId: taskId, toTaskId: otherTaskId, dependencyType, reason, confidence, impact: confidence > 0.8 ? 'high' : confidence > 0.6 ? 'medium' : 'low' }); } } return suggestions; } suggestDependencyTypeImprovements() { const suggestions = []; for (const [, edge] of this.edges) { const fromNode = this.nodes.get(edge.from); const toNode = this.nodes.get(edge.to); if (!fromNode || !toNode) continue; const suggestedType = this.suggestBetterDependencyType(fromNode, toNode, edge.type); if (suggestedType && suggestedType !== edge.type) { suggestions.push({ type: 'modify', fromTaskId: edge.from, toTaskId: edge.to, dependencyType: suggestedType, reason: `Current type '${edge.type}' could be more specific as '${suggestedType}'`, confidence: 0.6, impact: 'low' }); } } return suggestions; } suggestBetterDependencyType(fromNode, toNode, currentType) { const fromTitle = fromNode.title.toLowerCase(); const toTitle = toNode.title.toLowerCase(); if (currentType === 'task') { if (toTitle.includes('package') || toTitle.includes('install') || toTitle.includes('npm')) { return 'package'; } if (toTitle.includes('framework') || toTitle.includes('react') || toTitle.includes('vue') || toTitle.includes('angular')) { return 'framework'; } if (toTitle.includes('tool') || toTitle.includes('webpack') || toTitle.includes('babel') || toTitle.includes('eslint')) { return 'tool'; } if (toTitle.includes('import') || toTitle.includes('module') || fromTitle.includes('import')) { return 'import'; } if (toTitle.includes('environment') || toTitle.includes('env') || toTitle.includes('config')) { return 'environment'; } } return null; } suggestParallelizationOpportunities() { const suggestions = []; for (const [, edge] of this.edges) { if (this.couldBeParallelized(edge.from, edge.to)) { suggestions.push({ type: 'remove', fromTaskId: edge.from, toTaskId: edge.to, dependencyType: edge.type, reason: 'Removing this dependency could enable parallel execution', confidence: 0.4, impact: 'medium' }); } } return suggestions; } couldBeParallelized(taskId1, taskId2) { const node1 = this.nodes.get(taskId1); const node2 = this.nodes.get(taskId2); if (!node1 || !node2) return false; const title1 = node1.title.toLowerCase(); const title2 = node2.title.toLowerCase(); const similarPatterns = [ ['test', 'test'], ['component', 'component'], ['style', 'style'], ['util', 'util'], ['helper', 'helper'] ]; for (const [pattern1, pattern2] of similarPatterns) { if (title1.includes(pattern1) && title2.includes(pattern2)) { return true; } } return false; } detectRedundantDependencies() { const warnings = []; for (const [edgeId, edge] of this.edges) { if (this.isTransitiveDependency(edge.from, edge.to)) { warnings.push({ type: 'redundant', message: `Dependency ${edgeId} may be redundant due to transitive dependencies`, affectedTasks: [edge.from, edge.to], recommendation: 'Consider removing this direct dependency if transitive path exists' }); } } const taskPairs = new Map(); for (const [, edge] of this.edges) { const pairKey = `${edge.from}->${edge.to}`; if (!taskPairs.has(pairKey)) { taskPairs.set(pairKey, []); } taskPairs.get(pairKey).push(edge.type); } for (const [pairKey, types] of taskPairs) { if (types.length > 1) { const [from, to] = pairKey.split('->'); warnings.push({ type: 'redundant', message: `Multiple dependencies between ${from} and ${to}: ${types.join(', ')}`, affectedTasks: [from, to], recommendation: 'Consider consolidating into a single dependency with the most appropriate type' }); } } return warnings; } isTransitiveDependency(fromTaskId, toTaskId) { const visited = new Set(); const queue = [fromTaskId]; visited.add(fromTaskId); while (queue.length > 0) { const current = queue.shift(); const dependencies = this.reverseIndex.get(current) || new Set(); for (const dep of dependencies) { if (dep === toTaskId) continue; if (dep === fromTaskId) continue; if (!visited.has(dep)) { visited.add(dep); queue.push(dep); if (this.hasPath(dep, toTaskId)) { return true; } } } } return false; } serializeToJSON() { const timestamp = new Date().toISOString(); const serializedData = this.createSerializedData('json', timestamp); const checksum = this.calculateChecksum(serializedData); return { ...serializedData, checksum }; } serializeToYAML() { const timestamp = new Date().toISOString(); const serializedData = this.createSerializedData('yaml', timestamp); const checksum = this.calculateChecksum(serializedData); return { ...serializedData, checksum }; } async saveToFile(filePath, format = 'json', createBackup = true) { try { if (createBackup && await this.fileExists(filePath)) { await this.createBackup(filePath); } const serializedGraph = format === 'json' ? this.serializeToJSON() : this.serializeToYAML(); const content = format === 'json' ? JSON.stringify(serializedGraph, null, 2) : this.convertToYAML(serializedGraph); await this.writeFile(filePath, content); const size = Buffer.byteLength(content, 'utf8'); logger.info({ filePath, format, size, checksum: serializedGraph.checksum }, 'Graph saved successfully'); return { success: true, filePath, format, size, checksum: serializedGraph.checksum, timestamp: new Date() }; } catch (error) { logger.error({ err: error, filePath, format }, 'Failed to save graph'); return { success: false, format, size: 0, checksum: '', error: error instanceof Error ? error.message : String(error), timestamp: new Date() }; } } async loadFromFile(filePath) { try { logger.debug({ filePath }, 'Loading graph from file')