feat: adds remove-task command + MCP implementation.

This commit is contained in:
Eyal Toledano
2025-04-03 00:35:11 -04:00
parent 6442bf5ee1
commit cdd87ccc5e
15 changed files with 1204 additions and 26 deletions

View File

@@ -13,6 +13,22 @@
- Significantly reduce response size by returning only the specific requested task instead of all tasks - Significantly reduce response size by returning only the specific requested task instead of all tasks
- Preserve dependency status relationships for the UI/CLI while keeping MCP responses lean and efficient - Preserve dependency status relationships for the UI/CLI while keeping MCP responses lean and efficient
- **Implement complete remove-task functionality:**
- Add `removeTask` core function to permanently delete tasks or subtasks from tasks.json
- Implement CLI command `remove-task` with confirmation prompt and force flag support
- Create MCP `remove_task` tool for AI-assisted task removal
- Automatically handle dependency cleanup by removing references to deleted tasks
- Update task files after removal to maintain consistency
- Provide robust error handling and detailed feedback messages
- **Update Cursor rules and documentation:**
- Enhance `new_features.mdc` with comprehensive guidelines for implementing removal commands
- Update `commands.mdc` with best practices for confirmation flows and cleanup procedures
- Expand `mcp.mdc` with detailed instructions for MCP tool implementation patterns
- Add examples of proper error handling and parameter validation to all relevant rules
- Include new sections about handling dependencies during task removal operations
- Document naming conventions and implementation patterns for destructive operations
- **Refactor project root handling for MCP Server:** - **Refactor project root handling for MCP Server:**
- **Prioritize Session Roots**: MCP tools now extract the project root path directly from `session.roots[0].uri` provided by the client (e.g., Cursor). - **Prioritize Session Roots**: MCP tools now extract the project root path directly from `session.roots[0].uri` provided by the client (e.g., Cursor).
- **New Utility `getProjectRootFromSession`**: Added to `mcp-server/src/tools/utils.js` to encapsulate session root extraction and decoding. **Further refined for more reliable detection, especially in integrated environments, including deriving root from script path and avoiding fallback to '/'.** - **New Utility `getProjectRootFromSession`**: Added to `mcp-server/src/tools/utils.js` to encapsulate session root extraction and decoding. **Further refined for more reliable detection, especially in integrated environments, including deriving root from script path and avoiding fallback to '/'.**

View File

@@ -37,6 +37,126 @@ While this document details the implementation of Task Master's **CLI commands**
- ✅ DO: Include validation for required parameters - ✅ DO: Include validation for required parameters
- ❌ DON'T: Implement business logic in command handlers - ❌ DON'T: Implement business logic in command handlers
## Best Practices for Removal/Delete Commands
When implementing commands that delete or remove data (like `remove-task` or `remove-subtask`), follow these specific guidelines:
- **Confirmation Prompts**:
- ✅ **DO**: Include a confirmation prompt by default for destructive operations
- ✅ **DO**: Provide a `--yes` or `-y` flag to skip confirmation for scripting/automation
- ✅ **DO**: Show what will be deleted in the confirmation message
- ❌ **DON'T**: Perform destructive operations without user confirmation unless explicitly overridden
```javascript
// ✅ DO: Include confirmation for destructive operations
programInstance
.command('remove-task')
.description('Remove a task or subtask permanently')
.option('-i, --id <id>', 'ID of the task to remove')
.option('-y, --yes', 'Skip confirmation prompt', false)
.action(async (options) => {
// Validation code...
if (!options.yes) {
const confirm = await inquirer.prompt([{
type: 'confirm',
name: 'proceed',
message: `Are you sure you want to permanently delete task ${taskId}? This cannot be undone.`,
default: false
}]);
if (!confirm.proceed) {
console.log(chalk.yellow('Operation cancelled.'));
return;
}
}
// Proceed with removal...
});
```
- **File Path Handling**:
- ✅ **DO**: Use `path.join()` to construct file paths
- ✅ **DO**: Follow established naming conventions for tasks (e.g., `task_001.txt`)
- ✅ **DO**: Check if files exist before attempting to delete them
- ✅ **DO**: Handle file deletion errors gracefully
- ❌ **DON'T**: Construct paths with string concatenation
```javascript
// ✅ DO: Properly construct file paths
const taskFilePath = path.join(
path.dirname(tasksPath),
`task_${taskId.toString().padStart(3, '0')}.txt`
);
// ✅ DO: Check existence before deletion
if (fs.existsSync(taskFilePath)) {
try {
fs.unlinkSync(taskFilePath);
console.log(chalk.green(`Task file deleted: ${taskFilePath}`));
} catch (error) {
console.warn(chalk.yellow(`Could not delete task file: ${error.message}`));
}
}
```
- **Clean Up References**:
- ✅ **DO**: Clean up references to the deleted item in other parts of the data
- ✅ **DO**: Handle both direct and indirect references
- ✅ **DO**: Explain what related data is being updated
- ❌ **DON'T**: Leave dangling references
```javascript
// ✅ DO: Clean up references when deleting items
console.log(chalk.blue('Cleaning up task dependencies...'));
let referencesRemoved = 0;
// Update dependencies in other tasks
data.tasks.forEach(task => {
if (task.dependencies && task.dependencies.includes(taskId)) {
task.dependencies = task.dependencies.filter(depId => depId !== taskId);
referencesRemoved++;
}
});
if (referencesRemoved > 0) {
console.log(chalk.green(`Removed ${referencesRemoved} references to task ${taskId} from other tasks`));
}
```
- **Task File Regeneration**:
- ✅ **DO**: Regenerate task files after destructive operations
- ✅ **DO**: Pass all required parameters to generation functions
- ✅ **DO**: Provide an option to skip regeneration if needed
- ❌ **DON'T**: Assume default parameters will work
```javascript
// ✅ DO: Properly regenerate files after deletion
if (!options.skipGenerate) {
console.log(chalk.blue('Regenerating task files...'));
try {
// Note both parameters are explicitly provided
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
console.log(chalk.green('Task files regenerated successfully'));
} catch (error) {
console.warn(chalk.yellow(`Warning: Could not regenerate task files: ${error.message}`));
}
}
```
- **Alternative Suggestions**:
- ✅ **DO**: Suggest non-destructive alternatives when appropriate
- ✅ **DO**: Explain the difference between deletion and status changes
- ✅ **DO**: Include examples of alternative commands
```javascript
// ✅ DO: Suggest alternatives for destructive operations
console.log(chalk.yellow('Note: If you just want to exclude this task from active work, consider:'));
console.log(chalk.cyan(` task-master set-status --id=${taskId} --status=cancelled`));
console.log(chalk.cyan(` task-master set-status --id=${taskId} --status=deferred`));
console.log('This preserves the task and its history for reference.');
```
## Option Naming Conventions ## Option Naming Conventions
- **Command Names**: - **Command Names**:

