feat(move-tasks): Implement move command for tasks and subtasks
Adds a new CLI command and MCP tool to reorganize tasks and subtasks within the hierarchy. Features include: - Moving tasks between different positions in the task list - Converting tasks to subtasks and vice versa - Moving subtasks between parents - Moving multiple tasks at once with comma-separated IDs - Creating placeholder tasks when moving to new IDs - Validation to prevent accidental data loss This is particularly useful for resolving merge conflicts when multiple team members create tasks on different branches.
This commit is contained in:
5
.changeset/tired-doodles-end.md
Normal file
5
.changeset/tired-doodles-end.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
'task-master-ai': patch
|
||||
---
|
||||
|
||||
adds alias to set-status 'mark' and 'set'
|
||||
29
.changeset/wild-seas-read.md
Normal file
29
.changeset/wild-seas-read.md
Normal file
@@ -0,0 +1,29 @@
|
||||
---
|
||||
'task-master-ai': patch
|
||||
---
|
||||
|
||||
Add move command to enable moving tasks and subtasks within the task hierarchy. This new command supports moving standalone tasks to become subtasks, subtasks to become standalone tasks, and moving subtasks between different parents. The implementation handles circular dependencies, validation, and proper updating of parent-child relationships.
|
||||
|
||||
**Usage:**
|
||||
- CLI command: `task-master move --from=<id> --to=<id>`
|
||||
- MCP tool: `move_task` with parameters:
|
||||
- `from`: ID of task/subtask to move (e.g., "5" or "5.2")
|
||||
- `to`: ID of destination (e.g., "7" or "7.3")
|
||||
- `file` (optional): Custom path to tasks.json
|
||||
|
||||
**Example scenarios:**
|
||||
- Move task to become subtask: `--from="5" --to="7"`
|
||||
- Move subtask to standalone task: `--from="5.2" --to="7"`
|
||||
- Move subtask to different parent: `--from="5.2" --to="7.3"`
|
||||
- Reorder subtask within same parent: `--from="5.2" --to="5.4"`
|
||||
- Move multiple tasks at once: `--from="10,11,12" --to="16,17,18"`
|
||||
- Move task to new ID: `--from="5" --to="25"` (creates a new task with ID 25)
|
||||
|
||||
**Multiple Task Support:**
|
||||
The command supports moving multiple tasks simultaneously by providing comma-separated lists for both `--from` and `--to` parameters. The number of source and destination IDs must match. This is particularly useful for resolving merge conflicts in task files when multiple team members have created tasks on different branches.
|
||||
|
||||
**Validation Features:**
|
||||
- Allows moving tasks to new, non-existent IDs (automatically creates placeholders)
|
||||
- Prevents moving to existing task IDs that already contain content (to avoid overwriting)
|
||||
- Validates source tasks exist before attempting to move them
|
||||
- Ensures proper parent-child relationships are maintained
|
||||
@@ -49,6 +49,7 @@ Task Master offers two primary ways to interact:
|
||||
- Maintain valid dependency structure with `add_dependency`/`remove_dependency` tools or `task-master add-dependency`/`remove-dependency` commands, `validate_dependencies` / `task-master validate-dependencies`, and `fix_dependencies` / `task-master fix-dependencies` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) when needed
|
||||
- Respect dependency chains and task priorities when selecting work
|
||||
- Report progress regularly using `get_tasks` / `task-master list`
|
||||
- Reorganize tasks as needed using `move_task` / `task-master move --from=<id> --to=<id>` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to change task hierarchy or ordering
|
||||
|
||||
## Task Complexity Analysis
|
||||
|
||||
@@ -154,6 +155,25 @@ Taskmaster configuration is managed through two main mechanisms:
|
||||
- Task files are automatically regenerated after dependency changes
|
||||
- Dependencies are visualized with status indicators in task listings and files
|
||||
|
||||
## Task Reorganization
|
||||
|
||||
- Use `move_task` / `task-master move --from=<id> --to=<id>` to move tasks or subtasks within the hierarchy
|
||||
- This command supports several use cases:
|
||||
- Moving a standalone task to become a subtask (e.g., `--from=5 --to=7`)
|
||||
- Moving a subtask to become a standalone task (e.g., `--from=5.2 --to=7`)
|
||||
- Moving a subtask to a different parent (e.g., `--from=5.2 --to=7.3`)
|
||||
- Reordering subtasks within the same parent (e.g., `--from=5.2 --to=5.4`)
|
||||
- Moving a task to a new, non-existent ID position (e.g., `--from=5 --to=25`)
|
||||
- Moving multiple tasks at once using comma-separated IDs (e.g., `--from=10,11,12 --to=16,17,18`)
|
||||
- The system includes validation to prevent data loss:
|
||||
- Allows moving to non-existent IDs by creating placeholder tasks
|
||||
- Prevents moving to existing task IDs that have content (to avoid overwriting)
|
||||
- Validates source tasks exist before attempting to move them
|
||||
- The system maintains proper parent-child relationships and dependency integrity
|
||||
- Task files are automatically regenerated after the move operation
|
||||
- This provides greater flexibility in organizing and refining your task structure as project understanding evolves
|
||||
- This is especially useful when dealing with potential merge conflicts arising from teams creating tasks on separate branches. Solve these conflicts very easily by moving your tasks and keeping theirs.
|
||||
|
||||
## Iterative Subtask Implementation
|
||||
|
||||
Once a task has been broken down into subtasks using `expand_task` or similar methods, follow this iterative process for implementation:
|
||||
|
||||
@@ -269,11 +269,36 @@ 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>`)
|
||||
* **Usage:** Delete unnecessary subtasks or promote a subtask to a top-level task.
|
||||
|
||||
### 17. Move Task (`move_task`)
|
||||
|
||||
* **MCP Tool:** `move_task`
|
||||
* **CLI Command:** `task-master move [options]`
|
||||
* **Description:** `Move a task or subtask to a new position within the task hierarchy.`
|
||||
* **Key Parameters/Options:**
|
||||
* `from`: `Required. ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated for multiple tasks.` (CLI: `--from <id>`)
|
||||
* `to`: `Required. ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated.` (CLI: `--to <id>`)
|
||||
* `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file <file>`)
|
||||
* **Usage:** Reorganize tasks by moving them within the hierarchy. Supports various scenarios like:
|
||||
* Moving a task to become a subtask
|
||||
* Moving a subtask to become a standalone task
|
||||
* Moving a subtask to a different parent
|
||||
* Reordering subtasks within the same parent
|
||||
* Moving a task to a new, non-existent ID (automatically creates placeholders)
|
||||
* Moving multiple tasks at once with comma-separated IDs
|
||||
* **Validation Features:**
|
||||
* Allows moving tasks to non-existent destination IDs (creates placeholder tasks)
|
||||
* Prevents moving to existing task IDs that already have content (to avoid overwriting)
|
||||
* Validates that source tasks exist before attempting to move them
|
||||
* Maintains proper parent-child relationships
|
||||
* **Example CLI:** `task-master move --from=5.2 --to=7.3` to move subtask 5.2 to become subtask 7.3.
|
||||
* **Example Multi-Move:** `task-master move --from=10,11,12 --to=16,17,18` to move multiple tasks to new positions.
|
||||
* **Common Use:** Resolving merge conflicts in tasks.json when multiple team members create tasks on different branches.
|
||||
|
||||
---
|
||||
|
||||
## Dependency Management
|
||||
|
||||
### 17. Add Dependency (`add_dependency`)
|
||||
### 18. Add Dependency (`add_dependency`)
|
||||
|
||||
* **MCP Tool:** `add_dependency`
|
||||
* **CLI Command:** `task-master add-dependency [options]`
|
||||
@@ -284,7 +309,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 <path>`)
|
||||
* **Usage:** Establish the correct order of execution between tasks.
|
||||
|
||||
### 18. Remove Dependency (`remove_dependency`)
|
||||
### 19. Remove Dependency (`remove_dependency`)
|
||||
|
||||
* **MCP Tool:** `remove_dependency`
|
||||
* **CLI Command:** `task-master remove-dependency [options]`
|
||||
@@ -295,7 +320,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>`)
|
||||
* **Usage:** Update task relationships when the order of execution changes.
|
||||
|
||||
### 19. Validate Dependencies (`validate_dependencies`)
|
||||
### 20. Validate Dependencies (`validate_dependencies`)
|
||||
|
||||
* **MCP Tool:** `validate_dependencies`
|
||||
* **CLI Command:** `task-master validate-dependencies [options]`
|
||||
@@ -304,7 +329,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>`)
|
||||
* **Usage:** Audit the integrity of your task dependencies.
|
||||
|
||||
### 20. Fix Dependencies (`fix_dependencies`)
|
||||
### 21. Fix Dependencies (`fix_dependencies`)
|
||||
|
||||
* **MCP Tool:** `fix_dependencies`
|
||||
* **CLI Command:** `task-master fix-dependencies [options]`
|
||||
@@ -317,7 +342,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
|
||||
|
||||
## Analysis & Reporting
|
||||
|
||||
### 21. Analyze Project Complexity (`analyze_project_complexity`)
|
||||
### 22. Analyze Project Complexity (`analyze_project_complexity`)
|
||||
|
||||
* **MCP Tool:** `analyze_project_complexity`
|
||||
* **CLI Command:** `task-master analyze-complexity [options]`
|
||||
@@ -330,7 +355,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
|
||||
* **Usage:** Used before breaking down tasks to identify which ones need the most attention.
|
||||
* **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress.
|
||||
|
||||
### 22. View Complexity Report (`complexity_report`)
|
||||
### 23. View Complexity Report (`complexity_report`)
|
||||
|
||||
* **MCP Tool:** `complexity_report`
|
||||
* **CLI Command:** `task-master complexity-report [options]`
|
||||
@@ -343,7 +368,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
|
||||
|
||||
## File Management
|
||||
|
||||
### 23. Generate Task Files (`generate`)
|
||||
### 24. Generate Task Files (`generate`)
|
||||
|
||||
* **MCP Tool:** `generate`
|
||||
* **CLI Command:** `task-master generate [options]`
|
||||
|
||||
@@ -187,6 +187,32 @@ task-master validate-dependencies
|
||||
task-master fix-dependencies
|
||||
```
|
||||
|
||||
## Move Tasks
|
||||
|
||||
```bash
|
||||
# Move a task or subtask to a new position
|
||||
task-master move --from=<id> --to=<id>
|
||||
|
||||
# Examples:
|
||||
# Move task to become a subtask
|
||||
task-master move --from=5 --to=7
|
||||
|
||||
# Move subtask to become a standalone task
|
||||
task-master move --from=5.2 --to=7
|
||||
|
||||
# Move subtask to a different parent
|
||||
task-master move --from=5.2 --to=7.3
|
||||
|
||||
# Reorder subtasks within the same parent
|
||||
task-master move --from=5.2 --to=5.4
|
||||
|
||||
# Move a task to a new ID position (creates placeholder if doesn't exist)
|
||||
task-master move --from=5 --to=25
|
||||
|
||||
# Move multiple tasks at once (must have the same number of IDs)
|
||||
task-master move --from=10,11,12 --to=16,17,18
|
||||
```
|
||||
|
||||
## Add a New Task
|
||||
|
||||
```bash
|
||||
|
||||
@@ -30,7 +30,7 @@ I need to regenerate the subtasks for task 3 with a different approach. Can you
|
||||
## Handling changes
|
||||
|
||||
```
|
||||
We've decided to use MongoDB instead of PostgreSQL. Can you update all future tasks to reflect this change?
|
||||
I've decided to use MongoDB instead of PostgreSQL. Can you update all future tasks to reflect this change?
|
||||
```
|
||||
|
||||
## Completing work
|
||||
@@ -40,6 +40,32 @@ I've finished implementing the authentication system described in task 2. All te
|
||||
Please mark it as complete and tell me what I should work on next.
|
||||
```
|
||||
|
||||
## Reorganizing tasks
|
||||
|
||||
```
|
||||
I think subtask 5.2 would fit better as part of task 7. Can you move it there?
|
||||
```
|
||||
|
||||
(Agent runs: `task-master move --from=5.2 --to=7.3`)
|
||||
|
||||
```
|
||||
Task 8 should actually be a subtask of task 4. Can you reorganize this?
|
||||
```
|
||||
|
||||
(Agent runs: `task-master move --from=8 --to=4.1`)
|
||||
|
||||
```
|
||||
I just merged the main branch and there's a conflict in tasks.json. My teammates created tasks 10-15 on their branch while I created tasks 10-12 on my branch. Can you help me resolve this by moving my tasks?
|
||||
```
|
||||
|
||||
(Agent runs:
|
||||
```bash
|
||||
task-master move --from=10 --to=16
|
||||
task-master move --from=11 --to=17
|
||||
task-master move --from=12 --to=18
|
||||
```
|
||||
)
|
||||
|
||||
## Analyzing complexity
|
||||
|
||||
```
|
||||
|
||||
@@ -268,7 +268,60 @@ task-master update --from=4 --prompt="Update to use MongoDB, researching best pr
|
||||
|
||||
This will rewrite or re-scope subsequent tasks in tasks.json while preserving completed work.
|
||||
|
||||
### 6. Breaking Down Complex Tasks
|
||||
### 6. Reorganizing Tasks
|
||||
|
||||
If you need to reorganize your task structure:
|
||||
|
||||
```
|
||||
I think subtask 5.2 would fit better as part of task 7 instead. Can you move it there?
|
||||
```
|
||||
|
||||
The agent will execute:
|
||||
|
||||
```bash
|
||||
task-master move --from=5.2 --to=7.3
|
||||
```
|
||||
|
||||
You can reorganize tasks in various ways:
|
||||
|
||||
- Moving a standalone task to become a subtask: `--from=5 --to=7`
|
||||
- Moving a subtask to become a standalone task: `--from=5.2 --to=7`
|
||||
- Moving a subtask to a different parent: `--from=5.2 --to=7.3`
|
||||
- Reordering subtasks within the same parent: `--from=5.2 --to=5.4`
|
||||
- Moving a task to a new ID position: `--from=5 --to=25` (even if task 25 doesn't exist yet)
|
||||
- Moving multiple tasks at once: `--from=10,11,12 --to=16,17,18` (must have same number of IDs, Taskmaster will look through each position)
|
||||
|
||||
When moving tasks to new IDs:
|
||||
- The system automatically creates placeholder tasks for non-existent destination IDs
|
||||
- This prevents accidental data loss during reorganization
|
||||
- Any tasks that depend on moved tasks will have their dependencies updated
|
||||
- When moving a parent task, all its subtasks are automatically moved with it and renumbered
|
||||
|
||||
This is particularly useful as your project understanding evolves and you need to refine your task structure.
|
||||
|
||||
### 7. Resolving Merge Conflicts with Tasks
|
||||
|
||||
When working with a team, you might encounter merge conflicts in your tasks.json file if multiple team members create tasks on different branches. The move command makes resolving these conflicts straightforward:
|
||||
|
||||
```
|
||||
I just merged the main branch and there's a conflict with tasks.json. My teammates created tasks 10-15 while I created tasks 10-12 on my branch. Can you help me resolve this?
|
||||
```
|
||||
|
||||
The agent will help you:
|
||||
|
||||
1. Keep your teammates' tasks (10-15)
|
||||
2. Move your tasks to new positions to avoid conflicts:
|
||||
|
||||
```bash
|
||||
# Move your tasks to new positions (e.g., 16-18)
|
||||
task-master move --from=10 --to=16
|
||||
task-master move --from=11 --to=17
|
||||
task-master move --from=12 --to=18
|
||||
```
|
||||
|
||||
This approach preserves everyone's work while maintaining a clean task structure, making it much easier to handle task conflicts than trying to manually merge JSON files.
|
||||
|
||||
### 8. Breaking Down Complex Tasks
|
||||
|
||||
For complex tasks that need more granularity:
|
||||
|
||||
|
||||
93
mcp-server/src/core/direct-functions/move-task.js
Normal file
93
mcp-server/src/core/direct-functions/move-task.js
Normal file
@@ -0,0 +1,93 @@
|
||||
/**
|
||||
* Direct function wrapper for moveTask
|
||||
*/
|
||||
|
||||
import { moveTask } from '../../../../scripts/modules/task-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
|
||||
/**
|
||||
* Move a task or subtask to a new position
|
||||
* @param {Object} args - Function arguments
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file
|
||||
* @param {string} args.sourceId - ID of the task/subtask to move (e.g., '5' or '5.2')
|
||||
* @param {string} args.destinationId - ID of the destination (e.g., '7' or '7.3')
|
||||
* @param {string} args.file - Alternative path to the tasks.json file
|
||||
* @param {string} args.projectRoot - Project root directory
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<{success: boolean, data?: Object, error?: Object}>}
|
||||
*/
|
||||
export async function moveTaskDirect(args, log, context = {}) {
|
||||
const { session } = context;
|
||||
|
||||
// Validate required parameters
|
||||
if (!args.sourceId) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Source ID is required',
|
||||
code: 'MISSING_SOURCE_ID'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!args.destinationId) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Destination ID is required',
|
||||
code: 'MISSING_DESTINATION_ID'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// Find tasks.json path if not provided
|
||||
let tasksPath = args.tasksJsonPath || args.file;
|
||||
if (!tasksPath) {
|
||||
if (!args.projectRoot) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Project root is required if tasksJsonPath is not provided',
|
||||
code: 'MISSING_PROJECT_ROOT'
|
||||
}
|
||||
};
|
||||
}
|
||||
tasksPath = findTasksJsonPath(args, log);
|
||||
}
|
||||
|
||||
// Enable silent mode to prevent console output during MCP operation
|
||||
enableSilentMode();
|
||||
|
||||
// Call the core moveTask function, always generate files
|
||||
const result = await moveTask(tasksPath, args.sourceId, args.destinationId, true);
|
||||
|
||||
// Restore console output
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
movedTask: result.movedTask,
|
||||
message: `Successfully moved task/subtask ${args.sourceId} to ${args.destinationId}`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Restore console output in case of error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Failed to move task: ${error.message}`);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: error.message,
|
||||
code: 'MOVE_TASK_ERROR'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -30,6 +30,7 @@ import { addDependencyDirect } from './direct-functions/add-dependency.js';
|
||||
import { removeTaskDirect } from './direct-functions/remove-task.js';
|
||||
import { initializeProjectDirect } from './direct-functions/initialize-project.js';
|
||||
import { modelsDirect } from './direct-functions/models.js';
|
||||
import { moveTaskDirect } from './direct-functions/move-task.js';
|
||||
|
||||
// Re-export utility functions
|
||||
export { findTasksJsonPath } from './utils/path-utils.js';
|
||||
@@ -60,7 +61,8 @@ export const directFunctions = new Map([
|
||||
['addDependencyDirect', addDependencyDirect],
|
||||
['removeTaskDirect', removeTaskDirect],
|
||||
['initializeProjectDirect', initializeProjectDirect],
|
||||
['modelsDirect', modelsDirect]
|
||||
['modelsDirect', modelsDirect],
|
||||
['moveTaskDirect', moveTaskDirect]
|
||||
]);
|
||||
|
||||
// Re-export all direct function implementations
|
||||
@@ -89,5 +91,6 @@ export {
|
||||
addDependencyDirect,
|
||||
removeTaskDirect,
|
||||
initializeProjectDirect,
|
||||
modelsDirect
|
||||
modelsDirect,
|
||||
moveTaskDirect
|
||||
};
|
||||
|
||||
@@ -28,6 +28,7 @@ import { registerAddDependencyTool } from './add-dependency.js';
|
||||
import { registerRemoveTaskTool } from './remove-task.js';
|
||||
import { registerInitializeProjectTool } from './initialize-project.js';
|
||||
import { registerModelsTool } from './models.js';
|
||||
import { registerMoveTaskTool } from './move-task.js';
|
||||
|
||||
/**
|
||||
* Register all Task Master tools with the MCP server
|
||||
@@ -61,6 +62,7 @@ export function registerTaskMasterTools(server) {
|
||||
registerRemoveTaskTool(server);
|
||||
registerRemoveSubtaskTool(server);
|
||||
registerClearSubtasksTool(server);
|
||||
registerMoveTaskTool(server);
|
||||
|
||||
// Group 5: Task Analysis & Expansion
|
||||
registerAnalyzeProjectComplexityTool(server);
|
||||
|
||||
124
mcp-server/src/tools/move-task.js
Normal file
124
mcp-server/src/tools/move-task.js
Normal file
@@ -0,0 +1,124 @@
|
||||
/**
|
||||
* tools/move-task.js
|
||||
* Tool for moving tasks or subtasks to a new position
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
handleApiResult,
|
||||
createErrorResponse,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { moveTaskDirect } from '../core/task-master-core.js';
|
||||
import { findTasksJsonPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the moveTask tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerMoveTaskTool(server) {
|
||||
server.addTool({
|
||||
name: 'move_task',
|
||||
description: 'Move a task or subtask to a new position',
|
||||
parameters: z.object({
|
||||
from: z.string().describe(
|
||||
'ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated to move multiple tasks (e.g., "5,6,7")'
|
||||
),
|
||||
to: z.string().describe(
|
||||
'ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated'
|
||||
),
|
||||
file: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Custom path to tasks.json file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Root directory of the project (typically derived from session)')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
// Find tasks.json path if not provided
|
||||
let tasksJsonPath = args.file;
|
||||
|
||||
if (!tasksJsonPath) {
|
||||
tasksJsonPath = findTasksJsonPath(args, log);
|
||||
}
|
||||
|
||||
// Parse comma-separated IDs
|
||||
const fromIds = args.from.split(',').map(id => id.trim());
|
||||
const toIds = args.to.split(',').map(id => id.trim());
|
||||
|
||||
// Validate matching IDs count
|
||||
if (fromIds.length !== toIds.length) {
|
||||
return createErrorResponse(
|
||||
'The number of source and destination IDs must match',
|
||||
'MISMATCHED_ID_COUNT'
|
||||
);
|
||||
}
|
||||
|
||||
// If moving multiple tasks
|
||||
if (fromIds.length > 1) {
|
||||
const results = [];
|
||||
// Move tasks one by one, only generate files on the last move
|
||||
for (let i = 0; i < fromIds.length; i++) {
|
||||
const fromId = fromIds[i];
|
||||
const toId = toIds[i];
|
||||
|
||||
// Skip if source and destination are the same
|
||||
if (fromId === toId) {
|
||||
log.info(`Skipping ${fromId} -> ${toId} (same ID)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const shouldGenerateFiles = i === fromIds.length - 1;
|
||||
const result = await moveTaskDirect(
|
||||
{
|
||||
sourceId: fromId,
|
||||
destinationId: toId,
|
||||
tasksJsonPath,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
log.error(`Failed to move ${fromId} to ${toId}: ${result.error.message}`);
|
||||
} else {
|
||||
results.push(result.data);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
moves: results,
|
||||
message: `Successfully moved ${results.length} tasks`
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Moving a single task
|
||||
return handleApiResult(
|
||||
await moveTaskDirect(
|
||||
{
|
||||
sourceId: args.from,
|
||||
destinationId: args.to,
|
||||
tasksJsonPath,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
),
|
||||
log
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return createErrorResponse(
|
||||
`Failed to move task: ${error.message}`,
|
||||
'MOVE_TASK_ERROR'
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -30,7 +30,8 @@ import {
|
||||
updateSubtaskById,
|
||||
removeTask,
|
||||
findTaskById,
|
||||
taskExists
|
||||
taskExists,
|
||||
moveTask
|
||||
} from './task-manager.js';
|
||||
|
||||
import {
|
||||
@@ -1043,6 +1044,8 @@ function registerCommands(programInstance) {
|
||||
// set-status command
|
||||
programInstance
|
||||
.command('set-status')
|
||||
.alias('mark')
|
||||
.alias('set')
|
||||
.description('Set the status of a task')
|
||||
.option(
|
||||
'-i, --id <id>',
|
||||
@@ -2381,6 +2384,109 @@ Examples:
|
||||
return; // Stop execution here
|
||||
});
|
||||
|
||||
// move-task command
|
||||
programInstance
|
||||
.command('move')
|
||||
.description('Move a task or subtask to a new position')
|
||||
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
|
||||
.option('--from <id>', 'ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated to move multiple tasks (e.g., "5,6,7")')
|
||||
.option('--to <id>', 'ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated')
|
||||
.action(async (options) => {
|
||||
const tasksPath = options.file;
|
||||
const sourceId = options.from;
|
||||
const destinationId = options.to;
|
||||
|
||||
if (!sourceId || !destinationId) {
|
||||
console.error(
|
||||
chalk.red('Error: Both --from and --to parameters are required')
|
||||
);
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Usage: task-master move --from=<sourceId> --to=<destinationId>'
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Check if we're moving multiple tasks (comma-separated IDs)
|
||||
const sourceIds = sourceId.split(',').map(id => id.trim());
|
||||
const destinationIds = destinationId.split(',').map(id => id.trim());
|
||||
|
||||
// Validate that the number of source and destination IDs match
|
||||
if (sourceIds.length !== destinationIds.length) {
|
||||
console.error(
|
||||
chalk.red('Error: The number of source and destination IDs must match')
|
||||
);
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Example: task-master move --from=5,6,7 --to=10,11,12'
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// If moving multiple tasks
|
||||
if (sourceIds.length > 1) {
|
||||
console.log(
|
||||
chalk.blue(`Moving multiple tasks: ${sourceIds.join(', ')} to ${destinationIds.join(', ')}...`)
|
||||
);
|
||||
|
||||
try {
|
||||
// Read tasks data once to validate destination IDs
|
||||
const tasksData = readJSON(tasksPath);
|
||||
if (!tasksData || !tasksData.tasks) {
|
||||
console.error(chalk.red(`Error: Invalid or missing tasks file at ${tasksPath}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Move tasks one by one
|
||||
for (let i = 0; i < sourceIds.length; i++) {
|
||||
const fromId = sourceIds[i];
|
||||
const toId = destinationIds[i];
|
||||
|
||||
// Skip if source and destination are the same
|
||||
if (fromId === toId) {
|
||||
console.log(chalk.yellow(`Skipping ${fromId} -> ${toId} (same ID)`));
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(chalk.blue(`Moving task/subtask ${fromId} to ${toId}...`));
|
||||
try {
|
||||
await moveTask(tasksPath, fromId, toId, i === sourceIds.length - 1);
|
||||
console.log(
|
||||
chalk.green(
|
||||
`✓ Successfully moved task/subtask ${fromId} to ${toId}`
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error moving ${fromId} to ${toId}: ${error.message}`));
|
||||
// Continue with the next task rather than exiting
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
// Moving a single task (existing logic)
|
||||
console.log(
|
||||
chalk.blue(`Moving task/subtask ${sourceId} to ${destinationId}...`)
|
||||
);
|
||||
|
||||
try {
|
||||
const result = await moveTask(tasksPath, sourceId, destinationId, true);
|
||||
console.log(
|
||||
chalk.green(
|
||||
`✓ Successfully moved task/subtask ${sourceId} to ${destinationId}`
|
||||
)
|
||||
);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Error: ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return programInstance;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@ import updateSubtaskById from './task-manager/update-subtask-by-id.js';
|
||||
import removeTask from './task-manager/remove-task.js';
|
||||
import taskExists from './task-manager/task-exists.js';
|
||||
import isTaskDependentOn from './task-manager/is-task-dependent.js';
|
||||
import moveTask from './task-manager/move-task.js';
|
||||
import { readComplexityReport } from './utils.js';
|
||||
// Export task manager functions
|
||||
export {
|
||||
@@ -46,5 +47,6 @@ export {
|
||||
findTaskById,
|
||||
taskExists,
|
||||
isTaskDependentOn,
|
||||
moveTask,
|
||||
readComplexityReport
|
||||
};
|
||||
|
||||
@@ -35,6 +35,47 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
|
||||
log('info', `Validating and fixing dependencies`);
|
||||
validateAndFixDependencies(data, tasksPath);
|
||||
|
||||
// Get valid task IDs from tasks.json
|
||||
const validTaskIds = data.tasks.map(task => task.id);
|
||||
|
||||
// Cleanup orphaned task files
|
||||
log('info', 'Checking for orphaned task files to clean up...');
|
||||
try {
|
||||
// Get all task files in the output directory
|
||||
const files = fs.readdirSync(outputDir);
|
||||
const taskFilePattern = /^task_(\d+)\.txt$/;
|
||||
|
||||
// Filter for task files and check if they match a valid task ID
|
||||
const orphanedFiles = files.filter(file => {
|
||||
const match = file.match(taskFilePattern);
|
||||
if (match) {
|
||||
const fileTaskId = parseInt(match[1], 10);
|
||||
return !validTaskIds.includes(fileTaskId);
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
// Delete orphaned files
|
||||
if (orphanedFiles.length > 0) {
|
||||
log('info', `Found ${orphanedFiles.length} orphaned task files to remove`);
|
||||
|
||||
orphanedFiles.forEach(file => {
|
||||
const filePath = path.join(outputDir, file);
|
||||
try {
|
||||
fs.unlinkSync(filePath);
|
||||
log('info', `Removed orphaned task file: ${file}`);
|
||||
} catch (err) {
|
||||
log('warn', `Failed to remove orphaned task file ${file}: ${err.message}`);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
log('info', 'No orphaned task files found');
|
||||
}
|
||||
} catch (err) {
|
||||
log('warn', `Error cleaning up orphaned task files: ${err.message}`);
|
||||
// Continue with file generation even if cleanup fails
|
||||
}
|
||||
|
||||
// Generate task files
|
||||
log('info', 'Generating individual task files...');
|
||||
data.tasks.forEach((task) => {
|
||||
|
||||
510
scripts/modules/task-manager/move-task.js
Normal file
510
scripts/modules/task-manager/move-task.js
Normal file
@@ -0,0 +1,510 @@
|
||||
import path from 'path';
|
||||
import { log, readJSON, writeJSON } from '../utils.js';
|
||||
import { isTaskDependentOn } from '../task-manager.js';
|
||||
import generateTaskFiles from './generate-task-files.js';
|
||||
|
||||
/**
|
||||
* Move a task or subtask to a new position
|
||||
* @param {string} tasksPath - Path to tasks.json file
|
||||
* @param {string} sourceId - ID of the task/subtask to move (e.g., '5' or '5.2')
|
||||
* @param {string} destinationId - ID of the destination (e.g., '7' or '7.3')
|
||||
* @param {boolean} generateFiles - Whether to regenerate task files after moving
|
||||
* @returns {Object} Result object with moved task details
|
||||
*/
|
||||
async function moveTask(
|
||||
tasksPath,
|
||||
sourceId,
|
||||
destinationId,
|
||||
generateFiles = true
|
||||
) {
|
||||
try {
|
||||
log('info', `Moving task/subtask ${sourceId} to ${destinationId}...`);
|
||||
|
||||
// Read the existing tasks
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
throw new Error(`Invalid or missing tasks file at ${tasksPath}`);
|
||||
}
|
||||
|
||||
// Parse source ID to determine if it's a task or subtask
|
||||
const isSourceSubtask = sourceId.includes('.');
|
||||
let sourceTask, sourceParentTask, sourceSubtask, sourceTaskIndex, sourceSubtaskIndex;
|
||||
|
||||
// Parse destination ID to determine the target
|
||||
const isDestinationSubtask = destinationId.includes('.');
|
||||
let destTask, destParentTask, destSubtask, destTaskIndex, destSubtaskIndex;
|
||||
|
||||
// Validate source exists
|
||||
if (isSourceSubtask) {
|
||||
// Source is a subtask
|
||||
const [parentIdStr, subtaskIdStr] = sourceId.split('.');
|
||||
const parentIdNum = parseInt(parentIdStr, 10);
|
||||
const subtaskIdNum = parseInt(subtaskIdStr, 10);
|
||||
|
||||
sourceParentTask = data.tasks.find(t => t.id === parentIdNum);
|
||||
if (!sourceParentTask) {
|
||||
throw new Error(`Source parent task with ID ${parentIdNum} not found`);
|
||||
}
|
||||
|
||||
if (!sourceParentTask.subtasks || sourceParentTask.subtasks.length === 0) {
|
||||
throw new Error(`Source parent task ${parentIdNum} has no subtasks`);
|
||||
}
|
||||
|
||||
sourceSubtaskIndex = sourceParentTask.subtasks.findIndex(
|
||||
st => st.id === subtaskIdNum
|
||||
);
|
||||
if (sourceSubtaskIndex === -1) {
|
||||
throw new Error(`Source subtask ${sourceId} not found`);
|
||||
}
|
||||
|
||||
sourceSubtask = { ...sourceParentTask.subtasks[sourceSubtaskIndex] };
|
||||
} else {
|
||||
// Source is a task
|
||||
const sourceIdNum = parseInt(sourceId, 10);
|
||||
sourceTaskIndex = data.tasks.findIndex(t => t.id === sourceIdNum);
|
||||
if (sourceTaskIndex === -1) {
|
||||
throw new Error(`Source task with ID ${sourceIdNum} not found`);
|
||||
}
|
||||
|
||||
sourceTask = { ...data.tasks[sourceTaskIndex] };
|
||||
}
|
||||
|
||||
// Validate destination exists
|
||||
if (isDestinationSubtask) {
|
||||
// Destination is a subtask (target will be the parent of this subtask)
|
||||
const [parentIdStr, subtaskIdStr] = destinationId.split('.');
|
||||
const parentIdNum = parseInt(parentIdStr, 10);
|
||||
const subtaskIdNum = parseInt(subtaskIdStr, 10);
|
||||
|
||||
destParentTask = data.tasks.find(t => t.id === parentIdNum);
|
||||
if (!destParentTask) {
|
||||
throw new Error(`Destination parent task with ID ${parentIdNum} not found`);
|
||||
}
|
||||
|
||||
if (!destParentTask.subtasks || destParentTask.subtasks.length === 0) {
|
||||
throw new Error(`Destination parent task ${parentIdNum} has no subtasks`);
|
||||
}
|
||||
|
||||
destSubtaskIndex = destParentTask.subtasks.findIndex(
|
||||
st => st.id === subtaskIdNum
|
||||
);
|
||||
if (destSubtaskIndex === -1) {
|
||||
throw new Error(`Destination subtask ${destinationId} not found`);
|
||||
}
|
||||
|
||||
destSubtask = destParentTask.subtasks[destSubtaskIndex];
|
||||
} else {
|
||||
// Destination is a task
|
||||
const destIdNum = parseInt(destinationId, 10);
|
||||
destTaskIndex = data.tasks.findIndex(t => t.id === destIdNum);
|
||||
|
||||
if (destTaskIndex === -1) {
|
||||
// Create placeholder for destination if it doesn't exist
|
||||
log('info', `Creating placeholder for destination task ${destIdNum}`);
|
||||
const newTask = {
|
||||
id: destIdNum,
|
||||
title: `Task ${destIdNum}`,
|
||||
description: '',
|
||||
status: 'pending',
|
||||
priority: 'medium',
|
||||
details: '',
|
||||
testStrategy: ''
|
||||
};
|
||||
|
||||
// Find correct position to insert the new task
|
||||
let insertIndex = 0;
|
||||
while (insertIndex < data.tasks.length && data.tasks[insertIndex].id < destIdNum) {
|
||||
insertIndex++;
|
||||
}
|
||||
|
||||
// Insert the new task at the appropriate position
|
||||
data.tasks.splice(insertIndex, 0, newTask);
|
||||
destTaskIndex = insertIndex;
|
||||
destTask = data.tasks[destTaskIndex];
|
||||
} else {
|
||||
destTask = data.tasks[destTaskIndex];
|
||||
|
||||
// Check if destination task is already a "real" task with content
|
||||
// Only allow moving to destination IDs that don't have meaningful content
|
||||
if (destTask.title !== `Task ${destTask.id}` ||
|
||||
destTask.description !== '' ||
|
||||
destTask.details !== '') {
|
||||
throw new Error(`Cannot move to task ID ${destIdNum} as it already contains content. Choose a different destination ID.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate that we aren't trying to move a task to itself
|
||||
if (sourceId === destinationId) {
|
||||
throw new Error('Cannot move a task/subtask to itself');
|
||||
}
|
||||
|
||||
// Prevent moving a parent to its own subtask
|
||||
if (!isSourceSubtask && isDestinationSubtask) {
|
||||
const destParentId = parseInt(destinationId.split('.')[0], 10);
|
||||
if (parseInt(sourceId, 10) === destParentId) {
|
||||
throw new Error('Cannot move a parent task to one of its own subtasks');
|
||||
}
|
||||
}
|
||||
|
||||
// Check for circular dependency when moving tasks
|
||||
if (!isSourceSubtask && !isDestinationSubtask) {
|
||||
const sourceIdNum = parseInt(sourceId, 10);
|
||||
const destIdNum = parseInt(destinationId, 10);
|
||||
|
||||
// Check if destination is dependent on source
|
||||
if (isTaskDependentOn(data.tasks, destTask, sourceIdNum)) {
|
||||
throw new Error(
|
||||
`Cannot move task ${sourceId} to task ${destinationId} as it would create a circular dependency`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let movedTask;
|
||||
|
||||
// Handle different move scenarios
|
||||
if (!isSourceSubtask && !isDestinationSubtask) {
|
||||
// Check if destination is a placeholder we just created
|
||||
if (destTask.title === `Task ${destTask.id}` &&
|
||||
destTask.description === '' &&
|
||||
destTask.details === '') {
|
||||
// Case 0: Move task to a new position/ID (destination is a placeholder)
|
||||
movedTask = moveTaskToNewId(data, sourceTask, sourceTaskIndex, destTask, destTaskIndex);
|
||||
} else {
|
||||
// Case 1: Move standalone task to become a subtask of another task
|
||||
movedTask = moveTaskToTask(data, sourceTask, sourceTaskIndex, destTask);
|
||||
}
|
||||
} else if (!isSourceSubtask && isDestinationSubtask) {
|
||||
// Case 2: Move standalone task to become a subtask at a specific position
|
||||
movedTask = moveTaskToSubtaskPosition(data, sourceTask, sourceTaskIndex, destParentTask, destSubtaskIndex);
|
||||
} else if (isSourceSubtask && !isDestinationSubtask) {
|
||||
// Case 3: Move subtask to become a standalone task
|
||||
movedTask = moveSubtaskToTask(data, sourceSubtask, sourceParentTask, sourceSubtaskIndex, destTask);
|
||||
} else if (isSourceSubtask && isDestinationSubtask) {
|
||||
// Case 4: Move subtask to another parent or position
|
||||
// First check if it's the same parent
|
||||
const sourceParentId = parseInt(sourceId.split('.')[0], 10);
|
||||
const destParentId = parseInt(destinationId.split('.')[0], 10);
|
||||
|
||||
if (sourceParentId === destParentId) {
|
||||
// Case 4a: Move subtask within the same parent (reordering)
|
||||
movedTask = reorderSubtask(sourceParentTask, sourceSubtaskIndex, destSubtaskIndex);
|
||||
} else {
|
||||
// Case 4b: Move subtask to a different parent
|
||||
movedTask = moveSubtaskToAnotherParent(
|
||||
sourceSubtask,
|
||||
sourceParentTask,
|
||||
sourceSubtaskIndex,
|
||||
destParentTask,
|
||||
destSubtaskIndex
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Write the updated tasks back to the file
|
||||
writeJSON(tasksPath, data);
|
||||
|
||||
// Generate task files if requested
|
||||
if (generateFiles) {
|
||||
log('info', 'Regenerating task files...');
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
||||
}
|
||||
|
||||
return movedTask;
|
||||
} catch (error) {
|
||||
log('error', `Error moving task/subtask: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a standalone task to become a subtask of another task
|
||||
* @param {Object} data - Tasks data object
|
||||
* @param {Object} sourceTask - Source task to move
|
||||
* @param {number} sourceTaskIndex - Index of source task in data.tasks
|
||||
* @param {Object} destTask - Destination task
|
||||
* @returns {Object} Moved task object
|
||||
*/
|
||||
function moveTaskToTask(data, sourceTask, sourceTaskIndex, destTask) {
|
||||
// Initialize subtasks array if it doesn't exist
|
||||
if (!destTask.subtasks) {
|
||||
destTask.subtasks = [];
|
||||
}
|
||||
|
||||
// Find the highest subtask ID to determine the next ID
|
||||
const highestSubtaskId =
|
||||
destTask.subtasks.length > 0
|
||||
? Math.max(...destTask.subtasks.map(st => st.id))
|
||||
: 0;
|
||||
const newSubtaskId = highestSubtaskId + 1;
|
||||
|
||||
// Create the new subtask from the source task
|
||||
const newSubtask = {
|
||||
...sourceTask,
|
||||
id: newSubtaskId,
|
||||
parentTaskId: destTask.id
|
||||
};
|
||||
|
||||
// Add to destination's subtasks
|
||||
destTask.subtasks.push(newSubtask);
|
||||
|
||||
// Remove the original task from the tasks array
|
||||
data.tasks.splice(sourceTaskIndex, 1);
|
||||
|
||||
log(
|
||||
'info',
|
||||
`Moved task ${sourceTask.id} to become subtask ${destTask.id}.${newSubtaskId}`
|
||||
);
|
||||
|
||||
return newSubtask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a standalone task to become a subtask at a specific position
|
||||
* @param {Object} data - Tasks data object
|
||||
* @param {Object} sourceTask - Source task to move
|
||||
* @param {number} sourceTaskIndex - Index of source task in data.tasks
|
||||
* @param {Object} destParentTask - Destination parent task
|
||||
* @param {number} destSubtaskIndex - Index of the subtask before which to insert
|
||||
* @returns {Object} Moved task object
|
||||
*/
|
||||
function moveTaskToSubtaskPosition(data, sourceTask, sourceTaskIndex, destParentTask, destSubtaskIndex) {
|
||||
// Initialize subtasks array if it doesn't exist
|
||||
if (!destParentTask.subtasks) {
|
||||
destParentTask.subtasks = [];
|
||||
}
|
||||
|
||||
// Find the highest subtask ID to determine the next ID
|
||||
const highestSubtaskId =
|
||||
destParentTask.subtasks.length > 0
|
||||
? Math.max(...destParentTask.subtasks.map(st => st.id))
|
||||
: 0;
|
||||
const newSubtaskId = highestSubtaskId + 1;
|
||||
|
||||
// Create the new subtask from the source task
|
||||
const newSubtask = {
|
||||
...sourceTask,
|
||||
id: newSubtaskId,
|
||||
parentTaskId: destParentTask.id
|
||||
};
|
||||
|
||||
// Insert at specific position
|
||||
destParentTask.subtasks.splice(destSubtaskIndex + 1, 0, newSubtask);
|
||||
|
||||
// Remove the original task from the tasks array
|
||||
data.tasks.splice(sourceTaskIndex, 1);
|
||||
|
||||
log(
|
||||
'info',
|
||||
`Moved task ${sourceTask.id} to become subtask ${destParentTask.id}.${newSubtaskId}`
|
||||
);
|
||||
|
||||
return newSubtask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a subtask to become a standalone task
|
||||
* @param {Object} data - Tasks data object
|
||||
* @param {Object} sourceSubtask - Source subtask to move
|
||||
* @param {Object} sourceParentTask - Parent task of the source subtask
|
||||
* @param {number} sourceSubtaskIndex - Index of source subtask in parent's subtasks
|
||||
* @param {Object} destTask - Destination task (for position reference)
|
||||
* @returns {Object} Moved task object
|
||||
*/
|
||||
function moveSubtaskToTask(data, sourceSubtask, sourceParentTask, sourceSubtaskIndex, destTask) {
|
||||
// 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
|
||||
const newTask = {
|
||||
...sourceSubtask,
|
||||
id: newTaskId,
|
||||
priority: sourceParentTask.priority || 'medium' // Inherit priority from parent
|
||||
};
|
||||
delete newTask.parentTaskId;
|
||||
|
||||
// Add the parent task as a dependency if not already present
|
||||
if (!newTask.dependencies) {
|
||||
newTask.dependencies = [];
|
||||
}
|
||||
if (!newTask.dependencies.includes(sourceParentTask.id)) {
|
||||
newTask.dependencies.push(sourceParentTask.id);
|
||||
}
|
||||
|
||||
// Find the destination index to insert the new task
|
||||
const destTaskIndex = data.tasks.findIndex(t => t.id === destTask.id);
|
||||
|
||||
// Insert the new task after the destination task
|
||||
data.tasks.splice(destTaskIndex + 1, 0, newTask);
|
||||
|
||||
// Remove the subtask from the parent
|
||||
sourceParentTask.subtasks.splice(sourceSubtaskIndex, 1);
|
||||
|
||||
// If parent has no more subtasks, remove the subtasks array
|
||||
if (sourceParentTask.subtasks.length === 0) {
|
||||
delete sourceParentTask.subtasks;
|
||||
}
|
||||
|
||||
log(
|
||||
'info',
|
||||
`Moved subtask ${sourceParentTask.id}.${sourceSubtask.id} to become task ${newTaskId}`
|
||||
);
|
||||
|
||||
return newTask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reorder a subtask within the same parent
|
||||
* @param {Object} parentTask - Parent task containing the subtask
|
||||
* @param {number} sourceIndex - Current index of the subtask
|
||||
* @param {number} destIndex - Destination index for the subtask
|
||||
* @returns {Object} Moved subtask object
|
||||
*/
|
||||
function reorderSubtask(parentTask, sourceIndex, destIndex) {
|
||||
// Get the subtask to move
|
||||
const subtask = parentTask.subtasks[sourceIndex];
|
||||
|
||||
// Remove the subtask from its current position
|
||||
parentTask.subtasks.splice(sourceIndex, 1);
|
||||
|
||||
// Insert the subtask at the new position
|
||||
// If destIndex was after sourceIndex, it's now one less because we removed an item
|
||||
const adjustedDestIndex = sourceIndex < destIndex ? destIndex - 1 : destIndex;
|
||||
parentTask.subtasks.splice(adjustedDestIndex, 0, subtask);
|
||||
|
||||
log(
|
||||
'info',
|
||||
`Reordered subtask ${parentTask.id}.${subtask.id} within parent task ${parentTask.id}`
|
||||
);
|
||||
|
||||
return subtask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a subtask to a different parent
|
||||
* @param {Object} sourceSubtask - Source subtask to move
|
||||
* @param {Object} sourceParentTask - Parent task of the source subtask
|
||||
* @param {number} sourceSubtaskIndex - Index of source subtask in parent's subtasks
|
||||
* @param {Object} destParentTask - Destination parent task
|
||||
* @param {number} destSubtaskIndex - Index of the subtask before which to insert
|
||||
* @returns {Object} Moved subtask object
|
||||
*/
|
||||
function moveSubtaskToAnotherParent(
|
||||
sourceSubtask,
|
||||
sourceParentTask,
|
||||
sourceSubtaskIndex,
|
||||
destParentTask,
|
||||
destSubtaskIndex
|
||||
) {
|
||||
// Find the highest subtask ID in the destination parent
|
||||
const highestSubtaskId =
|
||||
destParentTask.subtasks.length > 0
|
||||
? Math.max(...destParentTask.subtasks.map(st => st.id))
|
||||
: 0;
|
||||
const newSubtaskId = highestSubtaskId + 1;
|
||||
|
||||
// Create the new subtask with updated parent reference
|
||||
const newSubtask = {
|
||||
...sourceSubtask,
|
||||
id: newSubtaskId,
|
||||
parentTaskId: destParentTask.id
|
||||
};
|
||||
|
||||
// If the subtask depends on its original parent, keep that dependency
|
||||
if (!newSubtask.dependencies) {
|
||||
newSubtask.dependencies = [];
|
||||
}
|
||||
if (!newSubtask.dependencies.includes(sourceParentTask.id)) {
|
||||
newSubtask.dependencies.push(sourceParentTask.id);
|
||||
}
|
||||
|
||||
// Insert at the destination position
|
||||
destParentTask.subtasks.splice(destSubtaskIndex + 1, 0, newSubtask);
|
||||
|
||||
// Remove the subtask from the original parent
|
||||
sourceParentTask.subtasks.splice(sourceSubtaskIndex, 1);
|
||||
|
||||
// If original parent has no more subtasks, remove the subtasks array
|
||||
if (sourceParentTask.subtasks.length === 0) {
|
||||
delete sourceParentTask.subtasks;
|
||||
}
|
||||
|
||||
log(
|
||||
'info',
|
||||
`Moved subtask ${sourceParentTask.id}.${sourceSubtask.id} to become subtask ${destParentTask.id}.${newSubtaskId}`
|
||||
);
|
||||
|
||||
return newSubtask;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move a standalone task to a new ID position
|
||||
* @param {Object} data - Tasks data object
|
||||
* @param {Object} sourceTask - Source task to move
|
||||
* @param {number} sourceTaskIndex - Index of source task in data.tasks
|
||||
* @param {Object} destTask - Destination placeholder task
|
||||
* @param {number} destTaskIndex - Index of destination task in data.tasks
|
||||
* @returns {Object} Moved task object
|
||||
*/
|
||||
function moveTaskToNewId(data, sourceTask, sourceTaskIndex, destTask, destTaskIndex) {
|
||||
// Create a copy of the source task with the new ID
|
||||
const movedTask = {
|
||||
...sourceTask,
|
||||
id: destTask.id
|
||||
};
|
||||
|
||||
// Get numeric IDs for comparison
|
||||
const sourceIdNum = parseInt(sourceTask.id, 10);
|
||||
const destIdNum = parseInt(destTask.id, 10);
|
||||
|
||||
// Handle subtasks if present
|
||||
if (sourceTask.subtasks && sourceTask.subtasks.length > 0) {
|
||||
// Update subtasks to reference the new parent ID if needed
|
||||
movedTask.subtasks = sourceTask.subtasks.map(subtask => ({
|
||||
...subtask,
|
||||
parentTaskId: destIdNum
|
||||
}));
|
||||
}
|
||||
|
||||
// Update any dependencies in other tasks that referenced the old ID
|
||||
data.tasks.forEach(task => {
|
||||
if (task.dependencies && task.dependencies.includes(sourceIdNum)) {
|
||||
// Replace the old ID with the new ID
|
||||
const depIndex = task.dependencies.indexOf(sourceIdNum);
|
||||
task.dependencies[depIndex] = destIdNum;
|
||||
}
|
||||
|
||||
// Also check for subtask dependencies that might reference this task
|
||||
if (task.subtasks && task.subtasks.length > 0) {
|
||||
task.subtasks.forEach(subtask => {
|
||||
if (subtask.dependencies && subtask.dependencies.includes(sourceIdNum)) {
|
||||
const depIndex = subtask.dependencies.indexOf(sourceIdNum);
|
||||
subtask.dependencies[depIndex] = destIdNum;
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Remove the original task from its position
|
||||
data.tasks.splice(sourceTaskIndex, 1);
|
||||
|
||||
// If we're moving to a position after the original, adjust the destination index
|
||||
// since removing the original shifts everything down by 1
|
||||
const adjustedDestIndex = sourceTaskIndex < destTaskIndex ? destTaskIndex - 1 : destTaskIndex;
|
||||
|
||||
// Remove the placeholder destination task
|
||||
data.tasks.splice(adjustedDestIndex, 1);
|
||||
|
||||
// Insert the moved task at the destination position
|
||||
data.tasks.splice(adjustedDestIndex, 0, movedTask);
|
||||
|
||||
log(
|
||||
'info',
|
||||
`Moved task ${sourceIdNum} to new ID ${destIdNum}`
|
||||
);
|
||||
|
||||
return movedTask;
|
||||
}
|
||||
|
||||
export default moveTask;
|
||||
@@ -1,6 +1,6 @@
|
||||
# Task ID: 69
|
||||
# Title: Enhance Analyze Complexity for Specific Task IDs
|
||||
# Status: pending
|
||||
# Status: done
|
||||
# Dependencies: None
|
||||
# Priority: medium
|
||||
# Description: Modify the analyze-complexity feature (CLI and MCP) to allow analyzing only specified task IDs or ranges, and append/update results in the report.
|
||||
@@ -71,25 +71,25 @@ Implementation Plan:
|
||||
3. Verify report `meta` section is updated correctly on each run.
|
||||
|
||||
# Subtasks:
|
||||
## 1. Modify core complexity analysis logic [pending]
|
||||
## 1. Modify core complexity analysis logic [done]
|
||||
### Dependencies: None
|
||||
### Description: Update the core complexity analysis function to accept specific task IDs or ranges as input parameters
|
||||
### Details:
|
||||
Refactor the existing complexity analysis module to allow filtering by task IDs or ranges. This involves modifying the data processing pipeline to filter tasks before analysis, ensuring the complexity metrics are calculated only for the specified tasks while maintaining context awareness.
|
||||
|
||||
## 2. Update CLI interface for task-specific complexity analysis [pending]
|
||||
## 2. Update CLI interface for task-specific complexity analysis [done]
|
||||
### Dependencies: 69.1
|
||||
### Description: Extend the CLI to accept task IDs or ranges as parameters for the complexity analysis command
|
||||
### Details:
|
||||
Add new flags `--id/-i`, `--from/-f`, and `--to/-t` to the CLI that allow users to specify task IDs or ranges for targeted complexity analysis. Update the command parser, help documentation, and ensure proper validation of the provided values.
|
||||
|
||||
## 3. Integrate task-specific analysis with MCP tool [pending]
|
||||
## 3. Integrate task-specific analysis with MCP tool [done]
|
||||
### Dependencies: 69.1
|
||||
### Description: Update the MCP tool interface to support analyzing complexity for specific tasks or ranges
|
||||
### Details:
|
||||
Modify the MCP tool's API endpoints and UI components to allow users to select specific tasks or ranges for complexity analysis. Ensure the UI provides clear feedback about which tasks are being analyzed and update the visualization components to properly display partial analysis results.
|
||||
|
||||
## 4. Create comprehensive tests for task-specific complexity analysis [pending]
|
||||
## 4. Create comprehensive tests for task-specific complexity analysis [done]
|
||||
### Dependencies: 69.1, 69.2, 69.3
|
||||
### Description: Develop test cases to verify the correct functioning of task-specific complexity analysis
|
||||
### Details:
|
||||
|
||||
@@ -1,11 +1,49 @@
|
||||
# Task ID: 91
|
||||
# Title: Implement Move Command for Tasks and Subtasks
|
||||
# Status: pending
|
||||
# Status: done
|
||||
# Dependencies: 1, 3
|
||||
# Priority: medium
|
||||
# Description: Introduce a 'move' command to enable moving tasks or subtasks to a different id, facilitating conflict resolution by allowing teams to assign new ids as needed.
|
||||
# Details:
|
||||
The move command should accept --id/-i and --to/-t flags, where --id specifies the task/subtask to be moved and --to indicates the new parent id for subtasks. Ensure that the command works seamlessly with both tasks and subtasks, allowing users to reassign them within the same project structure. The implementation should handle edge cases such as invalid ids, non-existent parents, and circular dependencies.
|
||||
The move command will consist of three core components: 1) Core Logic Function in scripts/modules/task-manager/move-task.js, 2) Direct Function Wrapper in mcp-server/src/core/direct-functions/move-task.js, and 3) MCP Tool in mcp-server/src/tools/move-task.js. The command will accept source and destination IDs, handling various scenarios including moving tasks to become subtasks, subtasks to become tasks, and subtasks between different parents. The implementation will handle edge cases such as invalid ids, non-existent parents, circular dependencies, and will properly update all dependencies.
|
||||
|
||||
# Test Strategy:
|
||||
Verify the move command by testing various scenarios including moving a task to a new id, moving a subtask under a different parent while preserving its hierarchy, handling errors for invalid or non-existent ids, and ensuring that the command does not disrupt existing task relationships.
|
||||
Testing will follow a three-tier approach: 1) Unit tests for core functionality including moving tasks to subtasks, subtasks to tasks, subtasks between parents, dependency handling, and validation error cases; 2) Integration tests for the direct function with mock MCP environment and task file regeneration; 3) End-to-end tests for the full MCP tool call path. This will verify all scenarios including moving a task to a new id, moving a subtask under a different parent while preserving its hierarchy, and handling errors for invalid operations.
|
||||
|
||||
# Subtasks:
|
||||
## 1. Design and implement core move logic [done]
|
||||
### Dependencies: None
|
||||
### Description: Create the fundamental logic for moving tasks and subtasks within the task management system hierarchy
|
||||
### Details:
|
||||
Implement the core logic function in scripts/modules/task-manager/move-task.js with the signature that accepts tasksPath, sourceId, destinationId, and generateFiles parameters. Develop functions to handle all movement operations including task-to-subtask, subtask-to-task, and subtask-to-subtask conversions. Implement validation for source and destination IDs, and ensure proper updating of parent-child relationships and dependencies.
|
||||
|
||||
## 2. Implement edge case handling [done]
|
||||
### Dependencies: 91.1
|
||||
### Description: Develop robust error handling for all potential edge cases in the move operation
|
||||
### Details:
|
||||
Create validation functions to detect invalid task IDs, non-existent parent tasks, and circular dependencies. Handle special cases such as moving a task to become the first/last subtask, reordering within the same parent, preventing moving a task to itself, and preventing moving a parent to its own subtask. Implement proper error messages and status codes for each edge case, and ensure system stability if a move operation fails.
|
||||
|
||||
## 3. Update CLI interface for move commands [done]
|
||||
### Dependencies: 91.1
|
||||
### Description: Extend the command-line interface to support the new move functionality with appropriate flags and options
|
||||
### Details:
|
||||
Create the Direct Function Wrapper in mcp-server/src/core/direct-functions/move-task.js to adapt the core logic for MCP, handling path resolution and parameter validation. Implement silent mode to prevent console output interfering with JSON responses. Create the MCP Tool in mcp-server/src/tools/move-task.js that exposes the functionality to Cursor, handles project root resolution, and includes proper Zod parameter definitions. Update MCP tool definition in .cursor/mcp.json and register the tool in mcp-server/src/tools/index.js.
|
||||
|
||||
## 4. Ensure data integrity during moves [done]
|
||||
### Dependencies: 91.1, 91.2
|
||||
### Description: Implement safeguards to maintain data consistency and update all relationships during move operations
|
||||
### Details:
|
||||
Implement dependency handling logic to update dependencies when converting between task/subtask, add appropriate parent dependencies when needed, and validate no circular dependencies are created. Create transaction-like operations to ensure atomic moves that either complete fully or roll back. Implement functions to update all affected task relationships after a move, and add verification steps to confirm data integrity post-move.
|
||||
|
||||
## 5. Create comprehensive test suite [done]
|
||||
### Dependencies: 91.1, 91.2, 91.3, 91.4
|
||||
### Description: Develop and execute tests covering all move scenarios and edge cases
|
||||
### Details:
|
||||
Create unit tests for core functionality including moving tasks to subtasks, subtasks to tasks, subtasks between parents, dependency handling, and validation error cases. Implement integration tests for the direct function with mock MCP environment and task file regeneration. Develop end-to-end tests for the full MCP tool call path. Ensure tests cover all identified edge cases and potential failure points, and verify data integrity after moves.
|
||||
|
||||
## 6. Export and integrate the move function [done]
|
||||
### Dependencies: 91.1
|
||||
### Description: Ensure the move function is properly exported and integrated with existing code
|
||||
### Details:
|
||||
Export the move function in scripts/modules/task-manager.js. Update task-master-core.js to include the direct function. Reuse validation logic from add-subtask.js and remove-subtask.js where appropriate. Follow silent mode implementation pattern from other direct functions and match parameter naming conventions in MCP tools.
|
||||
|
||||
|
||||
@@ -4545,7 +4545,7 @@
|
||||
"id": 69,
|
||||
"title": "Enhance Analyze Complexity for Specific Task IDs",
|
||||
"description": "Modify the analyze-complexity feature (CLI and MCP) to allow analyzing only specified task IDs or ranges, and append/update results in the report.",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"dependencies": [],
|
||||
"priority": "medium",
|
||||
"details": "\nImplementation Plan:\n\n1. **Core Logic (`scripts/modules/task-manager/analyze-task-complexity.js`)**\n * Modify function signature to accept optional parameters: `options.ids` (string, comma-separated IDs) and range parameters `options.from` and `options.to`.\n * If `options.ids` is present:\n * Parse the `ids` string into an array of target IDs.\n * Filter `tasksData.tasks` to include only tasks matching the target IDs.\n * Handle cases where provided IDs don't exist in `tasks.json`.\n * If range parameters (`options.from` and `options.to`) are present:\n * Parse these values into integers.\n * Filter tasks within the specified ID range (inclusive).\n * If neither `options.ids` nor range parameters are present: Continue with existing logic (filtering by active status).\n * Maintain existing logic for skipping completed tasks.\n * **Report Handling:**\n * Before generating analysis, check if the `outputPath` report file exists.\n * If it exists:\n * Read the existing `complexityAnalysis` array.\n * Generate new analysis only for target tasks (filtered by ID or range).\n * Merge results: Remove entries from the existing array that match IDs analyzed in the current run, then append new analysis results to the array.\n * Update the `meta` section (`generatedAt`, `tasksAnalyzed`).\n * Write merged `complexityAnalysis` and updated `meta` back to report file.\n * If the report file doesn't exist: Create it as usual.\n * **Prompt Generation:** Ensure `generateInternalComplexityAnalysisPrompt` receives correctly filtered list of tasks.\n\n2. **CLI (`scripts/modules/commands.js`)**\n * Add new options to the `analyze-complexity` command:\n * `--id/-i <ids>`: \"Comma-separated list of specific task IDs to analyze\"\n * `--from/-f <startId>`: \"Start ID for range analysis (inclusive)\"\n * `--to/-t <endId>`: \"End ID for range analysis (inclusive)\"\n * In the `.action` handler:\n * Check if `options.id`, `options.from`, or `options.to` are provided.\n * If yes, pass appropriate values to the `analyzeTaskComplexity` core function via the `options` object.\n * Update user feedback messages to indicate specific task analysis.\n\n3. **MCP Tool (`mcp-server/src/tools/analyze.js`)**\n * Add new optional parameters to Zod schema for `analyze_project_complexity` tool:\n * `ids: z.string().optional().describe(\"Comma-separated list of task IDs to analyze specifically\")`\n * `from: z.number().optional().describe(\"Start ID for range analysis (inclusive)\")`\n * `to: z.number().optional().describe(\"End ID for range analysis (inclusive)\")`\n * In the `execute` method, pass `args.ids`, `args.from`, and `args.to` to the `analyzeTaskComplexityDirect` function within its `args` object.\n\n4. **Direct Function (`mcp-server/src/core/direct-functions/analyze-task-complexity.js`)**\n * Update function to receive `ids`, `from`, and `to` values within the `args` object.\n * Pass these values along to the core `analyzeTaskComplexity` function within its `options` object.\n\n5. **Documentation:** Update relevant rule files (`commands.mdc`, `taskmaster.mdc`) to reflect new `--id/-i`, `--from/-f`, and `--to/-t` options/parameters.",
|
||||
@@ -4557,7 +4557,7 @@
|
||||
"description": "Update the core complexity analysis function to accept specific task IDs or ranges as input parameters",
|
||||
"dependencies": [],
|
||||
"details": "Refactor the existing complexity analysis module to allow filtering by task IDs or ranges. This involves modifying the data processing pipeline to filter tasks before analysis, ensuring the complexity metrics are calculated only for the specified tasks while maintaining context awareness.",
|
||||
"status": "pending"
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
@@ -4567,7 +4567,7 @@
|
||||
1
|
||||
],
|
||||
"details": "Add new flags `--id/-i`, `--from/-f`, and `--to/-t` to the CLI that allow users to specify task IDs or ranges for targeted complexity analysis. Update the command parser, help documentation, and ensure proper validation of the provided values.",
|
||||
"status": "pending"
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
@@ -4577,7 +4577,7 @@
|
||||
1
|
||||
],
|
||||
"details": "Modify the MCP tool's API endpoints and UI components to allow users to select specific tasks or ranges for complexity analysis. Ensure the UI provides clear feedback about which tasks are being analyzed and update the visualization components to properly display partial analysis results.",
|
||||
"status": "pending"
|
||||
"status": "done"
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
@@ -4589,7 +4589,7 @@
|
||||
3
|
||||
],
|
||||
"details": "Create unit and integration tests that verify the task-specific complexity analysis works correctly across both CLI and MCP interfaces. Include tests for edge cases such as invalid task IDs, tasks with dependencies outside the selected set, and performance tests for large task sets.",
|
||||
"status": "pending"
|
||||
"status": "done"
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -5332,15 +5332,84 @@
|
||||
"id": 91,
|
||||
"title": "Implement Move Command for Tasks and Subtasks",
|
||||
"description": "Introduce a 'move' command to enable moving tasks or subtasks to a different id, facilitating conflict resolution by allowing teams to assign new ids as needed.",
|
||||
"details": "The move command should accept --id/-i and --to/-t flags, where --id specifies the task/subtask to be moved and --to indicates the new parent id for subtasks. Ensure that the command works seamlessly with both tasks and subtasks, allowing users to reassign them within the same project structure. The implementation should handle edge cases such as invalid ids, non-existent parents, and circular dependencies.",
|
||||
"testStrategy": "Verify the move command by testing various scenarios including moving a task to a new id, moving a subtask under a different parent while preserving its hierarchy, handling errors for invalid or non-existent ids, and ensuring that the command does not disrupt existing task relationships.",
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"dependencies": [
|
||||
1,
|
||||
3
|
||||
],
|
||||
"priority": "medium",
|
||||
"subtasks": []
|
||||
"details": "The move command will consist of three core components: 1) Core Logic Function in scripts/modules/task-manager/move-task.js, 2) Direct Function Wrapper in mcp-server/src/core/direct-functions/move-task.js, and 3) MCP Tool in mcp-server/src/tools/move-task.js. The command will accept source and destination IDs, handling various scenarios including moving tasks to become subtasks, subtasks to become tasks, and subtasks between different parents. The implementation will handle edge cases such as invalid ids, non-existent parents, circular dependencies, and will properly update all dependencies.",
|
||||
"testStrategy": "Testing will follow a three-tier approach: 1) Unit tests for core functionality including moving tasks to subtasks, subtasks to tasks, subtasks between parents, dependency handling, and validation error cases; 2) Integration tests for the direct function with mock MCP environment and task file regeneration; 3) End-to-end tests for the full MCP tool call path. This will verify all scenarios including moving a task to a new id, moving a subtask under a different parent while preserving its hierarchy, and handling errors for invalid operations.",
|
||||
"subtasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Design and implement core move logic",
|
||||
"description": "Create the fundamental logic for moving tasks and subtasks within the task management system hierarchy",
|
||||
"dependencies": [],
|
||||
"details": "Implement the core logic function in scripts/modules/task-manager/move-task.js with the signature that accepts tasksPath, sourceId, destinationId, and generateFiles parameters. Develop functions to handle all movement operations including task-to-subtask, subtask-to-task, and subtask-to-subtask conversions. Implement validation for source and destination IDs, and ensure proper updating of parent-child relationships and dependencies.",
|
||||
"status": "done",
|
||||
"parentTaskId": 91
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "Implement edge case handling",
|
||||
"description": "Develop robust error handling for all potential edge cases in the move operation",
|
||||
"dependencies": [
|
||||
1
|
||||
],
|
||||
"details": "Create validation functions to detect invalid task IDs, non-existent parent tasks, and circular dependencies. Handle special cases such as moving a task to become the first/last subtask, reordering within the same parent, preventing moving a task to itself, and preventing moving a parent to its own subtask. Implement proper error messages and status codes for each edge case, and ensure system stability if a move operation fails.",
|
||||
"status": "done",
|
||||
"parentTaskId": 91
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "Update CLI interface for move commands",
|
||||
"description": "Extend the command-line interface to support the new move functionality with appropriate flags and options",
|
||||
"dependencies": [
|
||||
1
|
||||
],
|
||||
"details": "Create the Direct Function Wrapper in mcp-server/src/core/direct-functions/move-task.js to adapt the core logic for MCP, handling path resolution and parameter validation. Implement silent mode to prevent console output interfering with JSON responses. Create the MCP Tool in mcp-server/src/tools/move-task.js that exposes the functionality to Cursor, handles project root resolution, and includes proper Zod parameter definitions. Update MCP tool definition in .cursor/mcp.json and register the tool in mcp-server/src/tools/index.js.",
|
||||
"status": "done",
|
||||
"parentTaskId": 91
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"title": "Ensure data integrity during moves",
|
||||
"description": "Implement safeguards to maintain data consistency and update all relationships during move operations",
|
||||
"dependencies": [
|
||||
1,
|
||||
2
|
||||
],
|
||||
"details": "Implement dependency handling logic to update dependencies when converting between task/subtask, add appropriate parent dependencies when needed, and validate no circular dependencies are created. Create transaction-like operations to ensure atomic moves that either complete fully or roll back. Implement functions to update all affected task relationships after a move, and add verification steps to confirm data integrity post-move.",
|
||||
"status": "done",
|
||||
"parentTaskId": 91
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"title": "Create comprehensive test suite",
|
||||
"description": "Develop and execute tests covering all move scenarios and edge cases",
|
||||
"dependencies": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4
|
||||
],
|
||||
"details": "Create unit tests for core functionality including moving tasks to subtasks, subtasks to tasks, subtasks between parents, dependency handling, and validation error cases. Implement integration tests for the direct function with mock MCP environment and task file regeneration. Develop end-to-end tests for the full MCP tool call path. Ensure tests cover all identified edge cases and potential failure points, and verify data integrity after moves.",
|
||||
"status": "done",
|
||||
"parentTaskId": 91
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"title": "Export and integrate the move function",
|
||||
"description": "Ensure the move function is properly exported and integrated with existing code",
|
||||
"dependencies": [
|
||||
1
|
||||
],
|
||||
"details": "Export the move function in scripts/modules/task-manager.js. Update task-master-core.js to include the direct function. Reuse validation logic from add-subtask.js and remove-subtask.js where appropriate. Follow silent mode implementation pattern from other direct functions and match parameter naming conventions in MCP tools.",
|
||||
"status": "done",
|
||||
"parentTaskId": 91
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user