feat: implement cross-tag task movement functionality (#1088)
* feat: enhance move command with cross-tag functionality - Updated the `move` command to allow moving tasks between different tags, including options for handling dependencies. - Added new options: `--from-tag`, `--to-tag`, `--with-dependencies`, `--ignore-dependencies`, and `--force`. - Implemented validation for cross-tag moves and dependency checks. - Introduced helper functions in the dependency manager for validating and resolving cross-tag dependencies. - Added integration and unit tests to cover new functionality and edge cases. * fix: refactor cross-tag move logic and enhance validation - Moved the import of `moveTasksBetweenTags` to the correct location in `commands.js` for better clarity. - Added new helper functions in `dependency-manager.js` to improve validation and error handling for cross-tag moves. - Enhanced existing functions to ensure proper handling of task dependencies and conflicts. - Updated tests to cover new validation scenarios and ensure robust error messaging for invalid task IDs and tags. * fix: improve task ID handling and error messaging in cross-tag moves - Refactored `moveTasksBetweenTags` to normalize task IDs for comparison, ensuring consistent handling of string and numeric IDs. - Enhanced error messages for cases where source and target tags are the same but no destination is specified. - Updated tests to validate new behavior, including handling string dependencies correctly during cross-tag moves. - Cleaned up existing code for better readability and maintainability. * test: add comprehensive tests for cross-tag move and dependency validation - Introduced new test files for `move-cross-tag` and `cross-tag-dependencies` to cover various scenarios in cross-tag task movement. - Implemented tests for handling task movement with and without dependencies, including edge cases for error handling. - Enhanced existing tests in `fix-dependencies-command` and `move-task` to ensure robust validation of task IDs and dependencies. - Mocked necessary modules and functions to isolate tests and improve reliability. - Ensured coverage for both successful and failed cross-tag move operations, validating expected outcomes and error messages. * test: refactor cross-tag move tests for better clarity and reusability - Introduced a helper function `simulateCrossTagMove` to streamline cross-tag move test cases, reducing redundancy and improving readability. - Updated existing tests to utilize the new helper function, ensuring consistent handling of expected messages and options. - Enhanced test coverage for various scenarios, including handling of dependencies and flags. * feat: add cross-tag task movement functionality - Introduced new commands for moving tasks between different tags, enhancing project organization capabilities. - Updated README with usage examples for cross-tag movement, including options for handling dependencies. - Created comprehensive documentation for cross-tag task movement, detailing usage, error handling, and best practices. - Implemented core logic for cross-tag moves, including validation for dependencies and error handling. - Added integration and unit tests to ensure robust functionality and coverage for various scenarios, including edge cases. * fix: enhance error handling and logging in cross-tag task movement - Improved logging in `moveTaskCrossTagDirect` to include detailed arguments for better traceability. - Refactored error handling to utilize structured error objects, providing clearer suggestions for resolving cross-tag dependency conflicts and subtask movement restrictions. - Updated documentation to reflect changes in error handling and provide clearer guidance on task movement options. - Added integration tests for cross-tag movement scenarios, ensuring robust validation of error handling and task movement logic. - Cleaned up existing tests for clarity and reusability, enhancing overall test coverage. * feat: enhance dependency resolution and error handling in task movement - Added recursive dependency resolution for tasks in `moveTasksBetweenTags`, improving handling of complex task relationships. - Introduced helper functions to find all dependencies and reverse dependencies, ensuring comprehensive coverage during task moves. - Enhanced error messages in `validateSubtaskMove` and `displaySubtaskMoveError` for better clarity on movement restrictions. - Updated tests to cover new functionality, including integration tests for complex cross-tag movement scenarios and edge cases. - Refactored existing code for improved readability and maintainability, ensuring consistent handling of task IDs and dependencies. * feat: unify dependency traversal and enhance task management utilities - Introduced `traverseDependencies` utility for unified forward and reverse dependency traversal, improving code reusability and clarity. - Refactored `findAllDependenciesRecursively` to leverage the new utility, streamlining dependency resolution in task management. - Added `formatTaskIdForDisplay` helper for better task ID formatting in UI, enhancing user experience during error displays. - Updated tests to cover new utility functions and ensure robust validation of dependency handling across various scenarios. - Improved overall code organization and readability, ensuring consistent handling of task dependencies and IDs. * fix: improve validation for dependency parameters in `findAllDependenciesRecursively` - Added checks to ensure `sourceTasks` and `allTasks` are arrays, throwing errors if not, to prevent runtime issues. - Updated documentation comment for clarity on the function's purpose and parameters. * fix: remove `force` option from task movement parameters - Eliminated the `force` parameter from the `moveTaskCrossTagDirect` function and related tools, simplifying the task movement logic. - Updated documentation and tests to reflect the removal of the `force` option, ensuring clarity and consistency across the codebase. - Adjusted related functions and tests to focus on `ignoreDependencies` as the primary control for handling dependency conflicts during task moves. * Add cross-tag task movement functionality - Introduced functionality for organizing tasks across different contexts by enabling cross-tag movement. - Added `formatTaskIdForDisplay` helper to improve task ID formatting in UI error messages. - Updated relevant tests to incorporate new functionality and ensure accurate error displays during task movements. * Update scripts/modules/dependency-manager.js Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * refactor(dependency-manager): Fix subtask resolution and extract helper functions 1. Fix subtask finding logic (lines 1315-1330): - Correctly locate parent task by numeric ID - Search within parent's subtasks array instead of top-level tasks - Properly handle relative subtask references 2. Extract helper functions from getDependentTaskIds (lines 1440-1636): - Move findTasksThatDependOn as module-level function - Move taskDependsOnSource as module-level function - Move subtasksDependOnSource as module-level function - Improves readability, maintainability, and testability Both fixes address architectural issues and improve code organization. * refactor(dependency-manager): Enhance subtask resolution and dependency validation - Improved subtask resolution logic to correctly find parent tasks and their subtasks, ensuring accurate identification of dependencies. - Filtered out null/undefined dependencies before processing, enhancing robustness in dependency checks. - Updated comments for clarity on the logic flow and purpose of changes, improving code maintainability. * refactor(move-task): clarify destination ID description and improve skipped task handling - Updated the description for the destination ID to clarify its usage in cross-tag moves. - Simplified the handling of skipped tasks during multiple task movements, improving readability and logging. - Enhanced the API result response to include detailed information about moved and skipped tasks, ensuring better feedback for users. * refactor(commands): remove redundant tag validation logic - Eliminated the check for identical source and target tags in the task movement logic, simplifying the code. - This change streamlines the flow for within-tag moves, enhancing readability and maintainability. * refactor(commands): enhance move command logic and error handling - Introduced helper functions for better organization of cross-tag and within-tag move logic, improving code readability and maintainability. - Enhanced error handling with structured error objects, providing clearer feedback for dependency conflicts and invalid tag combinations. - Updated move command help output to include best practices and error resolution tips, ensuring users have comprehensive guidance during task movements. - Streamlined task movement logic to handle multiple tasks more effectively, including detailed logging of successful and failed moves. * test(dependency-manager): add subtasks to task structure and mock dependency traversal - Updated `circular-dependencies.test.js` to include subtasks in task definitions, enhancing test coverage for task structures with nested dependencies. - Mocked `traverseDependencies` in `fix-dependencies-command.test.js` to ensure consistent behavior during tests, improving reliability of dependency-related tests. * refactor(dependency-manager): extract subtask finding logic into helper function - Added `findSubtaskInParent` function to encapsulate subtask resolution within a parent task's subtasks array, improving code organization and readability. - Updated `findDependencyTask` to utilize the new helper function, streamlining the logic for finding subtasks and enhancing maintainability. - Enhanced comments for clarity on the purpose and functionality of the new subtask finding logic. * refactor(ui): enhance subtask ID validation and improve error handling - Added validation for subtask ID format in `formatDependenciesWithStatus` and `taskExists`, ensuring proper handling of invalid formats. - Updated error logging in `displaySubtaskMoveError` to provide warnings for unexpected task ID formats, improving user feedback. - Converted hints to a Set in `displayDependencyValidationHints` to ensure unique hints are displayed, enhancing clarity in the UI. * test(cli): remove redundant timing check in complex cross-tag scenarios - Eliminated the timing check for task completion within 5 seconds in `complex-cross-tag-scenarios.test.js`, streamlining the test logic. - This change focuses on verifying task success without unnecessary timing constraints, enhancing test clarity and maintainability. * test(integration): enhance task movement tests with mock file system - Added integration tests for moving tasks within the same tag and between different tags using the actual `moveTask` and `moveTasksBetweenTags` functions. - Implemented `mock-fs` to simulate file system interactions, improving test isolation and reliability. - Verified task movement success and ensured proper handling of subtasks and dependencies, enhancing overall test coverage for task management functionality. - Included error handling tests for missing tags and task IDs to ensure robustness in task movement operations. * test(unit): add comprehensive tests for moveTaskCrossTagDirect functionality - Introduced new test cases to verify mock functionality, ensuring that mocks for `findTasksPath` and `readJSON` are working as expected. - Added tests for parameter validation, error handling, and function call flow, including scenarios for missing project roots and identical source/target tags. - Enhanced coverage for ID parsing and move options, ensuring robust handling of various input conditions and improving overall test reliability. * test(integration): skip tests for dependency conflict handling and withDependencies option - Marked tests for handling dependency conflicts and the withDependencies option as skipped due to issues with the mock setup. - Added TODOs to address the mock-fs setup for complex dependency scenarios, ensuring future improvements in test reliability. * test(unit): expand cross-tag move command tests with comprehensive mocks - Added extensive mocks for various modules to enhance the testing of the cross-tag move functionality in `move-cross-tag.test.js`. - Implemented detailed test cases for handling cross-tag moves, including validation for missing parameters and identical source/target tags. - Improved error handling tests to ensure robust feedback for invalid operations, enhancing overall test reliability and coverage. * test(integration): add complex dependency scenarios to task movement tests - Introduced new integration tests for handling complex dependency scenarios in task movement, utilizing the actual `moveTasksBetweenTags` function. - Added tests for circular dependencies, nested dependency chains, and cross-tag dependency resolution, enhancing coverage and reliability. - Documented limitations of the mock-fs setup for complex scenarios and provided warnings in the test output to guide future improvements. - Skipped tests for dependency conflicts and the withDependencies option due to mock setup issues, with TODOs for resolution. * test(unit): refactor move-cross-tag tests with focused mock system - Simplified mocking in `move-cross-tag.test.js` by implementing a configuration-driven mock system, reducing the number of mocked modules from 20+ to 5 core functionalities. - Introduced a reusable mock factory to streamline the creation of mocks based on configuration, enhancing maintainability and clarity. - Added documentation for the new mock system, detailing usage examples and benefits, including reduced complexity and improved test focus. - Implemented tests to validate the mock configuration, ensuring flexibility in enabling/disabling specific mocks. * test(unit): clean up mocks and improve isEmpty function in fix-dependencies-command tests - Removed the mock for `traverseDependencies` as it was unnecessary, simplifying the test setup. - Updated the `isEmpty` function to clarify its behavior regarding null and undefined values, enhancing code readability and maintainability. * test(unit): update traverseDependencies mock for consistency across tests - Standardized the mock implementation of `traverseDependencies` in both `fix-dependencies-command.test.js` and `complexity-report-tag-isolation.test.js` to accept `sourceTasks`, `allTasks`, and `options` parameters, ensuring uniformity in test setups. - This change enhances clarity and maintainability of the tests by aligning the mock behavior across different test files. * fix(core): improve task movement error handling and ID normalization - Wrapped task movement logic in a try-finally block to ensure console output is restored even on errors, enhancing reliability. - Normalized source IDs to handle mixed string/number comparisons, preventing potential issues in dependency checks. - Added tests for ID type consistency to verify that the normalization fix works correctly across various scenarios, improving test coverage and robustness. * refactor(task-manager): restructure task movement logic for improved validation and execution - Renamed and refactored `moveTasksBetweenTags` to streamline the task movement process into distinct phases: validation, data preparation, dependency resolution, execution, and finalization. - Introduced `validateMove`, `prepareTaskData`, `resolveDependencies`, `executeMoveOperation`, and `finalizeMove` functions to enhance modularity and clarity. - Updated documentation comments to reflect changes in function responsibilities and parameters. - Added comprehensive unit tests for the new structure, ensuring robust validation and error handling across various scenarios. - Improved handling of dependencies and task existence checks during the move operation, enhancing overall reliability. * fix(move-task): streamline task movement logic and improve error handling - Refactored the task movement process to enhance clarity and maintainability by replacing `forEach` with a `for...of` loop for better async handling. - Consolidated error handling and result logging to ensure consistent feedback during task moves. - Updated the logic for generating files only on the last move, improving performance and reducing unnecessary operations. - Enhanced validation for skipped tasks, ensuring accurate reporting of moved and skipped tasks in the final result. * fix(docs): update error message formatting and enhance clarity in task movement documentation - Changed code block syntax from generic to `text` for better readability in error messages related to task movement and dependency conflicts. - Ensured consistent formatting across all error message examples to improve user understanding of task movement restrictions and resolutions. - Added a newline at the end of the file for proper formatting. * Update .changeset/crazy-meals-hope.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * chore: improve changeset * chore: improve changeset * fix referenced bug in docs and remove docs * chore: fix format --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com>
This commit is contained in:
203
mcp-server/src/core/direct-functions/move-task-cross-tag.js
Normal file
203
mcp-server/src/core/direct-functions/move-task-cross-tag.js
Normal file
@@ -0,0 +1,203 @@
|
||||
/**
|
||||
* Direct function wrapper for cross-tag task moves
|
||||
*/
|
||||
|
||||
import { moveTasksBetweenTags } from '../../../../scripts/modules/task-manager/move-task.js';
|
||||
import { findTasksPath } from '../utils/path-utils.js';
|
||||
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
|
||||
/**
|
||||
* Move tasks between tags
|
||||
* @param {Object} args - Function arguments
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file
|
||||
* @param {string} args.sourceIds - Comma-separated IDs of tasks to move
|
||||
* @param {string} args.sourceTag - Source tag name
|
||||
* @param {string} args.targetTag - Target tag name
|
||||
* @param {boolean} args.withDependencies - Move dependent tasks along with main task
|
||||
* @param {boolean} args.ignoreDependencies - Break cross-tag dependencies during move
|
||||
* @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 moveTaskCrossTagDirect(args, log, context = {}) {
|
||||
const { session } = context;
|
||||
const { projectRoot } = args;
|
||||
|
||||
log.info(`moveTaskCrossTagDirect called with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Validate required parameters
|
||||
if (!args.sourceIds) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Source IDs are required',
|
||||
code: 'MISSING_SOURCE_IDS'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!args.sourceTag) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Source tag is required for cross-tag moves',
|
||||
code: 'MISSING_SOURCE_TAG'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!args.targetTag) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Target tag is required for cross-tag moves',
|
||||
code: 'MISSING_TARGET_TAG'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Validate that source and target tags are different
|
||||
if (args.sourceTag === args.targetTag) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: `Source and target tags are the same ("${args.sourceTag}")`,
|
||||
code: 'SAME_SOURCE_TARGET_TAG',
|
||||
suggestions: [
|
||||
'Use different tags for cross-tag moves',
|
||||
'Use within-tag move: task-master move --from=<id> --to=<id> --tag=<tag>',
|
||||
'Check available tags: task-master tags'
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
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 = findTasksPath(args, log);
|
||||
}
|
||||
|
||||
// Enable silent mode to prevent console output during MCP operation
|
||||
enableSilentMode();
|
||||
|
||||
try {
|
||||
// Parse source IDs
|
||||
const sourceIds = args.sourceIds.split(',').map((id) => id.trim());
|
||||
|
||||
// Prepare move options
|
||||
const moveOptions = {
|
||||
withDependencies: args.withDependencies || false,
|
||||
ignoreDependencies: args.ignoreDependencies || false
|
||||
};
|
||||
|
||||
// Call the core moveTasksBetweenTags function
|
||||
const result = await moveTasksBetweenTags(
|
||||
tasksPath,
|
||||
sourceIds,
|
||||
args.sourceTag,
|
||||
args.targetTag,
|
||||
moveOptions,
|
||||
{ projectRoot }
|
||||
);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
...result,
|
||||
message: `Successfully moved ${sourceIds.length} task(s) from "${args.sourceTag}" to "${args.targetTag}"`,
|
||||
moveOptions,
|
||||
sourceTag: args.sourceTag,
|
||||
targetTag: args.targetTag
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
// Restore console output - always executed regardless of success or error
|
||||
disableSilentMode();
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`Failed to move tasks between tags: ${error.message}`);
|
||||
log.error(`Error code: ${error.code}, Error name: ${error.name}`);
|
||||
|
||||
// Enhanced error handling with structured error objects
|
||||
let errorCode = 'MOVE_TASK_CROSS_TAG_ERROR';
|
||||
let suggestions = [];
|
||||
|
||||
// Handle structured errors first
|
||||
if (error.code === 'CROSS_TAG_DEPENDENCY_CONFLICTS') {
|
||||
errorCode = 'CROSS_TAG_DEPENDENCY_CONFLICT';
|
||||
suggestions = [
|
||||
'Use --with-dependencies to move dependent tasks together',
|
||||
'Use --ignore-dependencies to break cross-tag dependencies',
|
||||
'Run task-master validate-dependencies to check for issues',
|
||||
'Move dependencies first, then move the main task'
|
||||
];
|
||||
} else if (error.code === 'CANNOT_MOVE_SUBTASK') {
|
||||
errorCode = 'SUBTASK_MOVE_RESTRICTION';
|
||||
suggestions = [
|
||||
'Promote subtask to full task first: task-master remove-subtask --id=<subtaskId> --convert',
|
||||
'Move the parent task with all subtasks using --with-dependencies'
|
||||
];
|
||||
} else if (
|
||||
error.code === 'TASK_NOT_FOUND' ||
|
||||
error.code === 'INVALID_SOURCE_TAG' ||
|
||||
error.code === 'INVALID_TARGET_TAG'
|
||||
) {
|
||||
errorCode = 'TAG_OR_TASK_NOT_FOUND';
|
||||
suggestions = [
|
||||
'Check available tags: task-master tags',
|
||||
'Verify task IDs exist: task-master list',
|
||||
'Check task details: task-master show <id>'
|
||||
];
|
||||
} else if (error.message.includes('cross-tag dependency conflicts')) {
|
||||
// Fallback for legacy error messages
|
||||
errorCode = 'CROSS_TAG_DEPENDENCY_CONFLICT';
|
||||
suggestions = [
|
||||
'Use --with-dependencies to move dependent tasks together',
|
||||
'Use --ignore-dependencies to break cross-tag dependencies',
|
||||
'Run task-master validate-dependencies to check for issues',
|
||||
'Move dependencies first, then move the main task'
|
||||
];
|
||||
} else if (error.message.includes('Cannot move subtask')) {
|
||||
// Fallback for legacy error messages
|
||||
errorCode = 'SUBTASK_MOVE_RESTRICTION';
|
||||
suggestions = [
|
||||
'Promote subtask to full task first: task-master remove-subtask --id=<subtaskId> --convert',
|
||||
'Move the parent task with all subtasks using --with-dependencies'
|
||||
];
|
||||
} else if (error.message.includes('not found')) {
|
||||
// Fallback for legacy error messages
|
||||
errorCode = 'TAG_OR_TASK_NOT_FOUND';
|
||||
suggestions = [
|
||||
'Check available tags: task-master tags',
|
||||
'Verify task IDs exist: task-master list',
|
||||
'Check task details: task-master show <id>'
|
||||
];
|
||||
}
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: error.message,
|
||||
code: errorCode,
|
||||
suggestions
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ 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';
|
||||
import { moveTaskCrossTagDirect } from './direct-functions/move-task-cross-tag.js';
|
||||
import { researchDirect } from './direct-functions/research.js';
|
||||
import { addTagDirect } from './direct-functions/add-tag.js';
|
||||
import { deleteTagDirect } from './direct-functions/delete-tag.js';
|
||||
@@ -72,6 +73,7 @@ export const directFunctions = new Map([
|
||||
['initializeProjectDirect', initializeProjectDirect],
|
||||
['modelsDirect', modelsDirect],
|
||||
['moveTaskDirect', moveTaskDirect],
|
||||
['moveTaskCrossTagDirect', moveTaskCrossTagDirect],
|
||||
['researchDirect', researchDirect],
|
||||
['addTagDirect', addTagDirect],
|
||||
['deleteTagDirect', deleteTagDirect],
|
||||
@@ -111,6 +113,7 @@ export {
|
||||
initializeProjectDirect,
|
||||
modelsDirect,
|
||||
moveTaskDirect,
|
||||
moveTaskCrossTagDirect,
|
||||
researchDirect,
|
||||
addTagDirect,
|
||||
deleteTagDirect,
|
||||
|
||||
@@ -9,7 +9,10 @@ import {
|
||||
createErrorResponse,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { moveTaskDirect } from '../core/task-master-core.js';
|
||||
import {
|
||||
moveTaskDirect,
|
||||
moveTaskCrossTagDirect
|
||||
} from '../core/task-master-core.js';
|
||||
import { findTasksPath } from '../core/utils/path-utils.js';
|
||||
import { resolveTag } from '../../../scripts/modules/utils.js';
|
||||
|
||||
@@ -29,8 +32,9 @@ export function registerMoveTaskTool(server) {
|
||||
),
|
||||
to: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated'
|
||||
'ID of the destination (e.g., "7" or "7.3"). Required for within-tag moves. For cross-tag moves, if omitted, task will be moved to the target tag maintaining its ID'
|
||||
),
|
||||
file: z.string().optional().describe('Custom path to tasks.json file'),
|
||||
projectRoot: z
|
||||
@@ -38,101 +42,180 @@ export function registerMoveTaskTool(server) {
|
||||
.describe(
|
||||
'Root directory of the project (typically derived from session)'
|
||||
),
|
||||
tag: z.string().optional().describe('Tag context to operate on')
|
||||
tag: z.string().optional().describe('Tag context to operate on'),
|
||||
fromTag: z.string().optional().describe('Source tag for cross-tag moves'),
|
||||
toTag: z.string().optional().describe('Target tag for cross-tag moves'),
|
||||
withDependencies: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Move dependent tasks along with main task'),
|
||||
ignoreDependencies: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Break cross-tag dependencies during move')
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
const resolvedTag = resolveTag({
|
||||
projectRoot: args.projectRoot,
|
||||
tag: args.tag
|
||||
});
|
||||
// Find tasks.json path if not provided
|
||||
let tasksJsonPath = args.file;
|
||||
// Check if this is a cross-tag move
|
||||
const isCrossTagMove =
|
||||
args.fromTag && args.toTag && args.fromTag !== args.toTag;
|
||||
|
||||
if (!tasksJsonPath) {
|
||||
tasksJsonPath = findTasksPath(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,
|
||||
tag: resolvedTag
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
if (isCrossTagMove) {
|
||||
// Cross-tag move logic
|
||||
if (!args.from) {
|
||||
return createErrorResponse(
|
||||
'Source IDs are required for cross-tag moves',
|
||||
'MISSING_SOURCE_IDS'
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
log.error(
|
||||
`Failed to move ${fromId} to ${toId}: ${result.error.message}`
|
||||
);
|
||||
} else {
|
||||
results.push(result.data);
|
||||
}
|
||||
}
|
||||
|
||||
// Warn if 'to' parameter is provided for cross-tag moves
|
||||
if (args.to) {
|
||||
log.warn(
|
||||
'The "to" parameter is not used for cross-tag moves and will be ignored. Tasks retain their original IDs in the target tag.'
|
||||
);
|
||||
}
|
||||
|
||||
// Find tasks.json path if not provided
|
||||
let tasksJsonPath = args.file;
|
||||
if (!tasksJsonPath) {
|
||||
tasksJsonPath = findTasksPath(args, log);
|
||||
}
|
||||
|
||||
// Use cross-tag move function
|
||||
return handleApiResult(
|
||||
{
|
||||
success: true,
|
||||
data: {
|
||||
moves: results,
|
||||
message: `Successfully moved ${results.length} tasks`
|
||||
}
|
||||
},
|
||||
log,
|
||||
'Error moving multiple tasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} else {
|
||||
// Moving a single task
|
||||
return handleApiResult(
|
||||
await moveTaskDirect(
|
||||
await moveTaskCrossTagDirect(
|
||||
{
|
||||
sourceId: args.from,
|
||||
destinationId: args.to,
|
||||
sourceIds: args.from,
|
||||
sourceTag: args.fromTag,
|
||||
targetTag: args.toTag,
|
||||
withDependencies: args.withDependencies || false,
|
||||
ignoreDependencies: args.ignoreDependencies || false,
|
||||
tasksJsonPath,
|
||||
projectRoot: args.projectRoot,
|
||||
tag: resolvedTag
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
),
|
||||
log,
|
||||
'Error moving task',
|
||||
'Error moving tasks between tags',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} else {
|
||||
// Within-tag move logic (existing functionality)
|
||||
if (!args.to) {
|
||||
return createErrorResponse(
|
||||
'Destination ID is required for within-tag moves',
|
||||
'MISSING_DESTINATION_ID'
|
||||
);
|
||||
}
|
||||
|
||||
const resolvedTag = resolveTag({
|
||||
projectRoot: args.projectRoot,
|
||||
tag: args.tag
|
||||
});
|
||||
|
||||
// Find tasks.json path if not provided
|
||||
let tasksJsonPath = args.file;
|
||||
if (!tasksJsonPath) {
|
||||
tasksJsonPath = findTasksPath(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) {
|
||||
if (fromIds.length > 1) {
|
||||
const results = [];
|
||||
const skipped = [];
|
||||
// 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)`);
|
||||
skipped.push({ fromId, toId, reason: 'same ID' });
|
||||
continue;
|
||||
}
|
||||
|
||||
const shouldGenerateFiles = i === fromIds.length - 1;
|
||||
const result = await moveTaskDirect(
|
||||
{
|
||||
sourceId: fromId,
|
||||
destinationId: toId,
|
||||
tasksJsonPath,
|
||||
projectRoot: args.projectRoot,
|
||||
tag: resolvedTag,
|
||||
generateFiles: shouldGenerateFiles
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
log.error(
|
||||
`Failed to move ${fromId} to ${toId}: ${result.error.message}`
|
||||
);
|
||||
} else {
|
||||
results.push(result.data);
|
||||
}
|
||||
}
|
||||
|
||||
return handleApiResult(
|
||||
{
|
||||
success: true,
|
||||
data: {
|
||||
moves: results,
|
||||
skipped: skipped.length > 0 ? skipped : undefined,
|
||||
message: `Successfully moved ${results.length} tasks${skipped.length > 0 ? `, skipped ${skipped.length}` : ''}`
|
||||
}
|
||||
},
|
||||
log,
|
||||
'Error moving multiple tasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
}
|
||||
return handleApiResult(
|
||||
{
|
||||
success: true,
|
||||
data: {
|
||||
moves: results,
|
||||
skippedMoves: skippedMoves,
|
||||
message: `Successfully moved ${results.length} tasks${skippedMoves.length > 0 ? `, skipped ${skippedMoves.length} moves` : ''}`
|
||||
}
|
||||
},
|
||||
log,
|
||||
'Error moving multiple tasks',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
} else {
|
||||
// Moving a single task
|
||||
return handleApiResult(
|
||||
await moveTaskDirect(
|
||||
{
|
||||
sourceId: args.from,
|
||||
destinationId: args.to,
|
||||
tasksJsonPath,
|
||||
projectRoot: args.projectRoot,
|
||||
tag: resolvedTag,
|
||||
generateFiles: true
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
),
|
||||
log,
|
||||
'Error moving task',
|
||||
undefined,
|
||||
args.projectRoot
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
return createErrorResponse(
|
||||
|
||||
Reference in New Issue
Block a user