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
JavaScript
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();
}