UNPKG

task-master-neo-sdlc

Version:

Enhanced task management system with Neo SDLC agents and MCP tools for comprehensive, AI-driven software development lifecycle management.

1,040 lines (902 loc) 33.8 kB
import { readJSON, writeJSON } from '../../utils/file-utils.js'; import { log } from '../../utils/logging.js'; import { createQAAgent } from './agents/qa-agent.js'; import { createTDDFramework } from './tdd-framework.js'; // Environment variables with defaults const TASKMASTER_MCP_URL = process.env.TASKMASTER_MCP_URL || 'http://localhost:3001/mcp/execute'; const TASKMASTER_PROJECT_ROOT = process.env.TASKMASTER_PROJECT_ROOT || process.cwd(); export class AgentWorkflowSystem { constructor(knowledgeGraph) { this.knowledgeGraph = knowledgeGraph; this.agents = new Map(); this.workflows = new Map(); this.coordinationPatterns = { sequential: this.validateSequentialTransition.bind(this), parallel: this.validateParallelTransition.bind(this) }; // TODO: Determine the definitive source for projectRoot within the Neo context // Using TASKMASTER_PROJECT_ROOT env var or fallback to cwd for now this.projectRoot = process.env.TASKMASTER_PROJECT_ROOT || process.cwd(); // Initialize TDD framework this.tddFramework = createTDDFramework(this); log.info(`AgentWorkflowSystem initialized. Project Root: ${this.projectRoot}, MCP URL: ${TASKMASTER_MCP_URL}`); } async registerAgent(agentData) { const { id, capabilities, knowledgeDomains } = agentData; if (!id || !capabilities) { throw new Error('Agent must have id and capabilities'); } const agent = { ...agentData, status: 'available', metrics: { tasksCompleted: 0, successRate: 1.0, avgCompletionTime: 0, qualityScore: 1.0 }, currentTask: null, history: [] }; await this.knowledgeGraph.addNode({ id, type: 'agent', data: { capabilities, knowledgeDomains: knowledgeDomains || [], status: agent.status, metrics: agent.metrics } }); this.agents.set(id, agent); return agent; } async createWorkflow(workflowData) { const { id, steps, coordinationPattern = 'sequential', priority = 'medium' } = workflowData; if (!id || !steps || !Array.isArray(steps)) { throw new Error('Workflow must have id and steps array'); } const workflow = { ...workflowData, status: 'pending', currentStep: 0, assignedAgents: new Map(), metrics: { startTime: null, completionTime: null, totalTime: 0, qualityScore: 0 }, history: [] }; await this.knowledgeGraph.addNode({ id, type: 'workflow', data: { steps: steps.map(s => ({ ...s, status: 'pending' })), status: workflow.status, assignedAgents: [], coordinationPattern, priority } }); this.workflows.set(id, workflow); return workflow; } async assignStep(workflowId, stepIndex, agentId) { const workflow = this.workflows.get(workflowId); const agent = this.agents.get(agentId); if (!workflow || !agent) { throw new Error('Invalid workflow or agent ID'); } const step = workflow.steps[stepIndex]; if (!step) { throw new Error('Invalid step index'); } // Validate transition based on coordination pattern const validator = this.coordinationPatterns[workflow.coordinationPattern]; if (!validator || !validator(workflow, stepIndex)) { throw new Error('Invalid step transition for coordination pattern'); } // Check agent capabilities if (!this.validateAgentCapabilities(agent, step)) { throw new Error('Agent lacks required capabilities for step'); } // Update workflow workflow.assignedAgents.set(stepIndex, agentId); step.status = 'assigned'; step.assignedTo = agentId; step.assignedAt = Date.now(); // Update agent agent.status = 'busy'; agent.currentTask = { workflowId, stepIndex }; // Update knowledge graph await this.knowledgeGraph.addEdge(workflowId, agentId, 'assigned_to'); await this.updateWorkflowStatus(workflowId); return { workflow, agent }; } validateSequentialTransition(workflow, stepIndex) { if (stepIndex === 0) return true; const prevStep = workflow.steps[stepIndex - 1]; return prevStep && prevStep.status === 'completed'; } validateParallelTransition(workflow, stepIndex) { return workflow.steps[stepIndex].status === 'pending'; } validateAgentCapabilities(agent, step) { return step.requiredCapabilities.every(cap => agent.capabilities.includes(cap) ); } async completeStep(workflowId, stepIndex, result) { const workflow = this.workflows.get(workflowId); if (!workflow) { throw new Error('Invalid workflow ID'); } const step = workflow.steps[stepIndex]; if (!step) { throw new Error('Invalid step index'); } const agentId = workflow.assignedAgents.get(stepIndex); const agent = this.agents.get(agentId); if (!agent) { throw new Error('Agent not found'); } // Validate result const quality = await this.validateStepResult(step, result); // Update step step.status = 'completed'; step.completedAt = Date.now(); step.result = result; step.quality = quality; // Update agent metrics agent.status = 'available'; agent.currentTask = null; agent.metrics.tasksCompleted++; agent.metrics.avgCompletionTime = ( (agent.metrics.avgCompletionTime * (agent.metrics.tasksCompleted - 1) + (step.completedAt - step.assignedAt)) / agent.metrics.tasksCompleted ); agent.metrics.qualityScore = ( (agent.metrics.qualityScore * (agent.metrics.tasksCompleted - 1) + quality) / agent.metrics.tasksCompleted ); // Add to history agent.history.push({ workflowId, stepIndex, completedAt: step.completedAt, quality }); // Update workflow status await this.updateWorkflowStatus(workflowId); // Update knowledge graph await this.knowledgeGraph.updateContext({ id: agentId, type: 'agent', metrics: agent.metrics, status: agent.status }); return { workflow, agent }; } async handleStepFailure(workflowId, stepIndex, error) { const workflow = this.workflows.get(workflowId); if (!workflow) { throw new Error('Invalid workflow ID'); } const step = workflow.steps[stepIndex]; if (!step) { throw new Error('Invalid step index'); } const agentId = workflow.assignedAgents.get(stepIndex); const agent = this.agents.get(agentId); if (!agent) { throw new Error('Agent not found'); } // Update step step.status = 'failed'; step.error = error; step.failedAt = Date.now(); // Update agent metrics agent.status = 'available'; agent.currentTask = null; agent.metrics.successRate = ( (agent.metrics.tasksCompleted * agent.metrics.successRate) / (agent.metrics.tasksCompleted + 1) ); // Add to history agent.history.push({ workflowId, stepIndex, failedAt: step.failedAt, error }); // Update workflow status await this.updateWorkflowStatus(workflowId); // Update knowledge graph await this.knowledgeGraph.updateContext({ id: agentId, type: 'agent', metrics: agent.metrics, status: agent.status }); return { workflow, agent }; } async validateStepResult(step, result) { // Basic validation - can be extended based on step type if (!result || typeof result !== 'object') { return 0.5; // Mediocre quality for invalid format } // Check required fields const hasRequired = step.requiredResults.every(field => result.hasOwnProperty(field) ); if (!hasRequired) { return 0.6; // Below average for missing fields } // Check quality metrics if defined if (step.qualityMetrics) { const scores = await Promise.all( step.qualityMetrics.map(metric => this.evaluateQualityMetric(result, metric)) ); return scores.reduce((a, b) => a + b, 0) / scores.length; } return 0.8; // Good default quality } async evaluateQualityMetric(result, metric) { // Implement specific quality metric evaluations switch (metric.type) { case 'completeness': return Object.keys(result).length / metric.expectedFields; case 'accuracy': return metric.validateFn(result); default: return 0.7; // Default quality score } } async updateWorkflowStatus(workflowId) { const workflow = this.workflows.get(workflowId); if (!workflow) { throw new Error('Invalid workflow ID'); } const allCompleted = workflow.steps.every(s => s.status === 'completed'); const anyFailed = workflow.steps.some(s => s.status === 'failed'); const anyBlocked = workflow.steps.some(s => s.status === 'blocked'); if (allCompleted) { workflow.status = 'completed'; workflow.metrics.completionTime = Date.now(); workflow.metrics.totalTime = workflow.metrics.completionTime - workflow.metrics.startTime; workflow.metrics.qualityScore = workflow.steps.reduce( (sum, step) => sum + (step.quality || 0), 0 ) / workflow.steps.length; } else if (anyFailed) { workflow.status = 'failed'; } else if (anyBlocked) { workflow.status = 'blocked'; } else { workflow.status = 'in_progress'; } // Update knowledge graph await this.knowledgeGraph.updateContext({ id: workflowId, type: 'workflow', status: workflow.status, metrics: workflow.metrics }); return workflow; } async getWorkflowStatus(workflowId) { const workflow = this.workflows.get(workflowId); if (!workflow) { throw new Error('Invalid workflow ID'); } const context = await this.knowledgeGraph.getContext(workflowId); const analysis = await this.knowledgeGraph.analyzeContext(workflowId); return { ...workflow, context, analysis }; } async getAgentStatus(agentId) { const agent = this.agents.get(agentId); if (!agent) { throw new Error('Invalid agent ID'); } const context = await this.knowledgeGraph.getContext(agentId); const analysis = await this.knowledgeGraph.analyzeContext(agentId); return { ...agent, context, analysis, recentHistory: agent.history.slice(-5) }; } /** * Create a workflow from Task Master tasks * @param {Object} options - Options for creating the workflow * @param {string} options.id - Workflow ID * @param {Array<string>} options.taskMasterIds - Array of Task Master task IDs to include * @param {string} options.coordinationPattern - Coordination pattern (sequential or parallel) * @param {string} options.priority - Workflow priority * @returns {Promise<Object>} Created workflow */ async createWorkflowFromTaskMaster(options) { const { id, taskMasterIds, coordinationPattern = 'sequential', priority = 'medium' } = options; if (!id || !taskMasterIds || !Array.isArray(taskMasterIds) || taskMasterIds.length === 0) { throw new Error('Workflow must have id and at least one Task Master task ID'); } // Fetch tasks from Task Master const allTasks = await this.fetchTaskMasterTasks(null, true); // Get all tasks with subtasks // Create workflow steps from Task Master tasks const steps = []; for (const taskMasterId of taskMasterIds) { // Handle both main tasks and subtasks if (taskMasterId.includes('.')) { // This is a subtask ID (e.g., "5.2") const [parentId, subtaskId] = taskMasterId.split('.'); const parentTask = allTasks.find(t => t.id === parseInt(parentId)); if (parentTask && parentTask.subtasks) { const subtask = parentTask.subtasks.find(s => s.id === parseInt(subtaskId)); if (subtask) { // Create step from subtask steps.push(this.createStepFromTaskMasterSubtask(subtask, parentTask, taskMasterId)); } } } else { // This is a main task ID const task = allTasks.find(t => t.id === parseInt(taskMasterId)); if (task) { if (task.subtasks && task.subtasks.length > 0) { // Create steps from all subtasks for (const subtask of task.subtasks) { steps.push(this.createStepFromTaskMasterSubtask(subtask, task, `${task.id}.${subtask.id}`)); } } else { // Create step from main task steps.push(this.createStepFromTaskMasterTask(task, taskMasterId)); } } } } // Create the workflow return this.createWorkflow({ id, steps, coordinationPattern, priority }); } /** * Create a workflow step from a Task Master subtask * @param {Object} subtask - Task Master subtask * @param {Object} parentTask - Parent task * @param {string} taskMasterId - Task Master ID (e.g., "5.2") * @returns {Object} Workflow step */ createStepFromTaskMasterSubtask(subtask, parentTask, taskMasterId) { // Determine required capabilities based on subtask action const requiredCapabilities = this.getCapabilitiesForAction(subtask.action); // Create the step return { id: `step-${taskMasterId.replace('.', '-')}`, title: subtask.title, description: subtask.description, details: subtask.details, taskMasterId, requiredCapabilities, action: subtask.action, requiredResults: this.getRequiredResultsForAction(subtask.action) }; } /** * Create a workflow step from a Task Master main task * @param {Object} task - Task Master task * @param {string} taskMasterId - Task Master ID * @returns {Object} Workflow step */ createStepFromTaskMasterTask(task, taskMasterId) { // For main tasks, use a generic implementation action const action = { type: 'implementBusinessLogic', parameters: { taskTitle: task.title, taskDescription: task.description } }; // Create the step return { id: `step-${taskMasterId}`, title: task.title, description: task.description, details: task.details, taskMasterId, requiredCapabilities: ['coding', 'problem-solving'], action, requiredResults: ['implementationPath', 'implementationCode'] }; } /** * Get required capabilities for an action * @param {Object} action - Action object * @returns {Array<string>} Required capabilities */ getCapabilitiesForAction(action) { if (!action || !action.type) { return ['coding', 'problem-solving']; } // Map action types to required capabilities const capabilityMap = { 'createComponent': ['react', 'component-design', 'frontend'], 'implementApiLogic': ['api-design', 'backend', 'data-handling'], 'createTest': ['testing', 'quality-assurance'], 'setupDatabase': ['database', 'data-modeling'], 'configureAuthentication': ['security', 'authentication'], 'implementBusinessLogic': ['coding', 'problem-solving', 'algorithm-design'], 'createUtility': ['coding', 'software-design'] }; return capabilityMap[action.type] || ['coding', 'problem-solving']; } /** * Get required results for an action * @param {Object} action - Action object * @returns {Array<string>} Required results */ getRequiredResultsForAction(action) { if (!action || !action.type) { return ['implementationPath', 'implementationCode']; } // Map action types to required results const resultsMap = { 'createComponent': ['componentPath', 'componentCode'], 'implementApiLogic': ['apiPath', 'apiCode', 'apiDocumentation'], 'createTest': ['testPath', 'testCode', 'testResults'], 'setupDatabase': ['schemaPath', 'schemaCode', 'migrationPath'], 'configureAuthentication': ['authConfigPath', 'authImplementation'], 'implementBusinessLogic': ['implementationPath', 'implementationCode'], 'createUtility': ['utilityPath', 'utilityCode', 'utilityDocumentation'] }; return resultsMap[action.type] || ['implementationPath', 'implementationCode']; } /** * Check if a task needs expansion * @param {string} taskMasterId - Task Master ID * @returns {Promise<Object>} Result indicating if expansion is needed */ async checkTaskNeedsExpansion(taskMasterId) { // If this is a subtask ID, no expansion needed if (taskMasterId.includes('.')) { return { needsExpansion: false }; } // Fetch the task const task = await this.fetchTaskMasterTaskById(taskMasterId); if (!task) { throw new Error(`Task with ID ${taskMasterId} not found`); } // Check if task already has subtasks if (task.subtasks && task.subtasks.length > 0) { return { needsExpansion: false, hasSubtasks: true, subtaskCount: task.subtasks.length }; } // Check task complexity const complexityReport = await this.analyzeTaskComplexity([task]); const taskComplexity = complexityReport.find(t => t.taskId === parseInt(taskMasterId)); if (taskComplexity && taskComplexity.complexityScore >= 7) { return { needsExpansion: true, complexityScore: taskComplexity.complexityScore, recommendedSubtasks: taskComplexity.recommendedSubtasks, expansionPrompt: taskComplexity.expansionPrompt }; } return { needsExpansion: false, complexityScore: taskComplexity?.complexityScore }; } /** * Fetch a specific Task Master task by ID * @param {string} taskId - Task ID * @returns {Promise<Object>} Task object */ async fetchTaskMasterTaskById(taskId) { const tasks = await this.fetchTaskMasterTasks(null, true); if (!tasks) return null; if (taskId.includes('.')) { // This is a subtask ID (e.g., "5.2") const [parentId, subtaskId] = taskId.split('.'); const parentTask = tasks.find(t => t.id === parseInt(parentId)); if (parentTask && parentTask.subtasks) { const subtask = parentTask.subtasks.find(s => s.id === parseInt(subtaskId)); if (subtask) { return { ...subtask, parentTask }; } } } else { // This is a main task ID return tasks.find(t => t.id === parseInt(taskId)); } return null; } /** * Analyze task complexity * @param {Array<Object>} tasks - Tasks to analyze * @returns {Promise<Array>} Complexity analysis results */ async analyzeTaskComplexity(tasks) { log.info(`Analyzing complexity for ${tasks.length} tasks...`); const toolName = 'analyze_complexity'; const requestUrl = `${TASKMASTER_MCP_URL}/${toolName}`; const payload = { parameters: { projectRoot: this.projectRoot, tasks: JSON.stringify(tasks), threshold: '7' // Default threshold for high complexity } }; try { const response = await fetch(requestUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }); if (!response.ok) { let errorData; try { errorData = await response.json(); } catch (parseError) { errorData = await response.text(); } log.error(`HTTP error analyzing task complexity: ${response.status} ${response.statusText}`); log.error('Error details:', errorData); throw new Error(`HTTP error ${response.status}`); } const responseData = await response.json(); if (responseData && responseData.success) { log.info('Successfully analyzed task complexity'); return responseData.result.complexityAnalysis || []; } else { log.error('MCP Server indicated failure during complexity analysis:', responseData ? responseData.error : 'Unknown error structure'); return []; } } catch (error) { log.error(`Error calling Task Master MCP server (${toolName}):`, error.message); return []; } } /** * Execute a workflow step using the appropriate agent based on the action type * @param {string} workflowId - Workflow ID * @param {number} stepIndex - Step index * @param {string} agentId - Agent ID * @returns {Promise<Object>} Execution result */ async executeWorkflowStep(workflowId, stepIndex, agentId) { const workflow = this.workflows.get(workflowId); const agent = this.agents.get(agentId); if (!workflow || !agent) { throw new Error('Invalid workflow or agent ID'); } const step = workflow.steps[stepIndex]; if (!step) { throw new Error('Invalid step index'); } // Get the action from the step const action = step.action; if (!action || !action.type) { throw new Error('Step has no valid action'); } // Execute the appropriate action based on the type let result; try { switch (action.type) { case 'createComponent': result = await this.executeCreateComponent(agent, step, action.parameters); break; case 'implementApiLogic': result = await this.executeImplementApiLogic(agent, step, action.parameters); break; case 'createTest': result = await this.executeCreateTest(agent, step, action.parameters); break; case 'setupDatabase': result = await this.executeSetupDatabase(agent, step, action.parameters); break; case 'configureAuthentication': result = await this.executeConfigureAuthentication(agent, step, action.parameters); break; case 'implementBusinessLogic': result = await this.executeImplementBusinessLogic(agent, step, action.parameters); break; case 'createUtility': result = await this.executeCreateUtility(agent, step, action.parameters); break; default: throw new Error(`Unsupported action type: ${action.type}`); } // Complete the step with the result await this.completeStep(workflowId, stepIndex, result); // Update the Task Master task status if taskMasterId is provided if (step.taskMasterId) { await this.setTaskMasterTaskStatus(step.taskMasterId, 'completed'); } return result; } catch (error) { // Handle step failure await this.handleStepFailure(workflowId, stepIndex, error); // Update the Task Master task status if taskMasterId is provided if (step.taskMasterId) { await this.setTaskMasterTaskStatus(step.taskMasterId, 'failed'); } throw error; } } // Placeholder methods for executing different action types // These would be implemented with actual agent execution logic async executeCreateComponent(agent, step, parameters) { log.info(`Agent ${agent.id} executing createComponent action for step ${step.id}`); // Implementation would call the agent's createComponent method return { componentPath: `src/components/${parameters.componentName}.js`, componentCode: '// Generated component code' }; } async executeImplementApiLogic(agent, step, parameters) { log.info(`Agent ${agent.id} executing implementApiLogic action for step ${step.id}`); // Implementation would call the agent's implementApiLogic method return { apiPath: `src/api/${parameters.endpointPath}.js`, apiCode: '// Generated API code', apiDocumentation: '# API Documentation' }; } async executeCreateTest(agent, step, parameters) { log.info(`Agent ${agent.id} executing createTest action for step ${step.id}`); // Check if the agent is a QA agent if (agent.role === 'qa') { // Agent is already a QA agent, use its createTest method return await agent.createTest(parameters, { taskId: step.taskMasterId, workflowId: step.id, testFramework: parameters.testFramework || 'jest', runTests: parameters.runTests || false }); } else { // Create a temporary QA agent log.info(`Creating temporary QA agent for step ${step.id}`); const qaAgent = createQAAgent(`qa-${agent.id}`, { capabilities: agent.capabilities, knowledgeDomains: agent.knowledgeDomains }); // Use the QA agent to create tests return await qaAgent.createTest(parameters, { taskId: step.taskMasterId, workflowId: step.id, testFramework: parameters.testFramework || 'jest', runTests: parameters.runTests || false }); } } async executeSetupDatabase(agent, step, parameters) { log.info(`Agent ${agent.id} executing setupDatabase action for step ${step.id}`); // Implementation would call the agent's setupDatabase method return { schemaPath: 'src/db/schema.js', schemaCode: '// Generated schema code', migrationPath: 'src/db/migrations/' }; } async executeConfigureAuthentication(agent, step, parameters) { log.info(`Agent ${agent.id} executing configureAuthentication action for step ${step.id}`); // Implementation would call the agent's configureAuthentication method return { authConfigPath: 'src/auth/config.js', authImplementation: '// Generated auth implementation' }; } async executeImplementBusinessLogic(agent, step, parameters) { log.info(`Agent ${agent.id} executing implementBusinessLogic action for step ${step.id}`); // Implementation would call the agent's implementBusinessLogic method return { implementationPath: 'src/services/businessLogic.js', implementationCode: '// Generated business logic code' }; } async executeCreateUtility(agent, step, parameters) { log.info(`Agent ${agent.id} executing createUtility action for step ${step.id}`); // Implementation would call the agent's createUtility method return { utilityPath: 'src/utils/utility.js', utilityCode: '// Generated utility code', utilityDocumentation: '# Utility Documentation' }; } async fetchTaskMasterTasks(statusFilter = null, withSubtasks = false) { log.info(`Fetching Task Master tasks using fetch... Filter: ${statusFilter}, Subtasks: ${withSubtasks}`); const toolName = 'get_tasks'; const requestUrl = `${TASKMASTER_MCP_URL}/${toolName}`; const payload = { parameters: { projectRoot: this.projectRoot, status: statusFilter, withSubtasks: withSubtasks } // context: { session: { ... } } // Add context if needed }; try { // Use fetch instead of axios const response = await fetch(requestUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', // TODO: Add authentication headers if required }, body: JSON.stringify(payload) }); if (!response.ok) { // Handle non-2xx responses let errorData; try { errorData = await response.json(); } catch (parseError) { errorData = await response.text(); // Fallback to text if JSON parsing fails } log.error(`HTTP error fetching tasks: ${response.status} ${response.statusText}`); log.error('Error details:', errorData); throw new Error(`HTTP error ${response.status}`); } const responseData = await response.json(); if (responseData && responseData.success) { log.info('Successfully fetched tasks from Task Master via fetch:'); // Optional: console.log(JSON.stringify(responseData.result, null, 2)); return responseData.result; // Return the actual task data } else { log.error('MCP Server indicated failure:', responseData ? responseData.error : 'Unknown error structure'); return null; } } catch (error) { // Catch network errors or errors thrown from response handling log.error(`Error calling Task Master MCP server (${toolName}) with fetch:`, error.message); return null; } } async expandTaskMasterTask(taskId, options = {}) { const { num, research, prompt, force } = options; log.info(`Expanding Task Master task ${taskId} using fetch...`); const toolName = 'expand_task'; const requestUrl = `${TASKMASTER_MCP_URL}/${toolName}`; const payload = { parameters: { projectRoot: this.projectRoot, id: taskId, // Optional parameters for expand_task ...(num && { num: String(num) }), // Ensure num is string if provided ...(research && { research }), ...(prompt && { prompt }), ...(force && { force }) } // context: { session: { ... } } // Add context if needed }; try { const response = await fetch(requestUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', // TODO: Add authentication headers if required }, body: JSON.stringify(payload) }); if (!response.ok) { let errorData; try { errorData = await response.json(); } catch (parseError) { errorData = await response.text(); } log.error(`HTTP error expanding task ${taskId}: ${response.status} ${response.statusText}`); log.error('Error details:', errorData); throw new Error(`HTTP error ${response.status}`); } const responseData = await response.json(); if (responseData && responseData.success) { log.info(`Successfully requested expansion for task ${taskId}.`); // expand_task usually returns the updated task data including subtasks return responseData.result; } else { log.error(`MCP Server indicated failure during expansion of task ${taskId}:`, responseData ? responseData.error : 'Unknown error structure'); return null; } } catch (error) { log.error(`Error calling Task Master MCP server (${toolName}) for task ${taskId}:`, error.message); return null; } } async setTaskMasterTaskStatus(taskId, status) { // taskId can be a main task ID ('5') or a subtask ID ('5.2') log.info(`Setting Task Master task ${taskId} status to '${status}' using fetch...`); const toolName = 'set_task_status'; const requestUrl = `${TASKMASTER_MCP_URL}/${toolName}`; const payload = { parameters: { projectRoot: this.projectRoot, id: String(taskId), // Ensure ID is a string status: status } // context: { session: { ... } } // Add context if needed }; try { const response = await fetch(requestUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', // TODO: Add authentication headers if required }, body: JSON.stringify(payload) }); if (!response.ok) { let errorData; try { errorData = await response.json(); } catch (parseError) { errorData = await response.text(); } log.error(`HTTP error setting status for task ${taskId}: ${response.status} ${response.statusText}`); log.error('Error details:', errorData); throw new Error(`HTTP error ${response.status}`); } const responseData = await response.json(); if (responseData && responseData.success) { log.info(`Successfully set status for task ${taskId} to ${status}.`); return responseData.result; // Usually confirms the update } else { log.error(`MCP Server indicated failure setting status for task ${taskId}:`, responseData ? responseData.error : 'Unknown error structure'); return null; } } catch (error) { log.error(`Error calling Task Master MCP server (${toolName}) for task ${taskId}:`, error.message); return null; } } async fetchTaskMasterTaskById(taskId) { log.info(`Fetching details for Task Master task ${taskId} using fetch...`); const toolName = 'get_task'; // Assumes this MCP tool exists const requestUrl = `${TASKMASTER_MCP_URL}/${toolName}`; const payload = { parameters: { projectRoot: this.projectRoot, id: String(taskId) } // context: { session: { ... } } // Add context if needed }; try { const response = await fetch(requestUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', // TODO: Add authentication headers if required }, body: JSON.stringify(payload) }); if (!response.ok) { let errorData; try { errorData = await response.json(); } catch (parseError) { errorData = await response.text(); } log.error(`HTTP error fetching task ${taskId}: ${response.status} ${response.statusText}`); log.error('Error details:', errorData); throw new Error(`HTTP error ${response.status}`); } const responseData = await response.json(); if (responseData && responseData.success) { log.info(`Successfully fetched details for task ${taskId}.`); return responseData.result; // Return the task details } else { log.error(`MCP Server indicated failure fetching task ${taskId}:`, responseData ? responseData.error : 'Unknown error structure'); return null; } } catch (error) { log.error(`Error calling Task Master MCP server (${toolName}) for task ${taskId}:`, error.message); return null; } } }