Compare commits

...

4 Commits

Author SHA1 Message Date
Ralph Khreish
22ce57b221 chore: add changeset 2025-05-16 22:23:12 +02:00
Ralph Khreish
8a23ba9b2c chore: remove cached function 2025-05-16 22:19:45 +02:00
Ralph Khreish
0393e0fb14 fix: remove cache from list-tasks and next-task mcp calls 2025-05-16 21:45:56 +02:00
Ralph Khreish
da317f2607 fix: error handling of task status settings (#523)
* fix: error handling of task status settings

* fix: update import path

---------

Co-authored-by: shenysun <shenysun@163.com>
2025-05-16 15:47:01 +02:00
13 changed files with 116 additions and 55 deletions

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': patch
---
Fix the error handling of task status settings

View File

@@ -0,0 +1,7 @@
---
'task-master-ai': patch
---
Remove caching layer from MCP direct functions for task listing, next task, and complexity report
- Fixes issues users where having where they were getting stale data

View File

@@ -8,7 +8,6 @@ import {
enableSilentMode, enableSilentMode,
disableSilentMode disableSilentMode
} from '../../../../scripts/modules/utils.js'; } from '../../../../scripts/modules/utils.js';
import { getCachedOrExecute } from '../../tools/utils.js';
/** /**
* Direct function wrapper for displaying the complexity report with error handling and caching. * Direct function wrapper for displaying the complexity report with error handling and caching.
@@ -86,30 +85,20 @@ export async function complexityReportDirect(args, log) {
// Use the caching utility // Use the caching utility
try { try {
const result = await getCachedOrExecute({ const result = await coreActionFn();
cacheKey, log.info('complexityReportDirect completed');
actionFn: coreActionFn, return result;
log
});
log.info(
`complexityReportDirect completed. From cache: ${result.fromCache}`
);
return result; // Returns { success, data/error, fromCache }
} catch (error) { } catch (error) {
// Catch unexpected errors from getCachedOrExecute itself
// Ensure silent mode is disabled // Ensure silent mode is disabled
disableSilentMode(); disableSilentMode();
log.error( log.error(`Unexpected error during complexityReport: ${error.message}`);
`Unexpected error during getCachedOrExecute for complexityReport: ${error.message}`
);
return { return {
success: false, success: false,
error: { error: {
code: 'UNEXPECTED_ERROR', code: 'UNEXPECTED_ERROR',
message: error.message message: error.message
}, }
fromCache: false
}; };
} }
} catch (error) { } catch (error) {

View File

@@ -4,7 +4,6 @@
*/ */
import { listTasks } from '../../../../scripts/modules/task-manager.js'; import { listTasks } from '../../../../scripts/modules/task-manager.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import { import {
enableSilentMode, enableSilentMode,
disableSilentMode disableSilentMode
@@ -36,7 +35,6 @@ export async function listTasksDirect(args, log) {
// Use the explicit tasksJsonPath for cache key // Use the explicit tasksJsonPath for cache key
const statusFilter = status || 'all'; const statusFilter = status || 'all';
const withSubtasksFilter = withSubtasks || false; const withSubtasksFilter = withSubtasks || false;
const cacheKey = `listTasks:${tasksJsonPath}:${statusFilter}:${withSubtasksFilter}`;
// Define the action function to be executed on cache miss // Define the action function to be executed on cache miss
const coreListTasksAction = async () => { const coreListTasksAction = async () => {
@@ -88,25 +86,19 @@ export async function listTasksDirect(args, log) {
} }
}; };
// Use the caching utility
try { try {
const result = await getCachedOrExecute({ const result = await coreListTasksAction();
cacheKey, log.info('listTasksDirect completed');
actionFn: coreListTasksAction, return result;
log
});
log.info(`listTasksDirect completed. From cache: ${result.fromCache}`);
return result; // Returns { success, data/error, fromCache }
} catch (error) { } catch (error) {
// Catch unexpected errors from getCachedOrExecute itself (though unlikely) log.error(`Unexpected error during listTasks: ${error.message}`);
log.error(
`Unexpected error during getCachedOrExecute for listTasks: ${error.message}`
);
console.error(error.stack); console.error(error.stack);
return { return {
success: false, success: false,
error: { code: 'CACHE_UTIL_ERROR', message: error.message }, error: {
fromCache: false code: 'UNEXPECTED_ERROR',
message: error.message
}
}; };
} }
} }

