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.

532 lines (531 loc) 21.6 kB
import { getStorageManager } from '../core/storage/storage-manager.js'; import { getTaskOperations } from '../core/operations/task-operations.js'; import { getIdGenerator } from '../utils/id-generator.js'; import { InitializationMonitor } from '../../../utils/initialization-monitor.js'; import logger from '../../../logger.js'; export class EpicService { static instance; constructor() { } static getInstance() { if (!EpicService.instance) { const monitor = InitializationMonitor.getInstance(); monitor.startServiceInitialization('EpicService', [ 'StorageManager', 'TaskOperations', 'IdGenerator' ]); try { monitor.startPhase('EpicService', 'constructor'); EpicService.instance = new EpicService(); monitor.endPhase('EpicService', 'constructor'); monitor.endServiceInitialization('EpicService'); } catch (error) { monitor.endPhase('EpicService', 'constructor', error); monitor.endServiceInitialization('EpicService', error); throw error; } } return EpicService.instance; } static resetInstance() { EpicService.instance = undefined; } async createEpic(params, createdBy = 'system') { try { logger.info({ epicTitle: params.title, projectId: params.projectId, createdBy }, 'Creating new epic'); const validationResult = this.validateCreateEpicParams(params); if (!validationResult.valid) { return { success: false, error: `Epic creation validation failed: ${validationResult.errors.join(', ')}`, metadata: { filePath: 'epic-service', operation: 'create_epic', timestamp: new Date() } }; } const storageManager = await getStorageManager(); const projectExists = await storageManager.projectExists(params.projectId); if (!projectExists) { return { success: false, error: `Project ${params.projectId} not found`, metadata: { filePath: 'epic-service', operation: 'create_epic', timestamp: new Date() } }; } const idGenerator = getIdGenerator(); const idResult = await idGenerator.generateEpicId(params.projectId); if (!idResult.success) { return { success: false, error: `Failed to generate epic ID: ${idResult.error}`, metadata: { filePath: 'epic-service', operation: 'create_epic', timestamp: new Date() } }; } const epicId = idResult.id; const { getVibeTaskManagerConfig } = await import('../utils/config-loader.js'); const config = await getVibeTaskManagerConfig(); const epicTimeLimit = config?.taskManager?.rddConfig?.epicTimeLimit || 400; const epic = { id: epicId, title: params.title, description: params.description, status: 'pending', priority: params.priority || 'medium', functionalArea: params.functionalArea || 'data-management', projectId: params.projectId, estimatedHours: params.estimatedHours || epicTimeLimit, taskIds: [], dependencies: params.dependencies || [], dependents: [], metadata: { createdAt: new Date(), updatedAt: new Date(), createdBy, tags: params.tags || [] } }; const createResult = await storageManager.createEpic(epic); if (!createResult.success) { return { success: false, error: `Failed to save epic: ${createResult.error}`, metadata: createResult.metadata }; } logger.info({ epicId, epicTitle: params.title }, 'Epic created successfully'); return { success: true, data: createResult.data, metadata: { filePath: 'epic-service', operation: 'create_epic', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, epicTitle: params.title }, 'Failed to create epic'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'epic-service', operation: 'create_epic', timestamp: new Date() } }; } } async getEpic(epicId) { try { logger.debug({ epicId }, 'Getting epic'); const storageManager = await getStorageManager(); return await storageManager.getEpic(epicId); } catch (error) { logger.error({ err: error, epicId }, 'Failed to get epic'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'epic-service', operation: 'get_epic', timestamp: new Date() } }; } } async updateEpic(epicId, params, updatedBy = 'system') { try { logger.info({ epicId, updates: Object.keys(params), updatedBy }, 'Updating epic'); const validationResult = this.validateUpdateEpicParams(params); if (!validationResult.valid) { return { success: false, error: `Epic update validation failed: ${validationResult.errors.join(', ')}`, metadata: { filePath: 'epic-service', operation: 'update_epic', timestamp: new Date() } }; } const updates = { ...params, metadata: { updatedAt: new Date(), ...(params.tags && { tags: params.tags }) } }; const storageManager = await getStorageManager(); const updateResult = await storageManager.updateEpic(epicId, updates); if (!updateResult.success) { return { success: false, error: `Failed to update epic: ${updateResult.error}`, metadata: updateResult.metadata }; } logger.info({ epicId }, 'Epic updated successfully'); return { success: true, data: updateResult.data, metadata: { filePath: 'epic-service', operation: 'update_epic', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, epicId }, 'Failed to update epic'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'epic-service', operation: 'update_epic', timestamp: new Date() } }; } } async deleteEpic(epicId, deletedBy = 'system') { try { logger.info({ epicId, deletedBy }, 'Deleting epic'); const storageManager = await getStorageManager(); const deleteResult = await storageManager.deleteEpic(epicId); if (!deleteResult.success) { return { success: false, error: `Failed to delete epic: ${deleteResult.error}`, metadata: deleteResult.metadata }; } logger.info({ epicId }, 'Epic deleted successfully'); return { success: true, metadata: { filePath: 'epic-service', operation: 'delete_epic', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, epicId }, 'Failed to delete epic'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'epic-service', operation: 'delete_epic', timestamp: new Date() } }; } } async listEpics(query) { try { logger.debug({ query }, 'Listing epics'); const storageManager = await getStorageManager(); const result = await storageManager.listEpics(query?.projectId); if (!result.success) { return result; } let epics = result.data; if (query) { epics = this.applyEpicFilters(epics, query); } return { success: true, data: epics, metadata: { filePath: 'epic-service', operation: 'list_epics', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, query }, 'Failed to list epics'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'epic-service', operation: 'list_epics', timestamp: new Date() } }; } } async addTaskToEpic(epicId, taskId) { try { logger.info({ epicId, taskId }, 'Adding task to epic'); const epicResult = await this.getEpic(epicId); if (!epicResult.success) { return { success: false, error: `Epic not found: ${epicResult.error}`, metadata: epicResult.metadata }; } const epic = epicResult.data; if (epic.taskIds.includes(taskId)) { return { success: false, error: `Task ${taskId} is already in epic ${epicId}`, metadata: { filePath: 'epic-service', operation: 'add_task_to_epic', timestamp: new Date() } }; } const taskOperations = getTaskOperations(); const taskResult = await taskOperations.getTask(taskId); if (!taskResult.success) { return { success: false, error: `Task not found: ${taskResult.error}`, metadata: taskResult.metadata }; } const updatedTaskIds = [...epic.taskIds, taskId]; const storageManager = await getStorageManager(); const finalUpdateResult = await storageManager.updateEpic(epicId, { taskIds: updatedTaskIds }); logger.info({ epicId, taskId }, 'Task added to epic successfully'); return finalUpdateResult; } catch (error) { logger.error({ err: error, epicId, taskId }, 'Failed to add task to epic'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'epic-service', operation: 'add_task_to_epic', timestamp: new Date() } }; } } async removeTaskFromEpic(epicId, taskId) { try { logger.info({ epicId, taskId }, 'Removing task from epic'); const epicResult = await this.getEpic(epicId); if (!epicResult.success) { return { success: false, error: `Epic not found: ${epicResult.error}`, metadata: epicResult.metadata }; } const epic = epicResult.data; if (!epic.taskIds.includes(taskId)) { return { success: false, error: `Task ${taskId} is not in epic ${epicId}`, metadata: { filePath: 'epic-service', operation: 'remove_task_from_epic', timestamp: new Date() } }; } const updatedTaskIds = epic.taskIds.filter(id => id !== taskId); const storageManager = await getStorageManager(); const updateResult = await storageManager.updateEpic(epicId, { taskIds: updatedTaskIds, metadata: { ...epic.metadata, updatedAt: new Date() } }); logger.info({ epicId, taskId }, 'Task removed from epic successfully'); return updateResult; } catch (error) { logger.error({ err: error, epicId, taskId }, 'Failed to remove task from epic'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'epic-service', operation: 'remove_task_from_epic', timestamp: new Date() } }; } } async getEpicProgress(epicId) { try { logger.debug({ epicId }, 'Getting epic progress'); const epicResult = await this.getEpic(epicId); if (!epicResult.success) { return { success: false, error: `Epic not found: ${epicResult.error}`, metadata: epicResult.metadata }; } const epic = epicResult.data; const taskOperations = getTaskOperations(); const tasks = []; for (const taskId of epic.taskIds) { const taskResult = await taskOperations.getTask(taskId); if (taskResult.success) { tasks.push(taskResult.data); } } const totalTasks = tasks.length; const completedTasks = tasks.filter(t => t.status === 'completed').length; const inProgressTasks = tasks.filter(t => t.status === 'in_progress').length; const pendingTasks = tasks.filter(t => t.status === 'pending').length; const blockedTasks = tasks.filter(t => t.status === 'blocked').length; const progressPercentage = totalTasks > 0 ? Math.round((completedTasks / totalTasks) * 100) : 0; const estimatedHours = tasks.reduce((sum, task) => sum + task.estimatedHours, 0); const actualHours = tasks.reduce((sum, task) => sum + (task.actualHours || 0), 0); const remainingHours = Math.max(0, estimatedHours - actualHours); const progress = { epicId, totalTasks, completedTasks, inProgressTasks, pendingTasks, blockedTasks, progressPercentage, estimatedHours, actualHours, remainingHours }; return { success: true, data: progress, metadata: { filePath: 'epic-service', operation: 'get_epic_progress', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, epicId }, 'Failed to get epic progress'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'epic-service', operation: 'get_epic_progress', timestamp: new Date() } }; } } validateCreateEpicParams(params) { const errors = []; if (!params.title || typeof params.title !== 'string' || params.title.trim().length === 0) { errors.push('Epic title is required and must be a non-empty string'); } if (params.title && params.title.length > 200) { errors.push('Epic title must be 200 characters or less'); } if (!params.description || typeof params.description !== 'string' || params.description.trim().length === 0) { errors.push('Epic description is required and must be a non-empty string'); } if (!params.projectId || typeof params.projectId !== 'string') { errors.push('Project ID is required and must be a string'); } if (params.priority && !['low', 'medium', 'high', 'critical'].includes(params.priority)) { errors.push('Epic priority must be one of: low, medium, high, critical'); } if (params.estimatedHours !== undefined && (typeof params.estimatedHours !== 'number' || params.estimatedHours < 0)) { errors.push('Estimated hours must be a non-negative number'); } if (params.tags && !Array.isArray(params.tags)) { errors.push('Tags must be an array'); } if (params.dependencies && !Array.isArray(params.dependencies)) { errors.push('Dependencies must be an array'); } return { valid: errors.length === 0, errors }; } validateUpdateEpicParams(params) { const errors = []; if (params.title !== undefined) { if (typeof params.title !== 'string' || params.title.trim().length === 0) { errors.push('Epic title must be a non-empty string'); } if (params.title.length > 200) { errors.push('Epic title must be 200 characters or less'); } } if (params.description !== undefined) { if (typeof params.description !== 'string' || params.description.trim().length === 0) { errors.push('Epic description must be a non-empty string'); } } if (params.status && !['pending', 'in_progress', 'completed', 'blocked', 'cancelled'].includes(params.status)) { errors.push('Epic status must be one of: pending, in_progress, completed, blocked, cancelled'); } if (params.priority && !['low', 'medium', 'high', 'critical'].includes(params.priority)) { errors.push('Epic priority must be one of: low, medium, high, critical'); } if (params.estimatedHours !== undefined && (typeof params.estimatedHours !== 'number' || params.estimatedHours < 0)) { errors.push('Estimated hours must be a non-negative number'); } if (params.tags && !Array.isArray(params.tags)) { errors.push('Tags must be an array'); } if (params.dependencies && !Array.isArray(params.dependencies)) { errors.push('Dependencies must be an array'); } return { valid: errors.length === 0, errors }; } applyEpicFilters(epics, query) { let filteredEpics = [...epics]; if (query.status) { filteredEpics = filteredEpics.filter(epic => epic.status === query.status); } if (query.priority) { filteredEpics = filteredEpics.filter(epic => epic.priority === query.priority); } if (query.tags && query.tags.length > 0) { filteredEpics = filteredEpics.filter(epic => query.tags.some(tag => epic.metadata.tags.includes(tag))); } if (query.createdAfter) { filteredEpics = filteredEpics.filter(epic => epic.metadata.createdAt >= query.createdAfter); } if (query.createdBefore) { filteredEpics = filteredEpics.filter(epic => epic.metadata.createdAt <= query.createdBefore); } if (query.offset !== undefined) { filteredEpics = filteredEpics.slice(query.offset); } if (query.limit !== undefined) { filteredEpics = filteredEpics.slice(0, query.limit); } return filteredEpics; } } export function getEpicService() { return EpicService.getInstance(); }