View File

@@ -15,6 +15,80 @@ The MCP server acts as a bridge between external tools (like Cursor) and the cor
- **Flow**: `External Tool (Cursor)` <-> `FastMCP Server` <-> `MCP Tools` (`mcp-server/src/tools/*.js`) <-> `Core Logic Wrappers` (`mcp-server/src/core/direct-functions/*.js`, exported via `task-master-core.js`) <-> `Core Modules` (`scripts/modules/*.js`) - **Flow**: `External Tool (Cursor)` <-> `FastMCP Server` <-> `MCP Tools` (`mcp-server/src/tools/*.js`) <-> `Core Logic Wrappers` (`mcp-server/src/core/direct-functions/*.js`, exported via `task-master-core.js`) <-> `Core Modules` (`scripts/modules/*.js`)
- **Goal**: Provide a performant and reliable way for external tools to interact with Task Master functionality without directly invoking the CLI for every operation. - **Goal**: Provide a performant and reliable way for external tools to interact with Task Master functionality without directly invoking the CLI for every operation.
## Direct Function Implementation Best Practices
When implementing a new direct function in `mcp-server/src/core/direct-functions/`, follow these critical guidelines:
1. **Verify Function Dependencies**:
- ✅ **DO**: Check that all helper functions your direct function needs are properly exported from their source modules
- ✅ **DO**: Import these dependencies explicitly at the top of your file
- ❌ **DON'T**: Assume helper functions like `findTaskById` or `taskExists` are automatically available
- **Example**:
```javascript
// At top of direct-function file
import { removeTask, findTaskById, taskExists } from '../../../../scripts/modules/task-manager.js';
```
2. **Parameter Verification and Completeness**:
- ✅ **DO**: Verify the signature of core functions you're calling and ensure all required parameters are provided
- ✅ **DO**: Pass explicit values for required parameters rather than relying on defaults
- ✅ **DO**: Double-check parameter order against function definition
- ❌ **DON'T**: Omit parameters assuming they have default values
- **Example**:
```javascript
// Correct parameter handling in direct function
async function generateTaskFilesDirect(args, log) {
const tasksPath = findTasksJsonPath(args, log);
const outputDir = args.output || path.dirname(tasksPath);
try {
// Pass all required parameters
const result = await generateTaskFiles(tasksPath, outputDir);
return { success: true, data: result, fromCache: false };
} catch (error) {
// Error handling...
}
}
```
3. **Consistent File Path Handling**:
- ✅ **DO**: Use `path.join()` instead of string concatenation for file paths
- ✅ **DO**: Follow established file naming conventions (`task_001.txt` not `1.md`)
- ✅ **DO**: Use `path.dirname()` and other path utilities for manipulating paths
- ✅ **DO**: When paths relate to task files, follow the standard format: `task_${id.toString().padStart(3, '0')}.txt`
- ❌ **DON'T**: Create custom file path handling logic that diverges from established patterns
- **Example**:
```javascript
// Correct file path handling
const taskFilePath = path.join(
path.dirname(tasksPath),
`task_${taskId.toString().padStart(3, '0')}.txt`
);
```
4. **Comprehensive Error Handling**:
- ✅ **DO**: Wrap core function calls in try/catch blocks
- ✅ **DO**: Log errors with appropriate severity and context
- ✅ **DO**: Return standardized error objects with code and message
- ✅ **DO**: Handle file system errors separately from function-specific errors
- **Example**:
```javascript
try {
// Core function call
} catch (error) {
log.error(`Failed to execute command: ${error.message}`);
return {
success: false,
error: {
code: error.code || 'DIRECT_FUNCTION_ERROR',
message: error.message,
details: error.stack
},
fromCache: false
};
}
```
## Tool Definition and Execution ## Tool Definition and Execution
### Tool Structure ### Tool Structure

View File

