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.

866 lines (865 loc) 34.8 kB
import { getStorageManager } from '../storage/storage-manager.js'; import { getVibeTaskManagerConfig } from '../../utils/config-loader.js'; import { getIdGenerator } from '../../utils/id-generator.js'; import { UnifiedSecurityEngine, createDefaultSecurityConfig } from '../unified-security-engine.js'; import logger from '../../../../logger.js'; export class TaskOperations { static instance; securityEngine; constructor() { const securityConfig = { ...createDefaultSecurityConfig(), concurrentAccess: { enabled: true, maxLockDuration: 300000, deadlockDetection: true, lockCleanupInterval: 60000, maxLocksPerResource: 10 } }; this.securityEngine = UnifiedSecurityEngine.getInstance(securityConfig); } async acquireLock(resource, _owner, operation, options = {}) { const result = await this.securityEngine.acquireLock(resource, operation, undefined, options.timeout); if (result.success) { return { success: true, lock: { id: result.data.lockId }, error: undefined }; } else { return { success: false, lock: undefined, error: result.error.message }; } } async releaseLock(lockId) { const result = await this.securityEngine.releaseLock(lockId); return result.success; } async sanitizeData(data) { const result = await this.securityEngine.sanitizeData(data); if (result.success) { return { success: true, sanitizedData: result.data.sanitizedData, violations: result.data.violations || [] }; } else { return { success: false, sanitizedData: data, violations: [result.error.message] }; } } static getInstance() { if (!TaskOperations.instance) { TaskOperations.instance = new TaskOperations(); } return TaskOperations.instance; } static resetInstance() { TaskOperations.instance = undefined; } async createTask(params, createdBy = 'system') { const lockIds = []; try { logger.info({ taskTitle: params.title, projectId: params.projectId, createdBy }, 'Creating new task'); const projectLockResult = await this.acquireLock(`project:${params.projectId}`, createdBy, 'write', { timeout: 30000, metadata: { operation: 'create_task', taskTitle: params.title } }); if (!projectLockResult.success) { return { success: false, error: `Failed to acquire project lock: ${projectLockResult.error}`, metadata: { filePath: 'task-operations', operation: 'create_task', timestamp: new Date() } }; } lockIds.push(projectLockResult.lock.id); let resolvedEpicId = params.epicId; if (!resolvedEpicId) { logger.debug({ taskTitle: params.title, projectId: params.projectId }, 'Epic ID not provided, resolving automatically'); const { getEpicContextResolver } = await import('../../services/epic-context-resolver.js'); const epicResolver = getEpicContextResolver(); const epicContext = await epicResolver.resolveEpicContext({ projectId: params.projectId, taskContext: { title: params.title, description: params.description, type: params.type || 'development', tags: params.tags || [] } }); resolvedEpicId = epicContext.epicId; logger.debug({ resolvedEpicId, source: epicContext.source }, 'Epic ID resolved automatically'); } const epicLockResult = await this.acquireLock(`epic:${resolvedEpicId}`, createdBy, 'write', { timeout: 30000, metadata: { operation: 'create_task', taskTitle: params.title } }); if (!epicLockResult.success) { await this.releaseLock(lockIds[0]); return { success: false, error: `Failed to acquire epic lock: ${epicLockResult.error}`, metadata: { filePath: 'task-operations', operation: 'create_task', timestamp: new Date() } }; } lockIds.push(epicLockResult.lock.id); const paramsWithEpicId = { ...params, epicId: resolvedEpicId }; const sanitizationResult = await this.sanitizeData(paramsWithEpicId); if (!sanitizationResult.success) { logger.error({ violations: sanitizationResult.violations, taskTitle: params.title }, 'Task creation input sanitization failed'); return { success: false, error: `Input sanitization failed: ${sanitizationResult.violations.join(', ')}`, metadata: { filePath: 'task-operations', operation: 'create_task', timestamp: new Date() } }; } const sanitizedParams = sanitizationResult.sanitizedData; const validationResult = this.validateCreateTaskParams(sanitizedParams); if (!validationResult.valid) { return { success: false, error: `Task creation validation failed: ${validationResult.errors.join(', ')}`, metadata: { filePath: 'task-operations', operation: 'create_task', timestamp: new Date() } }; } const storageManager = await getStorageManager(); const projectExists = await storageManager.projectExists(sanitizedParams.projectId); if (!projectExists) { return { success: false, error: `Project ${sanitizedParams.projectId} not found`, metadata: { filePath: 'task-operations', operation: 'create_task', timestamp: new Date() } }; } const { validateEpicForTask } = await import('../../utils/epic-validator.js'); const epicValidationResult = await validateEpicForTask({ epicId: sanitizedParams.epicId, projectId: sanitizedParams.projectId, title: sanitizedParams.title, description: sanitizedParams.description, type: sanitizedParams.type, tags: sanitizedParams.tags }); if (!epicValidationResult.valid) { return { success: false, error: `Epic validation failed: ${epicValidationResult.error || 'Unknown error'}`, metadata: { filePath: 'task-operations', operation: 'create_task', timestamp: new Date() } }; } if (epicValidationResult.epicId !== sanitizedParams.epicId) { logger.info({ originalEpicId: sanitizedParams.epicId, resolvedEpicId: epicValidationResult.epicId, created: epicValidationResult.created }, 'Epic ID resolved during validation'); sanitizedParams.epicId = epicValidationResult.epicId; } const idGenerator = getIdGenerator(); const idResult = await idGenerator.generateTaskId(); if (!idResult.success) { return { success: false, error: `Failed to generate task ID: ${idResult.error}`, metadata: { filePath: 'task-operations', operation: 'create_task', timestamp: new Date() } }; } const taskId = idResult.id; const config = await getVibeTaskManagerConfig(); if (!config) { return { success: false, error: 'Failed to load task manager configuration', metadata: { filePath: 'task-operations', operation: 'create_task', timestamp: new Date() } }; } const task = { id: taskId, title: sanitizedParams.title, description: sanitizedParams.description, status: 'pending', priority: sanitizedParams.priority || 'medium', type: sanitizedParams.type || 'development', functionalArea: sanitizedParams.functionalArea || 'data-management', estimatedHours: sanitizedParams.estimatedHours || 4, epicId: sanitizedParams.epicId, projectId: sanitizedParams.projectId, dependencies: [], dependents: [], filePaths: sanitizedParams.filePaths || [], acceptanceCriteria: sanitizedParams.acceptanceCriteria || [], testingRequirements: { unitTests: [], integrationTests: [], performanceTests: [], coverageTarget: config.taskManager.performanceTargets.minTestCoverage }, performanceCriteria: { responseTime: `<${config.taskManager.performanceTargets.maxResponseTime}ms`, memoryUsage: `<${config.taskManager.performanceTargets.maxMemoryUsage}MB` }, qualityCriteria: { codeQuality: ['TypeScript strict mode', 'ESLint compliance'], documentation: ['JSDoc comments'], typeScript: true, eslint: true }, integrationCriteria: { compatibility: ['Existing MCP patterns'], patterns: ['Tool registration pattern'] }, validationMethods: { automated: ['Unit tests', 'Integration tests'], manual: ['Code review'] }, assignedAgent: sanitizedParams.assignedAgent, createdAt: new Date(), updatedAt: new Date(), createdBy, tags: sanitizedParams.tags || [], metadata: { createdAt: new Date(), updatedAt: new Date(), createdBy, tags: sanitizedParams.tags || [] } }; const createResult = await storageManager.createTask(task); if (!createResult.success) { return { success: false, error: `Failed to save task: ${createResult.error}`, metadata: createResult.metadata }; } try { const { getEpicService } = await import('../../services/epic-service.js'); const epicService = getEpicService(); const addTaskResult = await epicService.addTaskToEpic(sanitizedParams.epicId, taskId); if (!addTaskResult.success) { logger.warn({ taskId, epicId: sanitizedParams.epicId, error: addTaskResult.error }, 'Failed to add task to epic taskIds array'); } else { logger.debug({ taskId, epicId: sanitizedParams.epicId }, 'Task added to epic taskIds array'); } } catch (error) { logger.warn({ err: error, taskId, epicId: sanitizedParams.epicId }, 'Error updating epic with new task'); } logger.info({ taskId, taskTitle: params.title }, 'Task created successfully'); return { success: true, data: createResult.data, metadata: { filePath: 'task-operations', operation: 'create_task', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, taskTitle: params.title }, 'Failed to create task'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'task-operations', operation: 'create_task', timestamp: new Date() } }; } finally { for (const lockId of lockIds) { try { await this.releaseLock(lockId); } catch (error) { logger.error({ err: error, lockId }, 'Failed to release lock during task creation cleanup'); } } } } async getTask(taskId) { try { logger.debug({ taskId }, 'Getting task'); const storageManager = await getStorageManager(); return await storageManager.getTask(taskId); } catch (error) { logger.error({ err: error, taskId }, 'Failed to get task'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'task-operations', operation: 'get_task', timestamp: new Date() } }; } } async updateTask(taskId, params, updatedBy = 'system') { try { logger.info({ taskId, updates: Object.keys(params), updatedBy }, 'Updating task'); const validationResult = this.validateUpdateTaskParams(params); if (!validationResult.valid) { return { success: false, error: `Task update validation failed: ${validationResult.errors.join(', ')}`, metadata: { filePath: 'task-operations', operation: 'update_task', timestamp: new Date() } }; } const storageManager = await getStorageManager(); const existingResult = await storageManager.getTask(taskId); if (!existingResult.success) { return { success: false, error: `Task not found: ${existingResult.error}`, metadata: existingResult.metadata }; } const existingTask = existingResult.data; const updates = { ...params, metadata: { ...existingTask.metadata, updatedAt: new Date(), ...(params.tags && { tags: params.tags }) } }; const updateResult = await storageManager.updateTask(taskId, updates); if (!updateResult.success) { return { success: false, error: `Failed to update task: ${updateResult.error}`, metadata: updateResult.metadata }; } logger.info({ taskId }, 'Task updated successfully'); return { success: true, data: updateResult.data, metadata: { filePath: 'task-operations', operation: 'update_task', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, taskId }, 'Failed to update task'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'task-operations', operation: 'update_task', timestamp: new Date() } }; } } async deleteTask(taskId, deletedBy = 'system') { try { logger.info({ taskId, deletedBy }, 'Deleting task'); const storageManager = await getStorageManager(); const taskExists = await storageManager.taskExists(taskId); if (!taskExists) { return { success: false, error: `Task ${taskId} not found`, metadata: { filePath: 'task-operations', operation: 'delete_task', timestamp: new Date() } }; } const deleteResult = await storageManager.deleteTask(taskId); if (!deleteResult.success) { return { success: false, error: `Failed to delete task: ${deleteResult.error}`, metadata: deleteResult.metadata }; } logger.info({ taskId }, 'Task deleted successfully'); return { success: true, metadata: { filePath: 'task-operations', operation: 'delete_task', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, taskId }, 'Failed to delete task'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'task-operations', operation: 'delete_task', timestamp: new Date() } }; } } async listTasks(query) { try { logger.debug({ query }, 'Listing tasks'); const storageManager = await getStorageManager(); let result; if (query?.status && query?.projectId) { result = await storageManager.getTasksByStatus(query.status, query.projectId); } else if (query?.priority && query?.projectId) { result = await storageManager.getTasksByPriority(query.priority, query.projectId); } else { result = await storageManager.listTasks(query?.projectId, query?.epicId); } if (!result.success) { return result; } let tasks = result.data; if (query) { tasks = this.applyTaskFilters(tasks, query); } return { success: true, data: tasks, metadata: { filePath: 'task-operations', operation: 'list_tasks', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, query }, 'Failed to list tasks'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'task-operations', operation: 'list_tasks', timestamp: new Date() } }; } } async searchTasks(searchQuery, query) { try { logger.debug({ searchQuery, query }, 'Searching tasks'); const storageManager = await getStorageManager(); const searchResult = await storageManager.searchTasks(searchQuery, query?.projectId); if (!searchResult.success) { return searchResult; } let tasks = searchResult.data; if (query) { tasks = this.applyTaskFilters(tasks, query); } return { success: true, data: tasks, metadata: { filePath: 'task-operations', operation: 'search_tasks', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, searchQuery }, 'Failed to search tasks'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'task-operations', operation: 'search_tasks', timestamp: new Date() } }; } } validateCreateTaskParams(params) { const errors = []; if (!params.title || typeof params.title !== 'string' || params.title.trim().length === 0) { errors.push('Task title is required and must be a non-empty string'); } if (params.title && params.title.length > 200) { errors.push('Task title must be 200 characters or less'); } if (!params.description || typeof params.description !== 'string' || params.description.trim().length === 0) { errors.push('Task 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.epicId || typeof params.epicId !== 'string') { errors.push('Epic ID is required and must be a string'); } if (params.estimatedHours !== undefined && (typeof params.estimatedHours !== 'number' || params.estimatedHours < 0)) { errors.push('Estimated hours must be a non-negative number'); } if (params.filePaths && !Array.isArray(params.filePaths)) { errors.push('File paths must be an array of strings'); } if (params.acceptanceCriteria && !Array.isArray(params.acceptanceCriteria)) { errors.push('Acceptance criteria must be an array of strings'); } if (params.tags && !Array.isArray(params.tags)) { errors.push('Tags must be an array of strings'); } return { valid: errors.length === 0, errors }; } validateUpdateTaskParams(params) { const errors = []; if (params.title !== undefined) { if (typeof params.title !== 'string' || params.title.trim().length === 0) { errors.push('Task title must be a non-empty string'); } if (params.title.length > 200) { errors.push('Task title must be 200 characters or less'); } } if (params.description !== undefined) { if (typeof params.description !== 'string' || params.description.trim().length === 0) { errors.push('Task description must be a non-empty string'); } } if (params.status !== undefined) { if (!['pending', 'in_progress', 'completed', 'blocked', 'cancelled'].includes(params.status)) { errors.push('Status must be one of: pending, in_progress, completed, blocked, cancelled'); } } if (params.priority !== undefined) { if (!['low', 'medium', 'high', 'critical'].includes(params.priority)) { errors.push('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.actualHours !== undefined && (typeof params.actualHours !== 'number' || params.actualHours < 0)) { errors.push('Actual hours must be a non-negative number'); } return { valid: errors.length === 0, errors }; } applyTaskFilters(tasks, query) { let filtered = tasks; if (query.type) { filtered = filtered.filter(task => task.type === query.type); } if (query.assignedAgent) { filtered = filtered.filter(task => task.assignedAgent === query.assignedAgent); } if (query.tags && query.tags.length > 0) { filtered = filtered.filter(task => query.tags.some(tag => task.metadata.tags.includes(tag))); } if (query.createdAfter) { filtered = filtered.filter(task => task.metadata.createdAt >= query.createdAfter); } if (query.createdBefore) { filtered = filtered.filter(task => task.metadata.createdAt <= query.createdBefore); } if (query.offset) { filtered = filtered.slice(query.offset); } if (query.limit) { filtered = filtered.slice(0, query.limit); } return filtered; } async updateTaskStatus(taskId, status, updatedBy = 'system') { try { logger.info({ taskId, status, updatedBy }, 'Updating task status'); const validStatuses = ['pending', 'in_progress', 'completed', 'failed', 'blocked']; if (!validStatuses.includes(status)) { return { success: false, error: `Invalid task status: ${status}. Valid statuses are: ${validStatuses.join(', ')}`, metadata: { filePath: 'task-operations', operation: 'update_task_status', timestamp: new Date() } }; } const updateParams = { status }; return await this.updateTask(taskId, updateParams, updatedBy); } catch (error) { logger.error({ err: error, taskId, status }, 'Failed to update task status'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'task-operations', operation: 'update_task_status', timestamp: new Date() } }; } } async updateTaskMetadata(taskId, metadata, updatedBy = 'system') { try { logger.info({ taskId, metadataKeys: Object.keys(metadata), updatedBy }, 'Updating task metadata'); const storageManager = await getStorageManager(); const existingResult = await storageManager.getTask(taskId); if (!existingResult.success) { return { success: false, error: `Task not found: ${existingResult.error}`, metadata: existingResult.metadata }; } const existingTask = existingResult.data; const mergedMetadata = { ...existingTask.metadata, ...metadata, updatedAt: new Date(), updatedBy }; const updates = { metadata: mergedMetadata }; const updateResult = await storageManager.updateTask(taskId, updates); if (!updateResult.success) { return { success: false, error: `Failed to update task metadata: ${updateResult.error}`, metadata: updateResult.metadata }; } logger.info({ taskId }, 'Task metadata updated successfully'); return { success: true, data: updateResult.data, metadata: { filePath: 'task-operations', operation: 'update_task_metadata', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, taskId }, 'Failed to update task metadata'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'task-operations', operation: 'update_task_metadata', timestamp: new Date() } }; } } async getTaskStatus(taskId) { try { logger.debug({ taskId }, 'Getting task status'); const taskResult = await this.getTask(taskId); if (!taskResult.success) { return { success: false, error: taskResult.error, metadata: taskResult.metadata }; } return { success: true, data: taskResult.data.status, metadata: { filePath: 'task-operations', operation: 'get_task_status', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, taskId }, 'Failed to get task status'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'task-operations', operation: 'get_task_status', timestamp: new Date() } }; } } async getTaskMetadata(taskId) { try { logger.debug({ taskId }, 'Getting task metadata'); const taskResult = await this.getTask(taskId); if (!taskResult.success) { return { success: false, error: taskResult.error, metadata: taskResult.metadata }; } return { success: true, data: taskResult.data.metadata || {}, metadata: { filePath: 'task-operations', operation: 'get_task_metadata', timestamp: new Date() } }; } catch (error) { logger.error({ err: error, taskId }, 'Failed to get task metadata'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'task-operations', operation: 'get_task_metadata', timestamp: new Date() } }; } } async addTaskTags(taskId, tags, updatedBy = 'system') { try { logger.info({ taskId, tags, updatedBy }, 'Adding task tags'); const taskResult = await this.getTask(taskId); if (!taskResult.success) { return { success: false, error: taskResult.error, metadata: taskResult.metadata }; } const existingTask = taskResult.data; const existingTags = existingTask.tags || []; const mergedTags = [...new Set([...existingTags, ...tags])]; const updateParams = { tags: mergedTags }; return await this.updateTask(taskId, updateParams, updatedBy); } catch (error) { logger.error({ err: error, taskId, tags }, 'Failed to add task tags'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'task-operations', operation: 'add_task_tags', timestamp: new Date() } }; } } async removeTaskTags(taskId, tags, updatedBy = 'system') { try { logger.info({ taskId, tags, updatedBy }, 'Removing task tags'); const taskResult = await this.getTask(taskId); if (!taskResult.success) { return { success: false, error: taskResult.error, metadata: taskResult.metadata }; } const existingTask = taskResult.data; const existingTags = existingTask.tags || []; const filteredTags = existingTags.filter(tag => !tags.includes(tag)); const updateParams = { tags: filteredTags }; return await this.updateTask(taskId, updateParams, updatedBy); } catch (error) { logger.error({ err: error, taskId, tags }, 'Failed to remove task tags'); return { success: false, error: error instanceof Error ? error.message : String(error), metadata: { filePath: 'task-operations', operation: 'remove_task_tags', timestamp: new Date() } }; } } } export function getTaskOperations() { return TaskOperations.getInstance(); }