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.

679 lines (678 loc) 28.4 kB
import { getStorageManager } from '../storage/storage-manager.js'; import { getIdGenerator } from '../../utils/id-generator.js'; import { DependencyValidator } from '../../services/dependency-validator.js'; import logger from '../../../../logger.js'; export class DependencyOperations { static instance; validator; constructor() { this.validator = new DependencyValidator(); } static getInstance() { if (!DependencyOperations.instance) { DependencyOperations.instance = new DependencyOperations(); } return DependencyOperations.instance; } static resetInstance() { DependencyOperations.instance = undefined; } async createDependency(params, createdBy = 'system') { try { logger.info({ fromTaskId: params.fromTaskId, toTaskId: params.toTaskId, type: params.type, createdBy }, 'Creating new dependency'); const validationResult = this.validateCreateParams(params); if (!validationResult.valid) { return { success: false, error: `Dependency creation validation failed: ${validationResult.errors.join(', ')}`, metadata: { filePath: 'dependency-operations', operation: 'create_dependency', timestamp: new Date() } }; } const storageManager = await getStorageManager(); const fromTaskExists = await storageManager.taskExists(params.fromTaskId); if (!fromTaskExists) { return { success: false, error: `From task ${params.fromTaskId} not found`, metadata: { filePath: 'dependency-operations', operation: 'create_dependency', timestamp: new Date() } }; } const toTaskExists = await storageManager.taskExists(params.toTaskId); if (!toTaskExists) { return { success: false, error: `To task ${params.toTaskId} not found`, metadata: { filePath: 'dependency-operations', operation: 'create_dependency', timestamp: new Date() } }; } const enhancedValidation = await this.validator.validateDependencyBeforeCreation(params.fromTaskId, params.toTaskId, 'project-id'); if (!enhancedValidation.isValid) { const criticalErrors = enhancedValidation.errors.filter(e => e.severity === 'critical' || e.severity === 'high'); if (criticalErrors.length > 0) { return { success: false, error: `Dependency validation failed: ${criticalErrors.map(e => e.message).join(', ')}`, metadata: { filePath: 'dependency-operations', operation: 'create_dependency', timestamp: new Date() } }; } if (enhancedValidation.warnings.length > 0) { logger.warn({ fromTaskId: params.fromTaskId, toTaskId: params.toTaskId, warnings: enhancedValidation.warnings.map(w => w.message) }, 'Dependency creation warnings detected'); } } const circularCheckResult = await this.checkCircularDependency(params.fromTaskId, params.toTaskId); if (!circularCheckResult.valid) { return { success: false, error: `Circular dependency detected: ${circularCheckResult.errors.join(', ')}`, metadata: { filePath: 'dependency-operations', operation: 'create_dependency', timestamp: new Date() } }; } const idGenerator = getIdGenerator(); const idResult = await idGenerator.generateDependencyId(params.fromTaskId, params.toTaskId); if (!idResult.success) { return { success: false, error: `Failed to generate dependency ID: ${idResult.error}`, metadata: { filePath: 'dependency-operations', operation: 'create_dependency', timestamp: new Date() } }; } const dependencyId = idResult.id; const dependency = { id: dependencyId, fromTaskId: params.fromTaskId, toTaskId: params.toTaskId, type: params.type, description: params.description, critical: params.critical || false, metadata: { createdAt: new Date(), createdBy, reason: params.description } }; const createResult = await storageManager.createDependency(dependency); if (!createResult.success) { return { success: false, error: `Failed to save dependency: ${createResult.error}`, metadata: createResult.metadata }; } await this.updateTaskDependencyLists(params.fromTaskId, params.toTaskId, dependencyId); logger.info({ dependencyId, fromTaskId: params.fromTaskId, toTaskId: params.toTaskId }, 'Dependency created successfully'); return { success: true, data: createResult.data, metadata: { filePath: 'dependency-operations', operation: 'create_dependency', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, fromTaskId: params.fromTaskId, toTaskId: params.toTaskId }, 'Failed to create dependency'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'dependency-operations', operation: 'create_dependency', timestamp: new Date() } }; } } async getDependency(dependencyId) { try { logger.debug({ dependencyId }, 'Getting dependency'); const storageManager = await getStorageManager(); return await storageManager.getDependency(dependencyId); } catch (error) { logger.error({ err: error, dependencyId }, 'Failed to get dependency'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'dependency-operations', operation: 'get_dependency', timestamp: new Date() } }; } } async updateDependency(dependencyId, params, updatedBy = 'system') { try { logger.info({ dependencyId, updates: Object.keys(params), updatedBy }, 'Updating dependency'); const validationResult = this.validateUpdateParams(params); if (!validationResult.valid) { return { success: false, error: `Dependency update validation failed: ${validationResult.errors.join(', ')}`, metadata: { filePath: 'dependency-operations', operation: 'update_dependency', timestamp: new Date() } }; } const updates = { ...params }; const storageManager = await getStorageManager(); const updateResult = await storageManager.updateDependency(dependencyId, updates); if (!updateResult.success) { return { success: false, error: `Failed to update dependency: ${updateResult.error}`, metadata: updateResult.metadata }; } logger.info({ dependencyId }, 'Dependency updated successfully'); return { success: true, data: updateResult.data, metadata: { filePath: 'dependency-operations', operation: 'update_dependency', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, dependencyId }, 'Failed to update dependency'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'dependency-operations', operation: 'update_dependency', timestamp: new Date() } }; } } async deleteDependency(dependencyId, deletedBy = 'system') { try { logger.info({ dependencyId, deletedBy }, 'Deleting dependency'); const storageManager = await getStorageManager(); const dependencyResult = await storageManager.getDependency(dependencyId); if (!dependencyResult.success) { return { success: false, error: `Dependency ${dependencyId} not found`, metadata: { filePath: 'dependency-operations', operation: 'delete_dependency', timestamp: new Date() } }; } const dependency = dependencyResult.data; const deleteResult = await storageManager.deleteDependency(dependencyId); if (!deleteResult.success) { return { success: false, error: `Failed to delete dependency: ${deleteResult.error}`, metadata: deleteResult.metadata }; } await this.removeFromTaskDependencyLists(dependency.fromTaskId, dependency.toTaskId, dependencyId); logger.info({ dependencyId }, 'Dependency deleted successfully'); return { success: true, metadata: { filePath: 'dependency-operations', operation: 'delete_dependency', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, dependencyId }, 'Failed to delete dependency'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'dependency-operations', operation: 'delete_dependency', timestamp: new Date() } }; } } async getDependenciesForTask(taskId) { try { logger.debug({ taskId }, 'Getting dependencies for task'); const storageManager = await getStorageManager(); return await storageManager.getDependenciesForTask(taskId); } catch (error) { logger.error({ err: error, taskId }, 'Failed to get dependencies for task'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'dependency-operations', operation: 'get_dependencies_for_task', timestamp: new Date() } }; } } async getDependentsForTask(taskId) { try { logger.debug({ taskId }, 'Getting dependents for task'); const storageManager = await getStorageManager(); return await storageManager.getDependentsForTask(taskId); } catch (error) { logger.error({ err: error, taskId }, 'Failed to get dependents for task'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'dependency-operations', operation: 'get_dependents_for_task', timestamp: new Date() } }; } } async generateDependencyGraph(projectId) { try { logger.info({ projectId }, 'Generating dependency graph'); const storageManager = await getStorageManager(); const tasksResult = await storageManager.listTasks(projectId); if (!tasksResult.success) { return { success: false, error: `Failed to get tasks for project: ${tasksResult.error}`, metadata: tasksResult.metadata }; } const dependenciesResult = await storageManager.listDependencies(projectId); if (!dependenciesResult.success) { return { success: false, error: `Failed to get dependencies for project: ${dependenciesResult.error}`, metadata: dependenciesResult.metadata }; } const tasks = tasksResult.data; const dependencies = dependenciesResult.data; const nodes = new Map(); const edges = []; for (const task of tasks) { nodes.set(task.id, { taskId: task.id, title: task.title, status: task.status, priority: task.priority, estimatedHours: task.estimatedHours, dependencies: [], dependents: [], depth: 0, criticalPath: false }); } for (const dependency of dependencies) { edges.push(dependency); const fromNode = nodes.get(dependency.fromTaskId); const toNode = nodes.get(dependency.toTaskId); if (fromNode) { fromNode.dependencies.push(dependency.toTaskId); } if (toNode) { toNode.dependents.push(dependency.fromTaskId); } } const executionOrder = this.calculateExecutionOrder(nodes, edges); const criticalPath = this.calculateCriticalPath(nodes, edges); const graph = { projectId, nodes, edges, executionOrder, criticalPath, statistics: { totalTasks: tasks.length, totalDependencies: dependencies.length, maxDepth: Math.max(...Array.from(nodes.values()).map(node => node.depth)), cyclicDependencies: [], orphanedTasks: [] }, metadata: { generatedAt: new Date(), version: '1.0.0', isValid: true, validationErrors: [] } }; const saveResult = await storageManager.saveDependencyGraph(projectId, graph); if (!saveResult.success) { logger.warn({ projectId, error: saveResult.error }, 'Failed to save dependency graph, but generation succeeded'); } logger.info({ projectId, taskCount: tasks.length, dependencyCount: dependencies.length }, 'Dependency graph generated successfully'); return { success: true, data: graph, metadata: { filePath: 'dependency-operations', operation: 'generate_dependency_graph', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, projectId }, 'Failed to generate dependency graph'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'dependency-operations', operation: 'generate_dependency_graph', timestamp: new Date() } }; } } async validateProjectDependencies(projectId) { try { logger.info({ projectId }, 'Starting enhanced dependency validation for project'); const validationResult = await this.validator.validateProjectDependencies(projectId); logger.info({ projectId, isValid: validationResult.isValid, errorsFound: validationResult.errors.length, warningsFound: validationResult.warnings.length, suggestionsFound: validationResult.suggestions.length, circularDependencies: validationResult.circularDependencies.length, validationTime: validationResult.metadata.validationTime }, 'Enhanced dependency validation completed'); return { success: true, data: validationResult, metadata: { filePath: 'dependency-operations', operation: 'validate_project_dependencies', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, projectId }, 'Failed to validate project dependencies'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'dependency-operations', operation: 'validate_project_dependencies', timestamp: new Date() } }; } } async loadDependencyGraph(projectId) { try { logger.debug({ projectId }, 'Loading dependency graph'); const storageManager = await getStorageManager(); return await storageManager.loadDependencyGraph(projectId); } catch (error) { logger.error({ err: error, projectId }, 'Failed to load dependency graph'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'dependency-operations', operation: 'load_dependency_graph', timestamp: new Date() } }; } } async checkCircularDependency(fromTaskId, toTaskId) { try { const storageManager = await getStorageManager(); const dependenciesResult = await storageManager.getDependenciesForTask(toTaskId); if (!dependenciesResult.success) { return { valid: true, errors: [], warnings: [] }; } const visited = new Set(); const recursionStack = new Set(); const hasCycle = async (taskId) => { if (recursionStack.has(taskId)) { return true; } if (visited.has(taskId)) { return false; } visited.add(taskId); recursionStack.add(taskId); if (taskId === fromTaskId) { return true; } const taskDepsResult = await storageManager.getDependenciesForTask(taskId); if (taskDepsResult.success) { for (const dep of taskDepsResult.data) { if (await hasCycle(dep.toTaskId)) { return true; } } } recursionStack.delete(taskId); return false; }; const cycleExists = await hasCycle(toTaskId); if (cycleExists) { return { valid: false, errors: [`Adding dependency from ${fromTaskId} to ${toTaskId} would create a circular dependency`], warnings: [] }; } return { valid: true, errors: [], warnings: [] }; } catch (error) { return { valid: false, errors: [`Failed to check for circular dependencies: ${error instanceof Error ? error.message : String(error)}`], warnings: [] }; } } async updateTaskDependencyLists(fromTaskId, toTaskId, dependencyId) { try { const storageManager = await getStorageManager(); const fromTaskResult = await storageManager.getTask(fromTaskId); if (fromTaskResult.success) { const fromTask = fromTaskResult.data; if (!fromTask.dependencies.includes(toTaskId)) { fromTask.dependencies.push(toTaskId); await storageManager.updateTask(fromTaskId, { dependencies: fromTask.dependencies }); } } const toTaskResult = await storageManager.getTask(toTaskId); if (toTaskResult.success) { const toTask = toTaskResult.data; if (!toTask.dependents.includes(fromTaskId)) { toTask.dependents.push(fromTaskId); await storageManager.updateTask(toTaskId, { dependents: toTask.dependents }); } } } catch (error) { logger.warn({ err: error, fromTaskId, toTaskId, dependencyId }, 'Failed to update task dependency lists'); } } async removeFromTaskDependencyLists(fromTaskId, toTaskId, dependencyId) { try { const storageManager = await getStorageManager(); const fromTaskResult = await storageManager.getTask(fromTaskId); if (fromTaskResult.success) { const fromTask = fromTaskResult.data; const index = fromTask.dependencies.indexOf(toTaskId); if (index > -1) { fromTask.dependencies.splice(index, 1); await storageManager.updateTask(fromTaskId, { dependencies: fromTask.dependencies }); } } const toTaskResult = await storageManager.getTask(toTaskId); if (toTaskResult.success) { const toTask = toTaskResult.data; const index = toTask.dependents.indexOf(fromTaskId); if (index > -1) { toTask.dependents.splice(index, 1); await storageManager.updateTask(toTaskId, { dependents: toTask.dependents }); } } } catch (error) { logger.warn({ err: error, fromTaskId, toTaskId, dependencyId }, 'Failed to remove from task dependency lists'); } } calculateExecutionOrder(nodes, edges) { const inDegree = new Map(); const adjList = new Map(); for (const [taskId] of nodes) { inDegree.set(taskId, 0); adjList.set(taskId, []); } for (const edge of edges) { adjList.get(edge.fromTaskId)?.push(edge.toTaskId); inDegree.set(edge.toTaskId, (inDegree.get(edge.toTaskId) || 0) + 1); } const queue = []; const result = []; for (const [taskId, degree] of inDegree) { if (degree === 0) { queue.push(taskId); } } while (queue.length > 0) { const current = queue.shift(); result.push(current); for (const neighbor of adjList.get(current) || []) { const newDegree = (inDegree.get(neighbor) || 0) - 1; inDegree.set(neighbor, newDegree); if (newDegree === 0) { queue.push(neighbor); } } } return result; } calculateCriticalPath(nodes, edges) { const pathLengths = new Map(); const predecessors = new Map(); for (const [taskId, node] of nodes) { pathLengths.set(taskId, node.estimatedHours); } const executionOrder = this.calculateExecutionOrder(nodes, edges); for (const taskId of executionOrder) { const node = nodes.get(taskId); for (const depTaskId of node.dependencies) { const currentLength = pathLengths.get(taskId) || 0; const depLength = pathLengths.get(depTaskId) || 0; const newLength = depLength + node.estimatedHours; if (newLength > currentLength) { pathLengths.set(taskId, newLength); predecessors.set(taskId, depTaskId); } } } let maxLength = 0; let endTask = ''; for (const [taskId, length] of pathLengths) { if (length > maxLength) { maxLength = length; endTask = taskId; } } const criticalPath = []; let current = endTask; while (current) { criticalPath.unshift(current); current = predecessors.get(current) || ''; } return criticalPath; } validateCreateParams(params) { const errors = []; if (!params.fromTaskId || typeof params.fromTaskId !== 'string') { errors.push('From task ID is required and must be a string'); } if (!params.toTaskId || typeof params.toTaskId !== 'string') { errors.push('To task ID is required and must be a string'); } if (params.fromTaskId === params.toTaskId) { errors.push('A task cannot depend on itself'); } if (!['blocks', 'enables', 'requires', 'suggests'].includes(params.type)) { errors.push('Dependency type must be one of: blocks, enables, requires, suggests'); } if (!params.description || typeof params.description !== 'string' || params.description.trim().length === 0) { errors.push('Dependency description is required and must be a non-empty string'); } return { valid: errors.length === 0, errors }; } validateUpdateParams(params) { const errors = []; if (params.type !== undefined && !['blocks', 'enables', 'requires', 'suggests'].includes(params.type)) { errors.push('Dependency type must be one of: blocks, enables, requires, suggests'); } if (params.description !== undefined) { if (typeof params.description !== 'string' || params.description.trim().length === 0) { errors.push('Dependency description must be a non-empty string'); } } if (params.critical !== undefined && typeof params.critical !== 'boolean') { errors.push('Critical flag must be a boolean'); } return { valid: errors.length === 0, errors }; } } export function getDependencyOperations() { return DependencyOperations.getInstance(); }