@@ -31,6 +31,72 @@ The standard pattern for adding a feature follows this workflow:
5. **Configuration**: Update any configuration in [`utils.js`](mdc:scripts/modules/utils.js) if needed, following [`utilities.mdc`](mdc:.cursor/rules/utilities.mdc). 5. **Configuration**: Update any configuration in [`utils.js`](mdc:scripts/modules/utils.js) if needed, following [`utilities.mdc`](mdc:.cursor/rules/utilities.mdc).
6. **Documentation**: Update help text and documentation in [dev_workflow.mdc](mdc:scripts/modules/dev_workflow.mdc) 6. **Documentation**: Update help text and documentation in [dev_workflow.mdc](mdc:scripts/modules/dev_workflow.mdc)
## Critical Checklist for New Features
- **Comprehensive Function Exports**:
- ✅ **DO**: Export all helper functions and utility methods needed by your new function
- ✅ **DO**: Review dependencies and ensure functions like `findTaskById`, `taskExists` are exported
- ❌ **DON'T**: Assume internal functions are already exported - always check and add them explicitly
- **Example**: If implementing a feature that checks task existence, ensure the helper function is in exports:
```javascript
// At the bottom of your module file:
export {
// ... existing exports ...
yourNewFunction,
taskExists, // Helper function used by yourNewFunction
findTaskById, // Helper function used by yourNewFunction
};
```
- **Parameter Completeness**:
- ✅ **DO**: Pass all required parameters to functions you call within your implementation
- ✅ **DO**: Check function signatures before implementing calls to them
- ❌ **DON'T**: Assume default parameter values will handle missing arguments
- **Example**: When calling file generation, pass both required parameters:
```javascript
// ✅ DO: Pass all required parameters
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
// ❌ DON'T: Omit required parameters
await generateTaskFiles(tasksPath); // Error - missing outputDir parameter
```
- **Consistent File Path Handling**:
- ✅ **DO**: Use consistent file naming conventions: `task_${id.toString().padStart(3, '0')}.txt`
- ✅ **DO**: Use `path.join()` for composing file paths
- ✅ **DO**: Use appropriate file extensions (.txt for tasks, .json for data)
- ❌ **DON'T**: Hardcode path separators or inconsistent file extensions
- **Example**: Creating file paths for tasks:
```javascript
// ✅ DO: Use consistent file naming and path.join
const taskFileName = path.join(
path.dirname(tasksPath),
`task_${taskId.toString().padStart(3, '0')}.txt`
);
// ❌ DON'T: Use inconsistent naming or string concatenation
const taskFileName = path.dirname(tasksPath) + '/' + taskId + '.md';
```
- **Error Handling and Reporting**:
- ✅ **DO**: Use structured error objects with code and message properties
- ✅ **DO**: Include clear error messages identifying the specific problem
- ✅ **DO**: Handle both function-specific errors and potential file system errors
- ✅ **DO**: Log errors at appropriate severity levels
- **Example**: Structured error handling in core functions:
```javascript
try {
// Implementation...
} catch (error) {
log('error', `Error removing task: ${error.message}`);
throw {
code: 'REMOVE_TASK_ERROR',
message: error.message,
details: error.stack
};
}
```
```javascript ```javascript
// 1. CORE LOGIC: Add function to appropriate module (example in task-manager.js) // 1. CORE LOGIC: Add function to appropriate module (example in task-manager.js)
/** /**

View File

@@ -153,11 +153,23 @@ This document provides a detailed reference for interacting with Taskmaster, cov
* `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`) * `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`)
* **Usage:** Mark progress as tasks move through the development cycle. * **Usage:** Mark progress as tasks move through the development cycle.
### 12. Remove Task (`remove_task`)
* **MCP Tool:** `remove_task`
* **CLI Command:** `task-master remove-task [options]`
* **Description:** `Permanently remove a task or subtask from the Taskmaster tasks list.`
* **Key Parameters/Options:**
* `id`: `Required. The ID of the Taskmaster task (e.g., '5') or subtask (e.g., '5.2') to permanently remove.` (CLI: `-i, --id <id>`)
* `yes`: `Skip the confirmation prompt and immediately delete the task.` (CLI: `-y, --yes`)
* `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`)
* **Usage:** Permanently delete tasks or subtasks that are no longer needed in the project.
* **Notes:** Use with caution as this operation cannot be undone. Consider using 'blocked', 'cancelled', or 'deferred' status instead if you just want to exclude a task from active planning but keep it for reference. The command automatically cleans up dependency references in other tasks.
--- ---
## Task Structure & Breakdown ## Task Structure & Breakdown
### 12. Expand Task (`expand_task`) ### 13. Expand Task (`expand_task`)
* **MCP Tool:** `expand_task` * **MCP Tool:** `expand_task`
* **CLI Command:** `task-master expand [options]` * **CLI Command:** `task-master expand [options]`
@@ -171,7 +183,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
* `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`) * `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`)
* **Usage:** Generate a detailed implementation plan for a complex task before starting coding. * **Usage:** Generate a detailed implementation plan for a complex task before starting coding.
### 13. Expand All Tasks (`expand_all`) ### 14. Expand All Tasks (`expand_all`)
* **MCP Tool:** `expand_all` * **MCP Tool:** `expand_all`
* **CLI Command:** `task-master expand --all [options]` (Note: CLI uses the `expand` command with the `--all` flag) * **CLI Command:** `task-master expand --all [options]` (Note: CLI uses the `expand` command with the `--all` flag)
@@ -184,7 +196,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
* `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`) * `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`)
* **Usage:** Useful after initial task generation or complexity analysis to break down multiple tasks at once. * **Usage:** Useful after initial task generation or complexity analysis to break down multiple tasks at once.
### 14. Clear Subtasks (`clear_subtasks`) ### 15. Clear Subtasks (`clear_subtasks`)
* **MCP Tool:** `clear_subtasks` * **MCP Tool:** `clear_subtasks`
* **CLI Command:** `task-master clear-subtasks [options]` * **CLI Command:** `task-master clear-subtasks [options]`
@@ -195,7 +207,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
* `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`) * `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`)
* **Usage:** Used before regenerating subtasks with `expand_task` if the previous breakdown needs replacement. * **Usage:** Used before regenerating subtasks with `expand_task` if the previous breakdown needs replacement.
### 15. Remove Subtask (`remove_subtask`) ### 16. Remove Subtask (`remove_subtask`)
* **MCP Tool:** `remove_subtask` * **MCP Tool:** `remove_subtask`
* **CLI Command:** `task-master remove-subtask [options]` * **CLI Command:** `task-master remove-subtask [options]`
@@ -211,7 +223,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
## Dependency Management ## Dependency Management
### 16. Add Dependency (`add_dependency`) ### 17. Add Dependency (`add_dependency`)
* **MCP Tool:** `add_dependency` * **MCP Tool:** `add_dependency`
* **CLI Command:** `task-master add-dependency [options]` * **CLI Command:** `task-master add-dependency [options]`
@@ -222,7 +234,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
* `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`) * `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`)
* **Usage:** Establish the correct order of execution between tasks. * **Usage:** Establish the correct order of execution between tasks.
### 17. Remove Dependency (`remove_dependency`) ### 18. Remove Dependency (`remove_dependency`)
* **MCP Tool:** `remove_dependency` * **MCP Tool:** `remove_dependency`
* **CLI Command:** `task-master remove-dependency [options]` * **CLI Command:** `task-master remove-dependency [options]`
@@ -233,7 +245,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
* `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`) * `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`)
* **Usage:** Update task relationships when the order of execution changes. * **Usage:** Update task relationships when the order of execution changes.
### 18. Validate Dependencies (`validate_dependencies`) ### 19. Validate Dependencies (`validate_dependencies`)
* **MCP Tool:** `validate_dependencies` * **MCP Tool:** `validate_dependencies`
* **CLI Command:** `task-master validate-dependencies [options]` * **CLI Command:** `task-master validate-dependencies [options]`
@@ -242,7 +254,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
* `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`) * `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`)
* **Usage:** Audit the integrity of your task dependencies. * **Usage:** Audit the integrity of your task dependencies.
### 19. Fix Dependencies (`fix_dependencies`) ### 20. Fix Dependencies (`fix_dependencies`)
* **MCP Tool:** `fix_dependencies` * **MCP Tool:** `fix_dependencies`
* **CLI Command:** `task-master fix-dependencies [options]` * **CLI Command:** `task-master fix-dependencies [options]`
@@ -255,7 +267,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
## Analysis & Reporting ## Analysis & Reporting
### 20. Analyze Complexity (`analyze_complexity`) ### 21. Analyze Complexity (`analyze_complexity`)
* **MCP Tool:** `analyze_complexity` * **MCP Tool:** `analyze_complexity`
* **CLI Command:** `task-master analyze-complexity [options]` * **CLI Command:** `task-master analyze-complexity [options]`
@@ -267,7 +279,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
* `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`) * `file`: `Path to your Taskmaster 'tasks.json' file (default relies on auto-detection).` (CLI: `-f, --file <file>`)
* **Usage:** Identify which tasks are likely too large and need further breakdown before implementation. * **Usage:** Identify which tasks are likely too large and need further breakdown before implementation.
### 21. Complexity Report (`complexity_report`) ### 22. Complexity Report (`complexity_report`)
* **MCP Tool:** `complexity_report` * **MCP Tool:** `complexity_report`
* **CLI Command:** `task-master complexity-report [options]` * **CLI Command:** `task-master complexity-report [options]`
@@ -280,7 +292,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
## File Generation ## File Generation
### 22. Generate Task Files (`generate`) ### 23. Generate Task Files (`generate`)
* **MCP Tool:** `generate` * **MCP Tool:** `generate`
* **CLI Command:** `task-master generate [options]` * **CLI Command:** `task-master generate [options]`
@@ -295,5 +307,5 @@ This document provides a detailed reference for interacting with Taskmaster, cov
## Configuration & Metadata ## Configuration & Metadata
- **Environment Variables**: Taskmaster relies on environment variables for configuration (API keys, model preferences, default settings). See [`dev_workflow.mdc`](mdc:.cursor/rules/dev_workflow.mdc) or the project README for a list. - **Environment Variables**: Taskmaster relies on environment variables for configuration (API keys, model preferences, default settings). See [`dev_workflow.mdc`](mdc:.cursor/rules/dev_workflow.mdc) or the project README for a list.
- **`tasks.json`**: The core data file containing the array of tasks and their details. See [`tasks.mdc`](mdc:.cursor/rules/tasks.mdc). - **`tasks.json`**: The core data file containing the array of tasks and their details. See [`tasks.mdc`](mdc:.cursor/rules/tasks.mdc) for details.
- **`task_xxx.md` files**: Individual markdown files generated by the `generate` command/tool, reflecting the content of `tasks.json`. - **`task_xxx.md` files**: Individual markdown files generated by the `generate` command/tool, reflecting the content of `tasks.json`.

View File

@@ -410,6 +410,21 @@ task-master update-subtask --id=<parentId.subtaskId> --prompt="<prompt>" --resea
Unlike the `update-task` command which replaces task information, the `update-subtask` command _appends_ new information to the existing subtask details, marking it with a timestamp. This is useful for iteratively enhancing subtasks while preserving the original content. Unlike the `update-task` command which replaces task information, the `update-subtask` command _appends_ new information to the existing subtask details, marking it with a timestamp. This is useful for iteratively enhancing subtasks while preserving the original content.
### Remove Task
```bash
# Remove a task permanently
task-master remove-task --id=<id>
# Remove a subtask permanently
task-master remove-task --id=<parentId.subtaskId>
# Skip the confirmation prompt
task-master remove-task --id=<id> --yes
```
The `remove-task` command permanently deletes a task or subtask from `tasks.json`. It also automatically cleans up any references to the deleted task in other tasks' dependencies. Consider using 'blocked', 'cancelled', or 'deferred' status instead if you want to keep the task for reference.
### Generate Task Files ### Generate Task Files
```bash ```bash

View File

@@ -0,0 +1,91 @@
/**
* remove-task.js
* Direct function implementation for removing a task
*/
import { removeTask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
/**
* Direct function wrapper for removeTask with error handling.
*
* @param {Object} args - Command arguments
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: false }
*/
export async function removeTaskDirect(args, log) {
try {
// Find the tasks path first
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log);
} catch (error) {
log.error(`Tasks file not found: ${error.message}`);
return {
success: false,
error: {
code: 'FILE_NOT_FOUND_ERROR',
message: error.message
},
fromCache: false
};
}
// Validate task ID parameter
const taskId = args.id;
if (!taskId) {
log.error('Task ID is required');
return {
success: false,
error: {
code: 'INPUT_VALIDATION_ERROR',
message: 'Task ID is required'
},
fromCache: false
};
}
// Skip confirmation in the direct function since it's handled by the client
log.info(`Removing task with ID: ${taskId} from ${tasksPath}`);
try {
// Call the core removeTask function
const result = await removeTask(tasksPath, taskId);
log.info(`Successfully removed task: ${taskId}`);
// Return the result
return {
success: true,
data: {
message: result.message,
taskId: taskId,
tasksPath: tasksPath,
removedTask: result.removedTask
},
fromCache: false
};
} catch (error) {
log.error(`Error removing task: ${error.message}`);
return {
success: false,
error: {
code: error.code || 'REMOVE_TASK_ERROR',
message: error.message || 'Failed to remove task'
},
fromCache: false
};
}
} catch (error) {
// Catch any unexpected errors
log.error(`Unexpected error in removeTaskDirect: ${error.message}`);
return {
success: false,
error: {
code: 'UNEXPECTED_ERROR',
message: error.message
},
fromCache: false
};
}
}