View File

@@ -5,7 +5,6 @@
import { findNextTask } from '../../../../scripts/modules/task-manager.js'; import { findNextTask } from '../../../../scripts/modules/task-manager.js';
import { readJSON } from '../../../../scripts/modules/utils.js'; import { readJSON } from '../../../../scripts/modules/utils.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import { import {
enableSilentMode, enableSilentMode,
disableSilentMode disableSilentMode
@@ -35,9 +34,6 @@ export async function nextTaskDirect(args, log) {
}; };
} }
// Generate cache key using the provided task path
const cacheKey = `nextTask:${tasksJsonPath}`;
// Define the action function to be executed on cache miss // Define the action function to be executed on cache miss
const coreNextTaskAction = async () => { const coreNextTaskAction = async () => {
try { try {
@@ -118,18 +114,11 @@ export async function nextTaskDirect(args, log) {
// Use the caching utility // Use the caching utility
try { try {
const result = await getCachedOrExecute({ const result = await coreNextTaskAction();
cacheKey, log.info(`nextTaskDirect completed.`);
actionFn: coreNextTaskAction, return result;
log
});
log.info(`nextTaskDirect completed. From cache: ${result.fromCache}`);
return result; // Returns { success, data/error, fromCache }
} catch (error) { } catch (error) {
// Catch unexpected errors from getCachedOrExecute itself log.error(`Unexpected error during nextTask: ${error.message}`);
log.error(
`Unexpected error during getCachedOrExecute for nextTask: ${error.message}`
);
return { return {
success: false, success: false,
error: { error: {

View File

@@ -4,11 +4,6 @@
*/ */
import { findTaskById, readJSON } from '../../../../scripts/modules/utils.js'; import { findTaskById, readJSON } from '../../../../scripts/modules/utils.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
/** /**

View File

@@ -11,6 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { setTaskStatusDirect } from '../core/task-master-core.js'; import { setTaskStatusDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.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 * 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." "Task ID or subtask ID (e.g., '15', '15.2'). Can be comma-separated to update multiple tasks/subtasks at once."
), ),
status: z status: z
.string() .enum(TASK_STATUS_OPTIONS)
.describe( .describe(
"New status to set (e.g., 'pending', 'done', 'in-progress', 'review', 'deferred', 'cancelled'." "New status to set (e.g., 'pending', 'done', 'in-progress', 'review', 'deferred', 'cancelled'."
), ),

View File

@@ -73,6 +73,10 @@ import {
getApiKeyStatusReport getApiKeyStatusReport
} from './task-manager/models.js'; } from './task-manager/models.js';
import { findProjectRoot } from './utils.js'; import { findProjectRoot } from './utils.js';
import {
isValidTaskStatus,
TASK_STATUS_OPTIONS
} from '../../src/constants/task-status.js';
import { getTaskMasterVersion } from '../../src/utils/getVersion.js'; import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
/** /**
* Runs the interactive setup process for model configuration. * Runs the interactive setup process for model configuration.
@@ -1033,7 +1037,7 @@ function registerCommands(programInstance) {
) )
.option( .option(
'-s, --status <status>', '-s, --status <status>',
'New status (todo, in-progress, review, done)' `New status (one of: ${TASK_STATUS_OPTIONS.join(', ')})`
) )
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json') .option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.action(async (options) => { .action(async (options) => {
@@ -1046,6 +1050,16 @@ function registerCommands(programInstance) {
process.exit(1); 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( console.log(
chalk.blue(`Setting status of task(s) ${taskId} to: ${status}`) chalk.blue(`Setting status of task(s) ${taskId} to: ${status}`)
); );

View File

@@ -8,6 +8,10 @@ import { validateTaskDependencies } from '../dependency-manager.js';
import { getDebugFlag } from '../config-manager.js'; import { getDebugFlag } from '../config-manager.js';
import updateSingleTaskStatus from './update-single-task-status.js'; import updateSingleTaskStatus from './update-single-task-status.js';
import generateTaskFiles from './generate-task-files.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 * Set the status of a task
@@ -19,6 +23,11 @@ import generateTaskFiles from './generate-task-files.js';
*/ */
async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) { async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) {
try { 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 // Determine if we're in MCP mode by checking for mcpLog
const isMcpMode = !!options?.mcpLog; const isMcpMode = !!options?.mcpLog;

View File

@@ -1,6 +1,7 @@
import chalk from 'chalk'; import chalk from 'chalk';
import { log } from '../utils.js'; import { log } from '../utils.js';
import { isValidTaskStatus } from '../../../src/constants/task-status.js';
/** /**
* Update the status of a single task * Update the status of a single task
@@ -17,6 +18,12 @@ async function updateSingleTaskStatus(
data, data,
showUi = true 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") // Check if it's a subtask (e.g., "1.2")
if (taskIdInput.includes('.')) { if (taskIdInput.includes('.')) {
const [parentId, subtaskId] = taskIdInput const [parentId, subtaskId] = taskIdInput

View File

@@ -19,6 +19,7 @@ import {
import fs from 'fs'; import fs from 'fs';
import { findNextTask, analyzeTaskComplexity } from './task-manager.js'; import { findNextTask, analyzeTaskComplexity } from './task-manager.js';
import { getProjectName, getDefaultSubtasks } from './config-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'; import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
// Create a color gradient for the banner // Create a color gradient for the banner
@@ -448,7 +449,7 @@ function displayHelp() {
{ {
name: 'set-status', name: 'set-status',
args: '--id=<id> --status=<status>', args: '--id=<id> --status=<status>',
desc: 'Update task status (done, pending, etc.)' desc: `Update task status (${TASK_STATUS_OPTIONS.join(', ')})`
}, },
{ {
name: 'update', name: 'update',

View File

@@ -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);
}

View File

@@ -199,6 +199,12 @@ const testSetTaskStatus = (tasksData, taskIdInput, newStatus) => {
// Simplified version of updateSingleTaskStatus for testing // Simplified version of updateSingleTaskStatus for testing
const testUpdateSingleTaskStatus = (tasksData, taskIdInput, newStatus) => { 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") // Check if it's a subtask (e.g., "1.2")
if (taskIdInput.includes('.')) { if (taskIdInput.includes('.')) {
const [parentId, subtaskId] = taskIdInput const [parentId, subtaskId] = taskIdInput
@@ -329,6 +335,10 @@ const testAddTask = (
import * as taskManager from '../../scripts/modules/task-manager.js'; import * as taskManager from '../../scripts/modules/task-manager.js';
import { sampleClaudeResponse } from '../fixtures/sample-claude-response.js'; import { sampleClaudeResponse } from '../fixtures/sample-claude-response.js';
import { sampleTasks, emptySampleTasks } from '../fixtures/sample-tasks.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 // Destructure the required functions for convenience
const { findNextTask, generateTaskFiles, clearSubtasks, updateTaskById } = const { findNextTask, generateTaskFiles, clearSubtasks, updateTaskById } =
@@ -1165,6 +1175,16 @@ describe('Task Manager Module', () => {
expect(testTasksData.tasks[1].status).toBe('done'); 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 () => { test('should update subtask status', async () => {
// Arrange // Arrange
const testTasksData = JSON.parse(JSON.stringify(sampleTasks)); const testTasksData = JSON.parse(JSON.stringify(sampleTasks));