remove_subtask
Remove a subtask from its parent task or convert it to a standalone task using the provided subtask ID and project directory. Simplify task management in AI-driven development workflows.
Instructions
Remove a subtask from its parent task
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| convert | No | Convert the subtask to a standalone task instead of deleting it | |
| file | No | Absolute path to the tasks file (default: tasks/tasks.json) | |
| id | Yes | Subtask ID to remove in format 'parentId.subtaskId' (required) | |
| projectRoot | Yes | The directory of the project. Must be an absolute path. | |
| skipGenerate | No | Skip regenerating task files | |
| tag | No | Tag context to operate on |
Implementation Reference
- MCP tool execute handler for 'remove_subtask'. Resolves tag and tasks path, calls removeSubtaskDirect with prepared arguments, handles result and errors.execute: withNormalizedProjectRoot(async (args, { log, session }) => { try { const resolvedTag = resolveTag({ projectRoot: args.projectRoot, tag: args.tag }); log.info(`Removing subtask with args: ${JSON.stringify(args)}`); // Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) let tasksJsonPath; try { tasksJsonPath = findTasksPath( { projectRoot: args.projectRoot, file: args.file }, log ); } catch (error) { log.error(`Error finding tasks.json: ${error.message}`); return createErrorResponse( `Failed to find tasks.json: ${error.message}` ); } const result = await removeSubtaskDirect( { tasksJsonPath: tasksJsonPath, id: args.id, convert: args.convert, skipGenerate: args.skipGenerate, projectRoot: args.projectRoot, tag: resolvedTag }, log, { session } ); if (result.success) { log.info(`Subtask removed successfully: ${result.data.message}`); } else { log.error(`Failed to remove subtask: ${result.error.message}`); } return handleApiResult({ result, log: log, errorPrefix: 'Error removing subtask', projectRoot: args.projectRoot }); } catch (error) { log.error(`Error in removeSubtask tool: ${error.message}`); return createErrorResponse(error.message); } })
- Zod input schema defining parameters for the remove_subtask tool: id (required), convert, file, skipGenerate, projectRoot, tag.parameters: z.object({ id: z .string() .describe( "Subtask ID to remove in format 'parentId.subtaskId' (required)" ), convert: z .boolean() .optional() .describe( 'Convert the subtask to a standalone task instead of deleting it' ), file: z .string() .optional() .describe( 'Absolute path to the tasks file (default: tasks/tasks.json)' ), skipGenerate: z .boolean() .optional() .describe('Skip regenerating task files'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.'), tag: z.string().optional().describe('Tag context to operate on') }),
- mcp-server/src/tools/tool-registry.js:25-81 (registration)Tool registry entry that maps 'remove_subtask' to its registration function registerRemoveSubtaskTool.import { registerRemoveSubtaskTool } from './remove-subtask.js'; import { registerRemoveTaskTool } from './remove-task.js'; import { registerRenameTagTool } from './rename-tag.js'; import { registerResearchTool } from './research.js'; import { registerResponseLanguageTool } from './response-language.js'; import { registerRulesTool } from './rules.js'; import { registerScopeDownTool } from './scope-down.js'; import { registerScopeUpTool } from './scope-up.js'; import { registerUpdateSubtaskTool } from './update-subtask.js'; import { registerUpdateTaskTool } from './update-task.js'; import { registerUpdateTool } from './update.js'; import { registerUseTagTool } from './use-tag.js'; import { registerValidateDependenciesTool } from './validate-dependencies.js'; // Import TypeScript tools from apps/mcp import { registerAutopilotAbortTool, registerAutopilotCommitTool, registerAutopilotCompleteTool, registerAutopilotFinalizeTool, registerAutopilotNextTool, registerAutopilotResumeTool, registerAutopilotStartTool, registerAutopilotStatusTool, registerGenerateTool, registerGetTaskTool, registerGetTasksTool, registerSetTaskStatusTool } from '@tm/mcp'; /** * Comprehensive tool registry mapping tool names to their registration functions * Used for dynamic tool registration and validation */ export const toolRegistry = { initialize_project: registerInitializeProjectTool, models: registerModelsTool, rules: registerRulesTool, parse_prd: registerParsePRDTool, 'response-language': registerResponseLanguageTool, analyze_project_complexity: registerAnalyzeProjectComplexityTool, expand_task: registerExpandTaskTool, expand_all: registerExpandAllTool, scope_up_task: registerScopeUpTool, scope_down_task: registerScopeDownTool, get_tasks: registerGetTasksTool, get_task: registerGetTaskTool, next_task: registerNextTaskTool, complexity_report: registerComplexityReportTool, set_task_status: registerSetTaskStatusTool, add_task: registerAddTaskTool, add_subtask: registerAddSubtaskTool, update: registerUpdateTool, update_task: registerUpdateTaskTool, update_subtask: registerUpdateSubtaskTool, remove_task: registerRemoveTaskTool, remove_subtask: registerRemoveSubtaskTool,
- removeSubtaskDirect helper: validates inputs, enables silent mode, calls core removeSubtask, formats response.export async function removeSubtaskDirect(args, log) { // Destructure expected args const { tasksJsonPath, id, convert, skipGenerate, projectRoot, tag } = args; try { // Enable silent mode to prevent console logs from interfering with JSON response enableSilentMode(); log.info(`Removing subtask with args: ${JSON.stringify(args)}`); // Check if tasksJsonPath was provided if (!tasksJsonPath) { log.error('removeSubtaskDirect called without tasksJsonPath'); disableSilentMode(); // Disable before returning return { success: false, error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' } }; } if (!id) { disableSilentMode(); // Disable before returning return { success: false, error: { code: 'INPUT_VALIDATION_ERROR', message: 'Subtask ID is required and must be in format "parentId.subtaskId"' } }; } // Validate subtask ID format if (!id.includes('.')) { disableSilentMode(); // Disable before returning return { success: false, error: { code: 'INPUT_VALIDATION_ERROR', message: `Invalid subtask ID format: ${id}. Expected format: "parentId.subtaskId"` } }; } // Use provided path const tasksPath = tasksJsonPath; // Convert convertToTask to a boolean const convertToTask = convert === true; // Determine if we should generate files const generateFiles = !skipGenerate; log.info( `Removing subtask ${id} (convertToTask: ${convertToTask}, generateFiles: ${generateFiles})` ); // Use the provided tasksPath const result = await removeSubtask( tasksPath, id, convertToTask, generateFiles, { projectRoot, tag } ); // Restore normal logging disableSilentMode(); if (convertToTask && result) { // Return info about the converted task return { success: true, data: { message: `Subtask ${id} successfully converted to task #${result.id}`, task: result } }; } else { // Return simple success message for deletion return { success: true, data: { message: `Subtask ${id} successfully removed` } }; } } catch (error) { // Ensure silent mode is disabled even if an outer error occurs disableSilentMode(); log.error(`Error in removeSubtaskDirect: ${error.message}`); return { success: false, error: { code: 'CORE_FUNCTION_ERROR', message: error.message } }; } }
- Core implementation of removeSubtask: parses ID, finds parent and subtask in tasks.json, removes or converts to new task, writes back to file.async function removeSubtask( tasksPath, subtaskId, convertToTask = false, generateFiles = false, context = {} ) { const { projectRoot, tag } = context; try { log('info', `Removing subtask ${subtaskId}...`); // Read the existing tasks with proper context const data = readJSON(tasksPath, projectRoot, tag); if (!data || !data.tasks) { throw new Error(`Invalid or missing tasks file at ${tasksPath}`); } // Parse the subtask ID (format: "parentId.subtaskId") if (!subtaskId.includes('.')) { throw new Error( `Invalid subtask ID format: ${subtaskId}. Expected format: "parentId.subtaskId"` ); } const [parentIdStr, subtaskIdStr] = subtaskId.split('.'); const parentId = parseInt(parentIdStr, 10); const subtaskIdNum = parseInt(subtaskIdStr, 10); // Find the parent task const parentTask = data.tasks.find((t) => t.id === parentId); if (!parentTask) { throw new Error(`Parent task with ID ${parentId} not found`); } // Check if parent has subtasks if (!parentTask.subtasks || parentTask.subtasks.length === 0) { throw new Error(`Parent task ${parentId} has no subtasks`); } // Find the subtask to remove const subtaskIndex = parentTask.subtasks.findIndex( (st) => st.id === subtaskIdNum ); if (subtaskIndex === -1) { throw new Error(`Subtask ${subtaskId} not found`); } // Get a copy of the subtask before removing it const removedSubtask = { ...parentTask.subtasks[subtaskIndex] }; // Remove the subtask from the parent parentTask.subtasks.splice(subtaskIndex, 1); // If parent has no more subtasks, remove the subtasks array if (parentTask.subtasks.length === 0) { parentTask.subtasks = undefined; } let convertedTask = null; // Convert the subtask to a standalone task if requested if (convertToTask) { log('info', `Converting subtask ${subtaskId} to a standalone task...`); // Find the highest task ID to determine the next ID const highestId = Math.max(...data.tasks.map((t) => t.id)); const newTaskId = highestId + 1; // Create the new task from the subtask convertedTask = { id: newTaskId, title: removedSubtask.title, description: removedSubtask.description || '', details: removedSubtask.details || '', status: removedSubtask.status || 'pending', dependencies: removedSubtask.dependencies || [], priority: parentTask.priority || 'medium' // Inherit priority from parent }; // Add the parent task as a dependency if not already present if (!convertedTask.dependencies.includes(parentId)) { convertedTask.dependencies.push(parentId); } // Add the converted task to the tasks array data.tasks.push(convertedTask); log('info', `Created new task ${newTaskId} from subtask ${subtaskId}`); } else { log('info', `Subtask ${subtaskId} deleted`); } // Write the updated tasks back to the file with proper context writeJSON(tasksPath, data, projectRoot, tag); // Note: Task file generation is no longer supported and has been removed return convertedTask; } catch (error) { log('error', `Error removing subtask: ${error.message}`); throw error; } }