View File

@@ -27,6 +27,7 @@ import { validateDependenciesDirect } from './direct-functions/validate-dependen
import { fixDependenciesDirect } from './direct-functions/fix-dependencies.js'; import { fixDependenciesDirect } from './direct-functions/fix-dependencies.js';
import { complexityReportDirect } from './direct-functions/complexity-report.js'; import { complexityReportDirect } from './direct-functions/complexity-report.js';
import { addDependencyDirect } from './direct-functions/add-dependency.js'; import { addDependencyDirect } from './direct-functions/add-dependency.js';
import { removeTaskDirect } from './direct-functions/remove-task.js';
// Re-export utility functions // Re-export utility functions
export { findTasksJsonPath } from './utils/path-utils.js'; export { findTasksJsonPath } from './utils/path-utils.js';
@@ -54,7 +55,8 @@ export const directFunctions = new Map([
['validateDependenciesDirect', validateDependenciesDirect], ['validateDependenciesDirect', validateDependenciesDirect],
['fixDependenciesDirect', fixDependenciesDirect], ['fixDependenciesDirect', fixDependenciesDirect],
['complexityReportDirect', complexityReportDirect], ['complexityReportDirect', complexityReportDirect],
['addDependencyDirect', addDependencyDirect] ['addDependencyDirect', addDependencyDirect],
['removeTaskDirect', removeTaskDirect]
]); ]);
// Re-export all direct function implementations // Re-export all direct function implementations
@@ -80,5 +82,6 @@ export {
validateDependenciesDirect, validateDependenciesDirect,
fixDependenciesDirect, fixDependenciesDirect,
complexityReportDirect, complexityReportDirect,
addDependencyDirect addDependencyDirect,
removeTaskDirect
}; };

