UNPKG

claudemaster

Version:

Task management MCP server optimized for Claude Code - no API keys required

398 lines (359 loc) 11.8 kB
/** * basic-task-operations.js * Basic task operations for Claudemaster - no AI dependencies */ import { z } from 'zod'; import TaskEngine from '../core/task-engine.js'; export function registerBasicTaskOperationsTool(server) { // List tasks server.addTool({ name: 'list_tasks', description: 'List all tasks with optional filtering by status or priority.', parameters: z.object({ projectRoot: z.string().describe('Absolute path to project root directory'), status: z.enum(['pending', 'in-progress', 'done', 'cancelled', 'deferred']).optional().describe('Filter by task status'), priority: z.enum(['high', 'medium', 'low']).optional().describe('Filter by priority'), includeSubtasks: z.boolean().default(false).describe('Include subtasks in the listing') }), execute: async (args) => { try { const { projectRoot, status, priority, includeSubtasks } = args; const taskEngine = new TaskEngine(projectRoot); const filters = {}; if (status) filters.status = status; if (priority) filters.priority = priority; const tasks = taskEngine.listTasks(filters); const stats = taskEngine.getProjectStats(); let processedTasks = tasks; if (!includeSubtasks) { processedTasks = tasks.map(task => { const { subtasks, ...taskWithoutSubtasks } = task; return { ...taskWithoutSubtasks, subtaskCount: subtasks ? subtasks.length : 0 }; }); } return { success: true, tasks: processedTasks, totalCount: tasks.length, projectStats: stats, filters: filters }; } catch (error) { return { success: false, error: error.message }; } } }); // Get specific task server.addTool({ name: 'get_task', description: 'Get detailed information about a specific task including subtasks.', parameters: z.object({ projectRoot: z.string().describe('Absolute path to project root directory'), taskId: z.number().describe('ID of the task to retrieve') }), execute: async (args) => { try { const { projectRoot, taskId } = args; const taskEngine = new TaskEngine(projectRoot); const task = taskEngine.getTask(taskId); // Add dependency information const allTasks = taskEngine.listTasks(); const dependencyInfo = task.dependencies ? task.dependencies.map(depId => { const depTask = allTasks.find(t => t.id === depId); return depTask ? { id: depId, title: depTask.title, status: depTask.status } : { id: depId, title: 'Unknown Task', status: 'missing' }; }) : []; return { success: true, task: { ...task, dependencyInfo } }; } catch (error) { return { success: false, error: error.message }; } } }); // Set task status server.addTool({ name: 'set_task_status', description: 'Update the status of a task or subtask.', parameters: z.object({ projectRoot: z.string().describe('Absolute path to project root directory'), taskId: z.number().describe('ID of the task to update'), status: z.enum(['pending', 'in-progress', 'done', 'cancelled', 'deferred']).describe('New status for the task'), subtaskId: z.string().optional().describe('ID of subtask to update (if updating a subtask)') }), execute: async (args) => { try { const { projectRoot, taskId, status, subtaskId } = args; const taskEngine = new TaskEngine(projectRoot); if (subtaskId) { // Update subtask status const task = taskEngine.getTask(taskId); if (!task.subtasks) { throw new Error(`Task ${taskId} has no subtasks`); } const subtaskIndex = task.subtasks.findIndex(st => st.id === subtaskId); if (subtaskIndex === -1) { throw new Error(`Subtask ${subtaskId} not found in task ${taskId}`); } task.subtasks[subtaskIndex].status = status; task.subtasks[subtaskIndex].updatedAt = new Date().toISOString(); const updatedTask = taskEngine.updateTask(taskId, { subtasks: task.subtasks }); return { success: true, message: `Subtask ${subtaskId} status updated to ${status}`, updatedTask }; } else { // Update main task status const updatedTask = taskEngine.setTaskStatus(taskId, status); // If marking as done, also mark all subtasks as done if (status === 'done' && updatedTask.subtasks && updatedTask.subtasks.length > 0) { updatedTask.subtasks = updatedTask.subtasks.map(st => ({ ...st, status: 'done', updatedAt: new Date().toISOString() })); taskEngine.updateTask(taskId, { subtasks: updatedTask.subtasks }); } return { success: true, message: `Task ${taskId} status updated to ${status}`, updatedTask }; } } catch (error) { return { success: false, error: error.message }; } } }); // Add new task server.addTool({ name: 'add_task', description: 'Add a new task to the project.', parameters: z.object({ projectRoot: z.string().describe('Absolute path to project root directory'), title: z.string().describe('Title of the new task'), description: z.string().describe('Description of the task'), priority: z.enum(['high', 'medium', 'low']).default('medium').describe('Priority level'), dependencies: z.array(z.number()).default([]).describe('Array of task IDs this task depends on'), details: z.string().default('').describe('Detailed implementation notes'), testStrategy: z.string().default('').describe('Testing strategy for the task') }), execute: async (args) => { try { const { projectRoot, ...taskData } = args; const taskEngine = new TaskEngine(projectRoot); const newTask = taskEngine.addTask(taskData); const stats = taskEngine.getProjectStats(); return { success: true, message: `Task "${newTask.title}" added successfully`, task: newTask, projectStats: stats }; } catch (error) { return { success: false, error: error.message }; } } }); // Update task server.addTool({ name: 'update_task', description: 'Update an existing task with new information.', parameters: z.object({ projectRoot: z.string().describe('Absolute path to project root directory'), taskId: z.number().describe('ID of the task to update'), title: z.string().optional().describe('New title'), description: z.string().optional().describe('New description'), priority: z.enum(['high', 'medium', 'low']).optional().describe('New priority'), dependencies: z.array(z.number()).optional().describe('New dependencies array'), details: z.string().optional().describe('New implementation details'), testStrategy: z.string().optional().describe('New testing strategy') }), execute: async (args) => { try { const { projectRoot, taskId, ...updates } = args; const taskEngine = new TaskEngine(projectRoot); // Remove undefined values const cleanUpdates = Object.fromEntries( Object.entries(updates).filter(([_, value]) => value !== undefined) ); const updatedTask = taskEngine.updateTask(taskId, cleanUpdates); return { success: true, message: `Task ${taskId} updated successfully`, updatedTask }; } catch (error) { return { success: false, error: error.message }; } } }); // Remove task server.addTool({ name: 'remove_task', description: 'Remove a task from the project.', parameters: z.object({ projectRoot: z.string().describe('Absolute path to project root directory'), taskId: z.number().describe('ID of the task to remove') }), execute: async (args) => { try { const { projectRoot, taskId } = args; const taskEngine = new TaskEngine(projectRoot); // Check if task exists before removal const task = taskEngine.getTask(taskId); taskEngine.removeTask(taskId); const stats = taskEngine.getProjectStats(); return { success: true, message: `Task "${task.title}" (ID: ${taskId}) removed successfully`, removedTask: { id: task.id, title: task.title }, projectStats: stats }; } catch (error) { return { success: false, error: error.message }; } } }); // Validate dependencies server.addTool({ name: 'validate_dependencies', description: 'Validate task dependencies and identify issues.', parameters: z.object({ projectRoot: z.string().describe('Absolute path to project root directory') }), execute: async (args) => { try { const { projectRoot } = args; const taskEngine = new TaskEngine(projectRoot); const issues = taskEngine.validateDependencies(); const stats = taskEngine.getProjectStats(); return { success: true, isValid: issues.length === 0, issues, issueCount: issues.length, projectStats: stats, recommendations: issues.length > 0 ? [ 'Fix missing dependencies before proceeding', 'Use update_task to correct dependency references', 'Consider removing invalid dependencies' ] : [ 'All dependencies are valid', 'Project structure is consistent' ] }; } catch (error) { return { success: false, error: error.message }; } } }); // Add subtask server.addTool({ name: 'add_subtask', description: 'Add a subtask to an existing task.', parameters: z.object({ projectRoot: z.string().describe('Absolute path to project root directory'), taskId: z.number().describe('ID of the parent task'), title: z.string().describe('Title of the subtask'), description: z.string().describe('Description of the subtask'), priority: z.enum(['high', 'medium', 'low']).default('medium').describe('Priority level'), estimatedHours: z.number().optional().describe('Estimated hours to complete') }), execute: async (args) => { try { const { projectRoot, taskId, ...subtaskData } = args; const taskEngine = new TaskEngine(projectRoot); const updatedTask = taskEngine.addSubtask(taskId, subtaskData); return { success: true, message: `Subtask "${subtaskData.title}" added to task ${taskId}`, parentTask: { id: updatedTask.id, title: updatedTask.title, subtaskCount: updatedTask.subtasks.length }, addedSubtask: updatedTask.subtasks[updatedTask.subtasks.length - 1] }; } catch (error) { return { success: false, error: error.message }; } } }); // Remove subtask server.addTool({ name: 'remove_subtask', description: 'Remove a subtask from a task.', parameters: z.object({ projectRoot: z.string().describe('Absolute path to project root directory'), taskId: z.number().describe('ID of the parent task'), subtaskId: z.string().describe('ID of the subtask to remove') }), execute: async (args) => { try { const { projectRoot, taskId, subtaskId } = args; const taskEngine = new TaskEngine(projectRoot); // Get subtask info before removal const task = taskEngine.getTask(taskId); const subtask = task.subtasks ? task.subtasks.find(st => st.id === subtaskId) : null; const updatedTask = taskEngine.removeSubtask(taskId, subtaskId); return { success: true, message: `Subtask "${subtask ? subtask.title : subtaskId}" removed from task ${taskId}`, parentTask: { id: updatedTask.id, title: updatedTask.title, subtaskCount: updatedTask.subtasks ? updatedTask.subtasks.length : 0 } }; } catch (error) { return { success: false, error: error.message }; } } }); }