From da317f2607ca34db1be78c19954996f634c40923 Mon Sep 17 00:00:00 2001 From: Ralph Khreish <35776126+Crunchyman-ralph@users.noreply.github.com> Date: Fri, 16 May 2025 15:47:01 +0200 Subject: [PATCH] fix: error handling of task status settings (#523) * fix: error handling of task status settings * fix: update import path --------- Co-authored-by: shenysun --- .changeset/sharp-dingos-melt.md | 5 +++ mcp-server/src/tools/set-task-status.js | 3 +- scripts/modules/commands.js | 16 +++++++++- .../modules/task-manager/set-task-status.js | 9 ++++++ .../task-manager/update-single-task-status.js | 7 ++++ scripts/modules/ui.js | 3 +- src/constants/task-status.js | 32 +++++++++++++++++++ tests/unit/task-manager.test.js | 20 ++++++++++++ 8 files changed, 92 insertions(+), 3 deletions(-) create mode 100644 .changeset/sharp-dingos-melt.md create mode 100644 src/constants/task-status.js diff --git a/.changeset/sharp-dingos-melt.md b/.changeset/sharp-dingos-melt.md new file mode 100644 index 00000000..4c2d9fd9 --- /dev/null +++ b/.changeset/sharp-dingos-melt.md @@ -0,0 +1,5 @@ +--- +'task-master-ai': patch +--- + +Fix the error handling of task status settings diff --git a/mcp-server/src/tools/set-task-status.js b/mcp-server/src/tools/set-task-status.js index d92b1b1c..04ae9052 100644 --- a/mcp-server/src/tools/set-task-status.js +++ b/mcp-server/src/tools/set-task-status.js @@ -11,6 +11,7 @@ import { } from './utils.js'; import { setTaskStatusDirect } from '../core/task-master-core.js'; import { findTasksJsonPath } from '../core/utils/path-utils.js'; +import { TASK_STATUS_OPTIONS } from '../../../src/constants/task-status.js'; /** * Register the setTaskStatus tool with the MCP server @@ -27,7 +28,7 @@ export function registerSetTaskStatusTool(server) { "Task ID or subtask ID (e.g., '15', '15.2'). Can be comma-separated to update multiple tasks/subtasks at once." ), status: z - .string() + .enum(TASK_STATUS_OPTIONS) .describe( "New status to set (e.g., 'pending', 'done', 'in-progress', 'review', 'deferred', 'cancelled'." ), diff --git a/scripts/modules/commands.js b/scripts/modules/commands.js index 78c9c9de..2ccc2412 100644 --- a/scripts/modules/commands.js +++ b/scripts/modules/commands.js @@ -73,6 +73,10 @@ import { getApiKeyStatusReport } from './task-manager/models.js'; import { findProjectRoot } from './utils.js'; +import { + isValidTaskStatus, + TASK_STATUS_OPTIONS +} from '../../src/constants/task-status.js'; import { getTaskMasterVersion } from '../../src/utils/getVersion.js'; /** * Runs the interactive setup process for model configuration. @@ -1033,7 +1037,7 @@ function registerCommands(programInstance) { ) .option( '-s, --status ', - 'New status (todo, in-progress, review, done)' + `New status (one of: ${TASK_STATUS_OPTIONS.join(', ')})` ) .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') .action(async (options) => { @@ -1046,6 +1050,16 @@ function registerCommands(programInstance) { process.exit(1); } + if (!isValidTaskStatus(status)) { + console.error( + chalk.red( + `Error: Invalid status value: ${status}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}` + ) + ); + + process.exit(1); + } + console.log( chalk.blue(`Setting status of task(s) ${taskId} to: ${status}`) ); diff --git a/scripts/modules/task-manager/set-task-status.js b/scripts/modules/task-manager/set-task-status.js index f8b5fc3e..9278fdff 100644 --- a/scripts/modules/task-manager/set-task-status.js +++ b/scripts/modules/task-manager/set-task-status.js @@ -8,6 +8,10 @@ import { validateTaskDependencies } from '../dependency-manager.js'; import { getDebugFlag } from '../config-manager.js'; import updateSingleTaskStatus from './update-single-task-status.js'; import generateTaskFiles from './generate-task-files.js'; +import { + isValidTaskStatus, + TASK_STATUS_OPTIONS +} from '../../../src/constants/task-status.js'; /** * Set the status of a task @@ -19,6 +23,11 @@ import generateTaskFiles from './generate-task-files.js'; */ async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) { try { + if (!isValidTaskStatus(newStatus)) { + throw new Error( + `Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}` + ); + } // Determine if we're in MCP mode by checking for mcpLog const isMcpMode = !!options?.mcpLog; diff --git a/scripts/modules/task-manager/update-single-task-status.js b/scripts/modules/task-manager/update-single-task-status.js index e9839e3a..b8b5d3a2 100644 --- a/scripts/modules/task-manager/update-single-task-status.js +++ b/scripts/modules/task-manager/update-single-task-status.js @@ -1,6 +1,7 @@ import chalk from 'chalk'; import { log } from '../utils.js'; +import { isValidTaskStatus } from '../../../src/constants/task-status.js'; /** * Update the status of a single task @@ -17,6 +18,12 @@ async function updateSingleTaskStatus( data, showUi = true ) { + if (!isValidTaskStatus(newStatus)) { + throw new Error( + `Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}` + ); + } + // Check if it's a subtask (e.g., "1.2") if (taskIdInput.includes('.')) { const [parentId, subtaskId] = taskIdInput diff --git a/scripts/modules/ui.js b/scripts/modules/ui.js index e6ea4c54..a88edc98 100644 --- a/scripts/modules/ui.js +++ b/scripts/modules/ui.js @@ -19,6 +19,7 @@ import { import fs from 'fs'; import { findNextTask, analyzeTaskComplexity } from './task-manager.js'; import { getProjectName, getDefaultSubtasks } from './config-manager.js'; +import { TASK_STATUS_OPTIONS } from '../../src/constants/task-status.js'; import { getTaskMasterVersion } from '../../src/utils/getVersion.js'; // Create a color gradient for the banner @@ -448,7 +449,7 @@ function displayHelp() { { name: 'set-status', args: '--id= --status=', - desc: 'Update task status (done, pending, etc.)' + desc: `Update task status (${TASK_STATUS_OPTIONS.join(', ')})` }, { name: 'update', diff --git a/src/constants/task-status.js b/src/constants/task-status.js new file mode 100644 index 00000000..ebad5a16 --- /dev/null +++ b/src/constants/task-status.js @@ -0,0 +1,32 @@ +/** + * @typedef {'pending' | 'done' | 'in-progress' | 'review' | 'deferred' | 'cancelled'} TaskStatus + */ + +/** + * Task status options list + * @type {TaskStatus[]} + * @description Defines possible task statuses: + * - pending: Task waiting to start + * - done: Task completed + * - in-progress: Task in progress + * - review: Task completed and waiting for review + * - deferred: Task postponed or paused + * - cancelled: Task cancelled and will not be completed + */ +export const TASK_STATUS_OPTIONS = [ + 'pending', + 'done', + 'in-progress', + 'review', + 'deferred', + 'cancelled' +]; + +/** + * Check if a given status is a valid task status + * @param {string} status - The status to check + * @returns {boolean} True if the status is valid, false otherwise + */ +export function isValidTaskStatus(status) { + return TASK_STATUS_OPTIONS.includes(status); +} diff --git a/tests/unit/task-manager.test.js b/tests/unit/task-manager.test.js index fcba1be3..ec4725e5 100644 --- a/tests/unit/task-manager.test.js +++ b/tests/unit/task-manager.test.js @@ -199,6 +199,12 @@ const testSetTaskStatus = (tasksData, taskIdInput, newStatus) => { // Simplified version of updateSingleTaskStatus for testing const testUpdateSingleTaskStatus = (tasksData, taskIdInput, newStatus) => { + if (!isValidTaskStatus(newStatus)) { + throw new Error( + `Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}` + ); + } + // Check if it's a subtask (e.g., "1.2") if (taskIdInput.includes('.')) { const [parentId, subtaskId] = taskIdInput @@ -329,6 +335,10 @@ const testAddTask = ( import * as taskManager from '../../scripts/modules/task-manager.js'; import { sampleClaudeResponse } from '../fixtures/sample-claude-response.js'; import { sampleTasks, emptySampleTasks } from '../fixtures/sample-tasks.js'; +import { + isValidTaskStatus, + TASK_STATUS_OPTIONS +} from '../../src/constants/task-status.js'; // Destructure the required functions for convenience const { findNextTask, generateTaskFiles, clearSubtasks, updateTaskById } = @@ -1165,6 +1175,16 @@ describe('Task Manager Module', () => { expect(testTasksData.tasks[1].status).toBe('done'); }); + test('should throw error for invalid status', async () => { + // Arrange + const testTasksData = JSON.parse(JSON.stringify(sampleTasks)); + + // Assert + expect(() => + testUpdateSingleTaskStatus(testTasksData, '2', 'Done') + ).toThrow(/Error: Invalid status value: Done./); + }); + test('should update subtask status', async () => { // Arrange const testTasksData = JSON.parse(JSON.stringify(sampleTasks));