View File

@@ -25,6 +25,7 @@ import { registerValidateDependenciesTool } from "./validate-dependencies.js";
import { registerFixDependenciesTool } from "./fix-dependencies.js"; import { registerFixDependenciesTool } from "./fix-dependencies.js";
import { registerComplexityReportTool } from "./complexity-report.js"; import { registerComplexityReportTool } from "./complexity-report.js";
import { registerAddDependencyTool } from "./add-dependency.js"; import { registerAddDependencyTool } from "./add-dependency.js";
import { registerRemoveTaskTool } from './remove-task.js';
/** /**
* Register all Task Master tools with the MCP server * Register all Task Master tools with the MCP server
@@ -54,6 +55,7 @@ export function registerTaskMasterTools(server) {
registerFixDependenciesTool(server); registerFixDependenciesTool(server);
registerComplexityReportTool(server); registerComplexityReportTool(server);
registerAddDependencyTool(server); registerAddDependencyTool(server);
registerRemoveTaskTool(server);
} catch (error) { } catch (error) {
logger.error(`Error registering Task Master tools: ${error.message}`); logger.error(`Error registering Task Master tools: ${error.message}`);
throw error; throw error;

View File

@@ -0,0 +1,71 @@
/**
* tools/remove-task.js
* Tool to remove a task by ID
*/
import { z } from "zod";
import {
handleApiResult,
createErrorResponse,
getProjectRootFromSession
} from "./utils.js";
import { removeTaskDirect } from "../core/task-master-core.js";
/**
* Register the remove-task tool with the MCP server
* @param {Object} server - FastMCP server instance
*/
export function registerRemoveTaskTool(server) {
server.addTool({
name: "remove_task",
description: "Remove a task or subtask permanently from the tasks list",
parameters: z.object({
id: z.string().describe("ID of the task or subtask to remove (e.g., '5' or '5.2')"),
file: z.string().optional().describe("Path to the tasks file"),
projectRoot: z
.string()
.optional()
.describe(
"Root directory of the project (default: current working directory)"
),
confirm: z.boolean().optional().describe("Whether to skip confirmation prompt (default: false)")
}),
execute: async (args, { log, session }) => {
try {
log.info(`Removing task with ID: ${args.id}`);
// Get project root from session
let rootFolder = getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
} else if (!rootFolder) {
// Ensure we have a default if nothing else works
rootFolder = process.cwd();
log.warn(`Session and args failed to provide root, using CWD: ${rootFolder}`);
}
log.info(`Using project root: ${rootFolder}`);
// Assume client has already handled confirmation if needed
const result = await removeTaskDirect({
id: args.id,
file: args.file,
projectRoot: rootFolder
}, log);
if (result.success) {
log.info(`Successfully removed task: ${args.id}`);
} else {
log.error(`Failed to remove task: ${result.error.message}`);
}
return handleApiResult(result, log, 'Error removing task');
} catch (error) {
log.error(`Error in remove-task tool: ${error.message}`);
return createErrorResponse(`Failed to remove task: ${error.message}`);
}
},
});
}

452
package-lock.json generated
View File

@@ -1,13 +1,13 @@
{ {
"name": "task-master-ai", "name": "task-master-ai",
"version": "0.9.30", "version": "0.10.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "task-master-ai", "name": "task-master-ai",
"version": "0.9.30", "version": "0.10.0",
"license": "(BSL-1.1 AND Apache-2.0)", "license": "MIT WITH Commons-Clause",
"dependencies": { "dependencies": {
"@anthropic-ai/sdk": "^0.39.0", "@anthropic-ai/sdk": "^0.39.0",
"boxen": "^8.0.1", "boxen": "^8.0.1",
@@ -22,6 +22,7 @@
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"gradient-string": "^3.0.0", "gradient-string": "^3.0.0",
"helmet": "^8.1.0", "helmet": "^8.1.0",
"inquirer": "^12.5.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lru-cache": "^10.2.0", "lru-cache": "^10.2.0",
"openai": "^4.89.0", "openai": "^4.89.0",
@@ -937,6 +938,365 @@
"node": ">=0.1.90" "node": ">=0.1.90"
} }
}, },
"node_modules/@inquirer/checkbox": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.1.4.tgz",
"integrity": "sha512-d30576EZdApjAMceijXA5jDzRQHT/MygbC+J8I7EqA6f/FRpYxlRtRJbHF8gHeWYeSdOuTEJqonn7QLB1ELezA==",
"license": "MIT",
"dependencies": {
"@inquirer/core": "^10.1.9",
"@inquirer/figures": "^1.0.11",
"@inquirer/type": "^3.0.5",
"ansi-escapes": "^4.3.2",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/confirm": {
"version": "5.1.8",
"resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.8.tgz",
"integrity": "sha512-dNLWCYZvXDjO3rnQfk2iuJNL4Ivwz/T2+C3+WnNfJKsNGSuOs3wAo2F6e0p946gtSAk31nZMfW+MRmYaplPKsg==",
"license": "MIT",
"dependencies": {
"@inquirer/core": "^10.1.9",
"@inquirer/type": "^3.0.5"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/core": {
"version": "10.1.9",
"resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.1.9.tgz",
"integrity": "sha512-sXhVB8n20NYkUBfDYgizGHlpRVaCRjtuzNZA6xpALIUbkgfd2Hjz+DfEN6+h1BRnuxw0/P4jCIMjMsEOAMwAJw==",
"license": "MIT",
"dependencies": {
"@inquirer/figures": "^1.0.11",
"@inquirer/type": "^3.0.5",
"ansi-escapes": "^4.3.2",
"cli-width": "^4.1.0",
"mute-stream": "^2.0.0",
"signal-exit": "^4.1.0",
"wrap-ansi": "^6.2.0",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/core/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/@inquirer/core/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/@inquirer/core/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@inquirer/core/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@inquirer/core/node_modules/wrap-ansi": {
"version": "6.2.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
"integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/@inquirer/editor": {
"version": "4.2.9",
"resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.9.tgz",
"integrity": "sha512-8HjOppAxO7O4wV1ETUlJFg6NDjp/W2NP5FB9ZPAcinAlNT4ZIWOLe2pUVwmmPRSV0NMdI5r/+lflN55AwZOKSw==",
"license": "MIT",
"dependencies": {
"@inquirer/core": "^10.1.9",
"@inquirer/type": "^3.0.5",
"external-editor": "^3.1.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/expand": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.11.tgz",
"integrity": "sha512-OZSUW4hFMW2TYvX/Sv+NnOZgO8CHT2TU1roUCUIF2T+wfw60XFRRp9MRUPCT06cRnKL+aemt2YmTWwt7rOrNEA==",
"license": "MIT",
"dependencies": {
"@inquirer/core": "^10.1.9",
"@inquirer/type": "^3.0.5",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/figures": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.11.tgz",
"integrity": "sha512-eOg92lvrn/aRUqbxRyvpEWnrvRuTYRifixHkYVpJiygTgVSBIHDqLh0SrMQXkafvULg3ck11V7xvR+zcgvpHFw==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/@inquirer/input": {
"version": "4.1.8",
"resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.1.8.tgz",
"integrity": "sha512-WXJI16oOZ3/LiENCAxe8joniNp8MQxF6Wi5V+EBbVA0ZIOpFcL4I9e7f7cXse0HJeIPCWO8Lcgnk98juItCi7Q==",
"license": "MIT",
"dependencies": {
"@inquirer/core": "^10.1.9",
"@inquirer/type": "^3.0.5"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/number": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.11.tgz",
"integrity": "sha512-pQK68CsKOgwvU2eA53AG/4npRTH2pvs/pZ2bFvzpBhrznh8Mcwt19c+nMO7LHRr3Vreu1KPhNBF3vQAKrjIulw==",
"license": "MIT",
"dependencies": {
"@inquirer/core": "^10.1.9",
"@inquirer/type": "^3.0.5"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/password": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.11.tgz",
"integrity": "sha512-dH6zLdv+HEv1nBs96Case6eppkRggMe8LoOTl30+Gq5Wf27AO/vHFgStTVz4aoevLdNXqwE23++IXGw4eiOXTg==",
"license": "MIT",
"dependencies": {
"@inquirer/core": "^10.1.9",
"@inquirer/type": "^3.0.5",
"ansi-escapes": "^4.3.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/prompts": {
"version": "7.4.0",
"resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.4.0.tgz",
"integrity": "sha512-EZiJidQOT4O5PYtqnu1JbF0clv36oW2CviR66c7ma4LsupmmQlUwmdReGKRp456OWPWMz3PdrPiYg3aCk3op2w==",
"license": "MIT",
"dependencies": {
"@inquirer/checkbox": "^4.1.4",
"@inquirer/confirm": "^5.1.8",
"@inquirer/editor": "^4.2.9",
"@inquirer/expand": "^4.0.11",
"@inquirer/input": "^4.1.8",
"@inquirer/number": "^3.0.11",
"@inquirer/password": "^4.0.11",
"@inquirer/rawlist": "^4.0.11",
"@inquirer/search": "^3.0.11",
"@inquirer/select": "^4.1.0"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/rawlist": {
"version": "4.0.11",
"resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.0.11.tgz",
"integrity": "sha512-uAYtTx0IF/PqUAvsRrF3xvnxJV516wmR6YVONOmCWJbbt87HcDHLfL9wmBQFbNJRv5kCjdYKrZcavDkH3sVJPg==",
"license": "MIT",
"dependencies": {
"@inquirer/core": "^10.1.9",
"@inquirer/type": "^3.0.5",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/search": {
"version": "3.0.11",
"resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.0.11.tgz",
"integrity": "sha512-9CWQT0ikYcg6Ls3TOa7jljsD7PgjcsYEM0bYE+Gkz+uoW9u8eaJCRHJKkucpRE5+xKtaaDbrND+nPDoxzjYyew==",
"license": "MIT",
"dependencies": {
"@inquirer/core": "^10.1.9",
"@inquirer/figures": "^1.0.11",
"@inquirer/type": "^3.0.5",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/select": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.1.0.tgz",
"integrity": "sha512-z0a2fmgTSRN+YBuiK1ROfJ2Nvrpij5lVN3gPDkQGhavdvIVGHGW29LwYZfM/j42Ai2hUghTI/uoBuTbrJk42bA==",
"license": "MIT",
"dependencies": {
"@inquirer/core": "^10.1.9",
"@inquirer/figures": "^1.0.11",
"@inquirer/type": "^3.0.5",
"ansi-escapes": "^4.3.2",
"yoctocolors-cjs": "^2.1.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@inquirer/type": {
"version": "3.0.5",
"resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.5.tgz",
"integrity": "sha512-ZJpeIYYueOz/i/ONzrfof8g89kNdO2hjGuvULROo3O8rlB2CRtSseE5KeirnyE4t/thAn/EwvS/vuQeJCn+NZg==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/@istanbuljs/load-nyc-config": { "node_modules/@istanbuljs/load-nyc-config": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz",
@@ -2098,7 +2458,6 @@
"version": "4.3.2", "version": "4.3.2",
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz",
"integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"type-fest": "^0.21.3" "type-fest": "^0.21.3"
@@ -2114,7 +2473,6 @@
"version": "0.21.3", "version": "0.21.3",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz",
"integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==",
"dev": true,
"license": "(MIT OR CC0-1.0)", "license": "(MIT OR CC0-1.0)",
"engines": { "engines": {
"node": ">=10" "node": ">=10"
@@ -2618,7 +2976,6 @@
"version": "0.7.0", "version": "0.7.0",
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
"dev": true,
"license": "MIT" "license": "MIT"
}, },
"node_modules/ci-info": { "node_modules/ci-info": {
@@ -2739,6 +3096,15 @@
"node": ">=8" "node": ">=8"
} }
}, },
"node_modules/cli-width": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz",
"integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==",
"license": "ISC",
"engines": {
"node": ">= 12"
}
},
"node_modules/cliui": { "node_modules/cliui": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
@@ -3545,7 +3911,6 @@
"version": "3.1.0", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
"integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"chardet": "^0.7.0", "chardet": "^0.7.0",
@@ -4419,6 +4784,32 @@
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC" "license": "ISC"
}, },
"node_modules/inquirer": {
"version": "12.5.0",
"resolved": "https://registry.npmjs.org/inquirer/-/inquirer-12.5.0.tgz",
"integrity": "sha512-aiBBq5aKF1k87MTxXDylLfwpRwToShiHrSv4EmB07EYyLgmnjEz5B3rn0aGw1X3JA/64Ngf2T54oGwc+BCsPIQ==",
"license": "MIT",
"dependencies": {
"@inquirer/core": "^10.1.9",
"@inquirer/prompts": "^7.4.0",
"@inquirer/type": "^3.0.5",
"ansi-escapes": "^4.3.2",
"mute-stream": "^2.0.0",
"run-async": "^3.0.0",
"rxjs": "^7.8.2"
},
"engines": {
"node": ">=18"
},
"peerDependencies": {
"@types/node": ">=18"
},
"peerDependenciesMeta": {
"@types/node": {
"optional": true
}
}
},
"node_modules/ipaddr.js": { "node_modules/ipaddr.js": {
"version": "1.9.1", "version": "1.9.1",
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
@@ -5736,6 +6127,15 @@
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/mute-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz",
"integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==",
"license": "ISC",
"engines": {
"node": "^18.17.0 || >=20.5.0"
}
},
"node_modules/natural-compare": { "node_modules/natural-compare": {
"version": "1.4.0", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
@@ -5952,7 +6352,6 @@
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
"integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -6555,6 +6954,15 @@
"node": ">=16" "node": ">=16"
} }
}, },
"node_modules/run-async": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz",
"integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==",
"license": "MIT",
"engines": {
"node": ">=0.12.0"
}
},
"node_modules/run-parallel": { "node_modules/run-parallel": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -6579,6 +6987,15 @@
"queue-microtask": "^1.2.2" "queue-microtask": "^1.2.2"
} }
}, },
"node_modules/rxjs": {
"version": "7.8.2",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz",
"integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==",
"license": "Apache-2.0",
"dependencies": {
"tslib": "^2.1.0"
}
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@@ -7124,7 +7541,6 @@
"version": "0.0.33", "version": "0.0.33",
"resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
"integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
"dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"os-tmpdir": "~1.0.2" "os-tmpdir": "~1.0.2"
@@ -7179,6 +7595,12 @@
"url": "https://github.com/sponsors/Borewit" "url": "https://github.com/sponsors/Borewit"
} }
}, },
"node_modules/tslib": {
"version": "2.8.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
"node_modules/type-detect": { "node_modules/type-detect": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
@@ -7556,6 +7978,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/yoctocolors-cjs": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.2.tgz",
"integrity": "sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==",
"license": "MIT",
"engines": {
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": { "node_modules/zod": {
"version": "3.24.2", "version": "3.24.2",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz", "resolved": "https://registry.npmjs.org/zod/-/zod-3.24.2.tgz",

View File

@@ -50,6 +50,7 @@
"fuse.js": "^7.0.0", "fuse.js": "^7.0.0",
"gradient-string": "^3.0.0", "gradient-string": "^3.0.0",
"helmet": "^8.1.0", "helmet": "^8.1.0",
"inquirer": "^12.5.0",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"lru-cache": "^10.2.0", "lru-cache": "^10.2.0",
"openai": "^4.89.0", "openai": "^4.89.0",

View File

@@ -515,6 +515,7 @@ function createProjectStructure(projectName, projectDescription, projectVersion,
"boxen": "^7.1.1", "boxen": "^7.1.1",
"gradient-string": "^2.0.2", "gradient-string": "^2.0.2",
"cli-table3": "^0.6.3", "cli-table3": "^0.6.3",
"inquirer": "^9.2.15",
"ora": "^7.0.1" "ora": "^7.0.1"
} }
}; };

View File

@@ -9,6 +9,7 @@ import chalk from 'chalk';
import boxen from 'boxen'; import boxen from 'boxen';
import fs from 'fs'; import fs from 'fs';
import https from 'https'; import https from 'https';
import inquirer from 'inquirer';
import { CONFIG, log, readJSON } from './utils.js'; import { CONFIG, log, readJSON } from './utils.js';
import { import {
@@ -25,7 +26,10 @@ import {
removeSubtask, removeSubtask,
analyzeTaskComplexity, analyzeTaskComplexity,
updateTaskById, updateTaskById,
updateSubtaskById updateSubtaskById,
removeTask,
findTaskById,
taskExists
} from './task-manager.js'; } from './task-manager.js';
import { import {
@@ -42,7 +46,9 @@ import {
displayTaskById, displayTaskById,
displayComplexityReport, displayComplexityReport,
getStatusWithColor, getStatusWithColor,
confirmTaskOverwrite confirmTaskOverwrite,
startLoadingIndicator,
stopLoadingIndicator
} from './ui.js'; } from './ui.js';
/** /**
@@ -863,7 +869,120 @@ function registerCommands(programInstance) {
console.log(chalk.white(' task-master init -y')); console.log(chalk.white(' task-master init -y'));
process.exit(0); process.exit(0);
}); });
// remove-task command
programInstance
.command('remove-task')
.description('Remove a task or subtask permanently')
.option('-i, --id <id>', 'ID of the task or subtask to remove (e.g., "5" or "5.2")')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option('-y, --yes', 'Skip confirmation prompt', false)
.action(async (options) => {
const tasksPath = options.file;
const taskId = options.id;
if (!taskId) {
console.error(chalk.red('Error: Task ID is required'));
console.error(chalk.yellow('Usage: task-master remove-task --id=<taskId>'));
process.exit(1);
}
try {
// Check if the task exists
const data = readJSON(tasksPath);
if (!data || !data.tasks) {
console.error(chalk.red(`Error: No valid tasks found in ${tasksPath}`));
process.exit(1);
}
if (!taskExists(data.tasks, taskId)) {
console.error(chalk.red(`Error: Task with ID ${taskId} not found`));
process.exit(1);
}
// Load task for display
const task = findTaskById(data.tasks, taskId);
// Skip confirmation if --yes flag is provided
if (!options.yes) {
// Display task information
console.log();
console.log(chalk.red.bold('⚠️ WARNING: This will permanently delete the following task:'));
console.log();
if (typeof taskId === 'string' && taskId.includes('.')) {
// It's a subtask
const [parentId, subtaskId] = taskId.split('.');
console.log(chalk.white.bold(`Subtask ${taskId}: ${task.title}`));
console.log(chalk.gray(`Parent Task: ${task.parentTask.id} - ${task.parentTask.title}`));
} else {
// It's a main task
console.log(chalk.white.bold(`Task ${taskId}: ${task.title}`));
// Show if it has subtasks
if (task.subtasks && task.subtasks.length > 0) {
console.log(chalk.yellow(`⚠️ This task has ${task.subtasks.length} subtasks that will also be deleted!`));
}
// Show if other tasks depend on it
const dependentTasks = data.tasks.filter(t =>
t.dependencies && t.dependencies.includes(parseInt(taskId, 10)));
if (dependentTasks.length > 0) {
console.log(chalk.yellow(`⚠️ Warning: ${dependentTasks.length} other tasks depend on this task!`));
console.log(chalk.yellow('These dependencies will be removed:'));
dependentTasks.forEach(t => {
console.log(chalk.yellow(` - Task ${t.id}: ${t.title}`));
});
}
}
console.log();
// Prompt for confirmation
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: chalk.red.bold('Are you sure you want to permanently delete this task?'),
default: false
}
]);
if (!confirm) {
console.log(chalk.blue('Task deletion cancelled.'));
process.exit(0);
}
}
const indicator = startLoadingIndicator('Removing task...');
// Remove the task
const result = await removeTask(tasksPath, taskId);
stopLoadingIndicator(indicator);
// Display success message with appropriate color based on task or subtask
if (typeof taskId === 'string' && taskId.includes('.')) {
// It was a subtask
console.log(boxen(
chalk.green(`Subtask ${taskId} has been successfully removed`),
{ padding: 1, borderColor: 'green', borderStyle: 'round' }
));
} else {
// It was a main task
console.log(boxen(
chalk.green(`Task ${taskId} has been successfully removed`),
{ padding: 1, borderColor: 'green', borderStyle: 'round' }
));
}
} catch (error) {
console.error(chalk.red(`Error: ${error.message || 'An unknown error occurred'}`));
process.exit(1);
}
});
// Add more commands as needed... // Add more commands as needed...
return programInstance; return programInstance;

View File

@@ -3464,6 +3464,156 @@ Provide concrete examples, code snippets, or implementation details when relevan
} }
} }
/**
* Removes a task or subtask from the tasks file
* @param {string} tasksPath - Path to the tasks file
* @param {string|number} taskId - ID of task or subtask to remove (e.g., '5' or '5.2')
* @returns {Object} Result object with success message and removed task info
*/
async function removeTask(tasksPath, taskId) {
try {
// Read the tasks file
const data = readJSON(tasksPath);
if (!data || !data.tasks) {
throw new Error(`No valid tasks found in ${tasksPath}`);
}
// Check if the task ID exists
if (!taskExists(data.tasks, taskId)) {
throw new Error(`Task with ID ${taskId} not found`);
}
// Handle subtask removal (e.g., '5.2')
if (typeof taskId === 'string' && taskId.includes('.')) {
const [parentTaskId, subtaskId] = taskId.split('.').map(id => parseInt(id, 10));
// Find the parent task
const parentTask = data.tasks.find(t => t.id === parentTaskId);
if (!parentTask || !parentTask.subtasks) {
throw new Error(`Parent task with ID ${parentTaskId} or its subtasks not found`);
}
// Find the subtask to remove
const subtaskIndex = parentTask.subtasks.findIndex(st => st.id === subtaskId);
if (subtaskIndex === -1) {
throw new Error(`Subtask with ID ${subtaskId} not found in parent task ${parentTaskId}`);
}
// Store the subtask info before removal for the result
const removedSubtask = parentTask.subtasks[subtaskIndex];
// Remove the subtask
parentTask.subtasks.splice(subtaskIndex, 1);
// Remove references to this subtask in other subtasks' dependencies
if (parentTask.subtasks && parentTask.subtasks.length > 0) {
parentTask.subtasks.forEach(subtask => {
if (subtask.dependencies && subtask.dependencies.includes(subtaskId)) {
subtask.dependencies = subtask.dependencies.filter(depId => depId !== subtaskId);
}
});
}
// Save the updated tasks
writeJSON(tasksPath, data);
// Generate updated task files
try {
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
} catch (genError) {
log('warn', `Successfully removed subtask but failed to regenerate task files: ${genError.message}`);
}
return {
success: true,
message: `Successfully removed subtask ${subtaskId} from task ${parentTaskId}`,
removedTask: removedSubtask,
parentTaskId: parentTaskId
};
}
// Handle main task removal
const taskIdNum = parseInt(taskId, 10);
const taskIndex = data.tasks.findIndex(t => t.id === taskIdNum);
if (taskIndex === -1) {
throw new Error(`Task with ID ${taskId} not found`);
}
// Store the task info before removal for the result
const removedTask = data.tasks[taskIndex];
// Remove the task
data.tasks.splice(taskIndex, 1);
// Remove references to this task in other tasks' dependencies
data.tasks.forEach(task => {
if (task.dependencies && task.dependencies.includes(taskIdNum)) {
task.dependencies = task.dependencies.filter(depId => depId !== taskIdNum);
}
});
// Save the updated tasks
writeJSON(tasksPath, data);
// Delete the task file if it exists
const taskFileName = path.join(path.dirname(tasksPath), `task_${taskIdNum.toString().padStart(3, '0')}.txt`);
if (fs.existsSync(taskFileName)) {
try {
fs.unlinkSync(taskFileName);
} catch (unlinkError) {
log('warn', `Successfully removed task from tasks.json but failed to delete task file: ${unlinkError.message}`);
}
}
// Generate updated task files
try {
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
} catch (genError) {
log('warn', `Successfully removed task but failed to regenerate task files: ${genError.message}`);
}
return {
success: true,
message: `Successfully removed task ${taskId}`,
removedTask: removedTask
};
} catch (error) {
log('error', `Error removing task: ${error.message}`);
throw {
code: 'REMOVE_TASK_ERROR',
message: error.message,
details: error.stack
};
}
}
/**
* Checks if a task with the given ID exists
* @param {Array} tasks - Array of tasks to search
* @param {string|number} taskId - ID of task or subtask to check
* @returns {boolean} Whether the task exists
*/
function taskExists(tasks, taskId) {
// Handle subtask IDs (e.g., "1.2")
if (typeof taskId === 'string' && taskId.includes('.')) {
const [parentIdStr, subtaskIdStr] = taskId.split('.');
const parentId = parseInt(parentIdStr, 10);
const subtaskId = parseInt(subtaskIdStr, 10);
// Find the parent task
const parentTask = tasks.find(t => t.id === parentId);
// If parent exists, check if subtask exists
return parentTask &&
parentTask.subtasks &&
parentTask.subtasks.some(st => st.id === subtaskId);
}
// Handle regular task IDs
const id = parseInt(taskId, 10);
return tasks.some(t => t.id === id);
}
// Export task manager functions // Export task manager functions
export { export {
parsePRD, parsePRD,
@@ -3482,4 +3632,7 @@ export {
removeSubtask, removeSubtask,
findNextTask, findNextTask,
analyzeTaskComplexity, analyzeTaskComplexity,
removeTask,
findTaskById,
taskExists,
}; };