chore: changeset + update rules.

This commit is contained in:
Eyal Toledano
2025-04-03 04:09:27 -04:00
parent 1582fe32c1
commit bad16b200f
54 changed files with 1462 additions and 316 deletions

View File

@@ -5,6 +5,7 @@
import { addDependency } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/**
* Direct function wrapper for addDependency with error handling.
@@ -51,9 +52,15 @@ export async function addDependencyDirect(args, log) {
log.info(`Adding dependency: task ${taskId} will depend on ${dependencyId}`);
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function
await addDependency(tasksPath, taskId, dependencyId);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
@@ -63,6 +70,9 @@ export async function addDependencyDirect(args, log) {
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in addDependencyDirect: ${error.message}`);
return {
success: false,

View File

@@ -4,6 +4,7 @@
import { addSubtask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/**
* Add a subtask to an existing task
@@ -67,10 +68,17 @@ export async function addSubtaskDirect(args, log) {
// Determine if we should generate files
const generateFiles = !args.skipGenerate;
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Case 1: Convert existing task to subtask
if (existingTaskId) {
log.info(`Converting task ${existingTaskId} to a subtask of ${parentId}`);
const result = await addSubtask(tasksPath, parentId, existingTaskId, null, generateFiles);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
@@ -92,6 +100,10 @@ export async function addSubtaskDirect(args, log) {
};
const result = await addSubtask(tasksPath, parentId, null, newSubtaskData, generateFiles);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
@@ -101,6 +113,9 @@ export async function addSubtaskDirect(args, log) {
};
}
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in addSubtaskDirect: ${error.message}`);
return {
success: false,

View File

@@ -5,6 +5,7 @@
import { addTask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/**
* Direct function wrapper for adding a new task with error handling.
@@ -20,6 +21,9 @@ import { findTasksJsonPath } from '../utils/path-utils.js';
*/
export async function addTaskDirect(args, log) {
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log);
@@ -44,8 +48,18 @@ export async function addTaskDirect(args, log) {
log.info(`Adding new task with prompt: "${prompt}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`);
// Call the addTask function
const newTaskId = await addTask(tasksPath, prompt, dependencies, priority);
// Call the addTask function with 'json' outputFormat to prevent console output when called via MCP
const newTaskId = await addTask(
tasksPath,
prompt,
dependencies,
priority,
{ mcpLog: log },
'json'
);
// Restore normal logging
disableSilentMode();
return {
success: true,
@@ -55,6 +69,9 @@ export async function addTaskDirect(args, log) {
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in addTaskDirect: ${error.message}`);
return {
success: false,

View File

@@ -4,6 +4,7 @@
import { analyzeTaskComplexity } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import fs from 'fs';
import path from 'path';
@@ -48,9 +49,15 @@ export async function analyzeTaskComplexityDirect(args, log) {
log.info('Using Perplexity AI for research-backed complexity analysis');
}
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function
await analyzeTaskComplexity(options);
// Restore normal logging
disableSilentMode();
// Verify the report file was created
if (!fs.existsSync(outputPath)) {
return {
@@ -79,6 +86,9 @@ export async function analyzeTaskComplexityDirect(args, log) {
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in analyzeTaskComplexityDirect: ${error.message}`);
return {
success: false,

View File

@@ -4,6 +4,7 @@
import { clearSubtasks } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import fs from 'fs';
/**
@@ -68,9 +69,15 @@ export async function clearSubtasksDirect(args, log) {
log.info(`Clearing subtasks from tasks: ${taskIds}`);
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function
clearSubtasks(tasksPath, taskIds);
// Restore normal logging
disableSilentMode();
// Read the updated data to provide a summary
const updatedData = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
const taskIdArray = taskIds.split(',').map(id => parseInt(id.trim(), 10));
@@ -90,6 +97,9 @@ export async function clearSubtasksDirect(args, log) {
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in clearSubtasksDirect: ${error.message}`);
return {
success: false,

View File

@@ -3,7 +3,7 @@
* Direct function implementation for displaying complexity analysis report
*/
import { readComplexityReport } from '../../../../scripts/modules/utils.js';
import { readComplexityReport, enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import path from 'path';
@@ -39,8 +39,14 @@ export async function complexityReportDirect(args, log) {
// Define the core action function to read the report
const coreActionFn = async () => {
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
const report = readComplexityReport(reportPath);
// Restore normal logging
disableSilentMode();
if (!report) {
log.warn(`No complexity report found at ${reportPath}`);
return {
@@ -60,6 +66,9 @@ export async function complexityReportDirect(args, log) {
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error reading complexity report: ${error.message}`);
return {
success: false,
@@ -82,6 +91,9 @@ export async function complexityReportDirect(args, log) {
return result; // Returns { success, data/error, fromCache }
} catch (error) {
// Catch unexpected errors from getCachedOrExecute itself
// Ensure silent mode is disabled
disableSilentMode();
log.error(`Unexpected error during getCachedOrExecute for complexityReport: ${error.message}`);
return {
success: false,
@@ -93,6 +105,9 @@ export async function complexityReportDirect(args, log) {
};
}
} catch (error) {
// Ensure silent mode is disabled if an outer error occurs
disableSilentMode();
log.error(`Error in complexityReportDirect: ${error.message}`);
return {
success: false,

View File

@@ -3,6 +3,7 @@
*/
import { expandAllTasks } from '../../../../scripts/modules/task-manager.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
/**
@@ -41,23 +42,38 @@ export async function expandAllTasksDirect(args, log) {
log.info('Force regeneration of subtasks is enabled');
}
// Call the core function
await expandAllTasks(numSubtasks, useResearch, additionalContext, forceFlag);
// The expandAllTasks function doesn't have a return value, so we'll create our own success response
return {
success: true,
data: {
message: "Successfully expanded all pending tasks with subtasks",
details: {
numSubtasks: numSubtasks,
research: useResearch,
prompt: additionalContext,
force: forceFlag
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function
await expandAllTasks(numSubtasks, useResearch, additionalContext, forceFlag);
// Restore normal logging
disableSilentMode();
// The expandAllTasks function doesn't have a return value, so we'll create our own success response
return {
success: true,
data: {
message: "Successfully expanded all pending tasks with subtasks",
details: {
numSubtasks: numSubtasks,
research: useResearch,
prompt: additionalContext,
force: forceFlag
}
}
}
};
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
throw error; // Rethrow to be caught by outer catch block
}
} catch (error) {
// Ensure silent mode is disabled
disableSilentMode();
log.error(`Error in expandAllTasksDirect: ${error.message}`);
return {
success: false,

View File

@@ -4,7 +4,7 @@
*/
import { expandTask } from '../../../../scripts/modules/task-manager.js';
import { readJSON, writeJSON } from '../../../../scripts/modules/utils.js';
import { readJSON, writeJSON, enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import path from 'path';
import fs from 'fs';
@@ -116,28 +116,50 @@ export async function expandTaskDirect(args, log) {
// Save tasks.json with potentially empty subtasks array
writeJSON(tasksPath, data);
// Call the core expandTask function
await expandTask(taskId, numSubtasks, useResearch, additionalContext);
// Read the updated data
const updatedData = readJSON(tasksPath);
const updatedTask = updatedData.tasks.find(t => t.id === taskId);
// Calculate how many subtasks were added
const subtasksAdded = updatedTask.subtasks ?
updatedTask.subtasks.length - subtasksCountBefore : 0;
// Return the result
log.info(`Successfully expanded task ${taskId} with ${subtasksAdded} new subtasks`);
return {
success: true,
data: {
task: updatedTask,
subtasksAdded,
hasExistingSubtasks
},
fromCache: false
};
// Process the request
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call expandTask
const result = await expandTask(taskId, numSubtasks, useResearch, additionalContext);
// Restore normal logging
disableSilentMode();
// Read the updated data
const updatedData = readJSON(tasksPath);
const updatedTask = updatedData.tasks.find(t => t.id === taskId);
// Calculate how many subtasks were added
const subtasksAdded = updatedTask.subtasks ?
updatedTask.subtasks.length - subtasksCountBefore : 0;
// Return the result
log.info(`Successfully expanded task ${taskId} with ${subtasksAdded} new subtasks`);
return {
success: true,
data: {
task: updatedTask,
subtasksAdded,
hasExistingSubtasks
},
fromCache: false
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error expanding task: ${error.message}`);
return {
success: false,
error: {
code: 'CORE_FUNCTION_ERROR',
message: error.message || 'Failed to expand task'
},
fromCache: false
};
}
} catch (error) {
log.error(`Error expanding task: ${error.message}`);
return {

View File

@@ -4,6 +4,7 @@
import { fixDependenciesCommand } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import fs from 'fs';
/**
@@ -32,9 +33,15 @@ export async function fixDependenciesDirect(args, log) {
};
}
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the original command function
await fixDependenciesCommand(tasksPath);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
@@ -43,6 +50,9 @@ export async function fixDependenciesDirect(args, log) {
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error fixing dependencies: ${error.message}`);
return {
success: false,

View File

@@ -4,6 +4,7 @@
*/
import { generateTaskFiles } from '../../../../scripts/modules/task-manager.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import path from 'path';
@@ -39,8 +40,27 @@ export async function generateTaskFilesDirect(args, log) {
log.info(`Generating task files from ${tasksPath} to ${outputDir}`);
// Execute core generateTaskFiles function
generateTaskFiles(tasksPath, outputDir);
// Execute core generateTaskFiles function in a separate try/catch
try {
// Enable silent mode to prevent logs from being written to stdout
enableSilentMode();
// The function is synchronous despite being awaited elsewhere
generateTaskFiles(tasksPath, outputDir);
// Restore normal logging after task generation
disableSilentMode();
} catch (genError) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in generateTaskFiles: ${genError.message}`);
return {
success: false,
error: { code: 'GENERATE_FILES_ERROR', message: genError.message },
fromCache: false
};
}
// Return success with file paths
return {
@@ -54,6 +74,9 @@ export async function generateTaskFilesDirect(args, log) {
fromCache: false // This operation always modifies state and should never be cached
};
} catch (error) {
// Make sure to restore normal logging if an outer error occurs
disableSilentMode();
log.error(`Error generating task files: ${error.message}`);
return {
success: false,

View File

@@ -6,6 +6,7 @@
import { listTasks } from '../../../../scripts/modules/task-manager.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/**
* Direct function wrapper for listTasks with error handling and caching.
@@ -38,6 +39,9 @@ export async function listTasksDirect(args, log) {
// Define the action function to be executed on cache miss
const coreListTasksAction = async () => {
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Executing core listTasks function for path: ${tasksPath}, filter: ${statusFilter}, subtasks: ${withSubtasks}`);
const resultData = listTasks(tasksPath, statusFilter, withSubtasks, 'json');
@@ -46,9 +50,16 @@ export async function listTasksDirect(args, log) {
return { success: false, error: { code: 'INVALID_CORE_RESPONSE', message: 'Invalid or empty response from listTasks core function' } };
}
log.info(`Core listTasks function retrieved ${resultData.tasks.length} tasks`);
// Restore normal logging
disableSilentMode();
return { success: true, data: resultData };
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Core listTasks function failed: ${error.message}`);
return { success: false, error: { code: 'LIST_TASKS_CORE_ERROR', message: error.message || 'Failed to list tasks' } };
}

View File

@@ -7,6 +7,7 @@ import { findNextTask } from '../../../../scripts/modules/task-manager.js';
import { readJSON } from '../../../../scripts/modules/utils.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/**
* Direct function wrapper for finding the next task to work on with error handling and caching.
@@ -38,6 +39,9 @@ export async function nextTaskDirect(args, log) {
// Define the action function to be executed on cache miss
const coreNextTaskAction = async () => {
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Finding next task from ${tasksPath}`);
// Read tasks data
@@ -67,6 +71,9 @@ export async function nextTaskDirect(args, log) {
};
}
// Restore normal logging
disableSilentMode();
// Return the next task data with the full tasks array for reference
log.info(`Successfully found next task ${nextTask.id}: ${nextTask.title}`);
return {
@@ -77,6 +84,9 @@ export async function nextTaskDirect(args, log) {
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error finding next task: ${error.message}`);
return {
success: false,

View File

@@ -7,6 +7,7 @@ import path from 'path';
import fs from 'fs';
import { parsePRD } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/**
* Direct function wrapper for parsing PRD documents and generating tasks.
@@ -66,9 +67,15 @@ export async function parsePRDDirect(args, log) {
log.info(`Preparing to parse PRD from ${inputPath} and output to ${outputPath} with ${numTasks} tasks`);
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Execute core parsePRD function (which is not async but we'll await it to maintain consistency)
await parsePRD(inputPath, outputPath, numTasks);
// Restore normal logging
disableSilentMode();
// Since parsePRD doesn't return a value but writes to a file, we'll read the result
// to return it to the caller
if (fs.existsSync(outputPath)) {
@@ -94,6 +101,9 @@ export async function parsePRDDirect(args, log) {
};
}
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error parsing PRD: ${error.message}`);
return {
success: false,

View File

@@ -4,6 +4,7 @@
import { removeDependency } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/**
* Remove a dependency from a task
@@ -49,9 +50,15 @@ export async function removeDependencyDirect(args, log) {
log.info(`Removing dependency: task ${taskId} no longer depends on ${dependencyId}`);
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function
await removeDependency(tasksPath, taskId, dependencyId);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
@@ -61,6 +68,9 @@ export async function removeDependencyDirect(args, log) {
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error in removeDependencyDirect: ${error.message}`);
return {
success: false,

View File

@@ -4,6 +4,7 @@
import { removeSubtask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/**
* Remove a subtask from its parent task
@@ -18,6 +19,9 @@ import { findTasksJsonPath } from '../utils/path-utils.js';
*/
export async function removeSubtaskDirect(args, log) {
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
if (!args.id) {
@@ -54,6 +58,9 @@ export async function removeSubtaskDirect(args, log) {
const result = await removeSubtask(tasksPath, args.id, convertToTask, generateFiles);
// Restore normal logging
disableSilentMode();
if (convertToTask && result) {
// Return info about the converted task
return {
@@ -73,6 +80,9 @@ export async function removeSubtaskDirect(args, log) {
};
}
} catch (error) {
// Ensure silent mode is disabled even if an outer error occurs
disableSilentMode();
log.error(`Error in removeSubtaskDirect: ${error.message}`);
return {
success: false,

View File

@@ -4,6 +4,7 @@
*/
import { removeTask } from '../../../../scripts/modules/task-manager.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
/**
@@ -49,9 +50,15 @@ export async function removeTaskDirect(args, log) {
log.info(`Removing task with ID: ${taskId} from ${tasksPath}`);
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core removeTask function
const result = await removeTask(tasksPath, taskId);
// Restore normal logging
disableSilentMode();
log.info(`Successfully removed task: ${taskId}`);
// Return the result
@@ -66,6 +73,9 @@ export async function removeTaskDirect(args, log) {
fromCache: false
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error removing task: ${error.message}`);
return {
success: false,
@@ -77,6 +87,9 @@ export async function removeTaskDirect(args, log) {
};
}
} catch (error) {
// Ensure silent mode is disabled even if an outer error occurs
disableSilentMode();
// Catch any unexpected errors
log.error(`Unexpected error in removeTaskDirect: ${error.message}`);
return {

View File

@@ -5,6 +5,7 @@
import { setTaskStatus } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/**
* Direct function wrapper for setTaskStatus with error handling.
@@ -63,20 +64,40 @@ export async function setTaskStatusDirect(args, log) {
log.info(`Setting task ${taskId} status to "${newStatus}"`);
// Execute the setTaskStatus function with source=mcp to avoid console output
await setTaskStatus(tasksPath, taskId, newStatus);
// Return success data
return {
success: true,
data: {
message: `Successfully updated task ${taskId} status to "${newStatus}"`,
taskId,
status: newStatus,
tasksPath
},
fromCache: false // This operation always modifies state and should never be cached
};
// Call the core function
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
await setTaskStatus(tasksPath, taskId, newStatus);
// Restore normal logging
disableSilentMode();
log.info(`Successfully set task ${taskId} status to ${newStatus}`);
// Return success data
return {
success: true,
data: {
message: `Successfully updated task ${taskId} status to "${newStatus}"`,
taskId,
status: newStatus,
tasksPath
},
fromCache: false // This operation always modifies state and should never be cached
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error setting task status: ${error.message}`);
return {
success: false,
error: { code: 'SET_STATUS_ERROR', message: error.message || 'Unknown error setting task status' },
fromCache: false
};
}
} catch (error) {
log.error(`Error setting task status: ${error.message}`);
return {

View File

@@ -7,6 +7,7 @@ import { findTaskById } from '../../../../scripts/modules/utils.js';
import { readJSON } from '../../../../scripts/modules/utils.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/**
* Direct function wrapper for showing task details with error handling and caching.
@@ -52,6 +53,9 @@ export async function showTaskDirect(args, log) {
// Define the action function to be executed on cache miss
const coreShowTaskAction = async () => {
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Retrieving task details for ID: ${taskId} from ${tasksPath}`);
// Read tasks data
@@ -79,6 +83,9 @@ export async function showTaskDirect(args, log) {
};
}
// Restore normal logging
disableSilentMode();
// Return the task data with the full tasks array for reference
// (needed for formatDependenciesWithStatus function in UI)
log.info(`Successfully found task ${taskId}`);
@@ -90,6 +97,9 @@ export async function showTaskDirect(args, log) {
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error showing task: ${error.message}`);
return {
success: false,
@@ -112,6 +122,7 @@ export async function showTaskDirect(args, log) {
return result; // Returns { success, data/error, fromCache }
} catch (error) {
// Catch unexpected errors from getCachedOrExecute itself
disableSilentMode();
log.error(`Unexpected error during getCachedOrExecute for showTask: ${error.message}`);
return {
success: false,

View File

@@ -4,6 +4,7 @@
*/
import { updateSubtaskById } from '../../../../scripts/modules/task-manager.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
/**
@@ -68,35 +69,50 @@ export async function updateSubtaskByIdDirect(args, log) {
log.info(`Updating subtask with ID ${subtaskId} with prompt "${args.prompt}" and research: ${useResearch}`);
// Execute core updateSubtaskById function
const updatedSubtask = await updateSubtaskById(tasksPath, subtaskId, args.prompt, useResearch);
// Handle the case where the subtask couldn't be updated (e.g., already marked as done)
if (!updatedSubtask) {
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Execute core updateSubtaskById function
const updatedSubtask = await updateSubtaskById(tasksPath, subtaskId, args.prompt, useResearch);
// Restore normal logging
disableSilentMode();
// Handle the case where the subtask couldn't be updated (e.g., already marked as done)
if (!updatedSubtask) {
return {
success: false,
error: {
code: 'SUBTASK_UPDATE_FAILED',
message: 'Failed to update subtask. It may be marked as completed, or another error occurred.'
},
fromCache: false
};
}
// Return the updated subtask information
return {
success: false,
error: {
code: 'SUBTASK_UPDATE_FAILED',
message: 'Failed to update subtask. It may be marked as completed, or another error occurred.'
success: true,
data: {
message: `Successfully updated subtask with ID ${subtaskId}`,
subtaskId,
parentId: subtaskId.split('.')[0],
subtask: updatedSubtask,
tasksPath,
useResearch
},
fromCache: false
fromCache: false // This operation always modifies state and should never be cached
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
throw error; // Rethrow to be caught by outer catch block
}
// Return the updated subtask information
return {
success: true,
data: {
message: `Successfully updated subtask with ID ${subtaskId}`,
subtaskId,
parentId: subtaskId.split('.')[0],
subtask: updatedSubtask,
tasksPath,
useResearch
},
fromCache: false // This operation always modifies state and should never be cached
};
} catch (error) {
// Ensure silent mode is disabled
disableSilentMode();
log.error(`Error updating subtask by ID: ${error.message}`);
return {
success: false,

View File

@@ -5,6 +5,7 @@
import { updateTaskById } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
/**
* Direct function wrapper for updateTaskById with error handling.
@@ -79,9 +80,15 @@ export async function updateTaskByIdDirect(args, log) {
log.info(`Updating task with ID ${taskId} with prompt "${args.prompt}" and research: ${useResearch}`);
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Execute core updateTaskById function
await updateTaskById(tasksPath, taskId, args.prompt, useResearch);
// Restore normal logging
disableSilentMode();
// Since updateTaskById doesn't return a value but modifies the tasks file,
// we'll return a success message
return {
@@ -95,6 +102,9 @@ export async function updateTaskByIdDirect(args, log) {
fromCache: false // This operation always modifies state and should never be cached
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error updating task by ID: ${error.message}`);
return {
success: false,

View File

@@ -4,6 +4,7 @@
*/
import { updateTasks } from '../../../../scripts/modules/task-manager.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
/**
@@ -73,22 +74,37 @@ export async function updateTasksDirect(args, log) {
log.info(`Updating tasks from ID ${fromId} with prompt "${args.prompt}" and research: ${useResearch}`);
// Execute core updateTasks function
await updateTasks(tasksPath, fromId, args.prompt, useResearch);
// Since updateTasks doesn't return a value but modifies the tasks file,
// we'll return a success message
return {
success: true,
data: {
message: `Successfully updated tasks from ID ${fromId} based on the prompt`,
fromId,
tasksPath,
useResearch
},
fromCache: false // This operation always modifies state and should never be cached
};
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Execute core updateTasks function
await updateTasks(tasksPath, fromId, args.prompt, useResearch);
// Restore normal logging
disableSilentMode();
// Since updateTasks doesn't return a value but modifies the tasks file,
// we'll return a success message
return {
success: true,
data: {
message: `Successfully updated tasks from ID ${fromId} based on the prompt`,
fromId,
tasksPath,
useResearch
},
fromCache: false // This operation always modifies state and should never be cached
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
throw error; // Rethrow to be caught by outer catch block
}
} catch (error) {
// Ensure silent mode is disabled
disableSilentMode();
log.error(`Error updating tasks: ${error.message}`);
return {
success: false,

View File

@@ -4,6 +4,7 @@
import { validateDependenciesCommand } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { enableSilentMode, disableSilentMode } from '../../../../scripts/modules/utils.js';
import fs from 'fs';
/**
@@ -32,9 +33,15 @@ export async function validateDependenciesDirect(args, log) {
};
}
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the original command function
await validateDependenciesCommand(tasksPath);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
@@ -43,6 +50,9 @@ export async function validateDependenciesDirect(args, log) {
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
log.error(`Error validating dependencies: ${error.message}`);
return {
success: false,

View File

@@ -0,0 +1,217 @@
import { v4 as uuidv4 } from 'uuid';
class AsyncOperationManager {
constructor() {
this.operations = new Map(); // Stores active operation state
this.completedOperations = new Map(); // Stores completed operations
this.maxCompletedOperations = 100; // Maximum number of completed operations to store
this.listeners = new Map(); // For potential future notifications
}
/**
* Adds an operation to be executed asynchronously.
* @param {Function} operationFn - The async function to execute (e.g., a Direct function).
* @param {Object} args - Arguments to pass to the operationFn.
* @param {Object} context - The MCP tool context { log, reportProgress, session }.
* @returns {string} The unique ID assigned to this operation.
*/
addOperation(operationFn, args, context) {
const operationId = `op-${uuidv4()}`;
const operation = {
id: operationId,
status: 'pending',
startTime: Date.now(),
endTime: null,
result: null,
error: null,
// Store necessary parts of context, especially log for background execution
log: context.log,
reportProgress: context.reportProgress, // Pass reportProgress through
session: context.session // Pass session through if needed by the operationFn
};
this.operations.set(operationId, operation);
this.log(operationId, 'info', `Operation added.`);
// Start execution in the background (don't await here)
this._runOperation(operationId, operationFn, args, context).catch(err => {
// Catch unexpected errors during the async execution setup itself
this.log(operationId, 'error', `Critical error starting operation: ${err.message}`, { stack: err.stack });
operation.status = 'failed';
operation.error = { code: 'MANAGER_EXECUTION_ERROR', message: err.message };
operation.endTime = Date.now();
// Move to completed operations
this._moveToCompleted(operationId);
});
return operationId;
}
/**
* Internal function to execute the operation.
* @param {string} operationId - The ID of the operation.
* @param {Function} operationFn - The async function to execute.
* @param {Object} args - Arguments for the function.
* @param {Object} context - The original MCP tool context.
*/
async _runOperation(operationId, operationFn, args, context) {
const operation = this.operations.get(operationId);
if (!operation) return; // Should not happen
operation.status = 'running';
this.log(operationId, 'info', `Operation running.`);
this.emit('statusChanged', { operationId, status: 'running' });
try {
// Pass the necessary context parts to the direct function
// The direct function needs to be adapted if it needs reportProgress
// We pass the original context's log, plus our wrapped reportProgress
const result = await operationFn(args, operation.log, {
reportProgress: (progress) => this._handleProgress(operationId, progress),
mcpLog: operation.log, // Pass log as mcpLog if direct fn expects it
session: operation.session
});
operation.status = result.success ? 'completed' : 'failed';
operation.result = result.success ? result.data : null;
operation.error = result.success ? null : result.error;
this.log(operationId, 'info', `Operation finished with status: ${operation.status}`);
} catch (error) {
this.log(operationId, 'error', `Operation failed with error: ${error.message}`, { stack: error.stack });
operation.status = 'failed';
operation.error = { code: 'OPERATION_EXECUTION_ERROR', message: error.message };
} finally {
operation.endTime = Date.now();
this.emit('statusChanged', { operationId, status: operation.status, result: operation.result, error: operation.error });
// Move to completed operations if done or failed
if (operation.status === 'completed' || operation.status === 'failed') {
this._moveToCompleted(operationId);
}
}
}
/**
* Move an operation from active operations to completed operations history.
* @param {string} operationId - The ID of the operation to move.
* @private
*/
_moveToCompleted(operationId) {
const operation = this.operations.get(operationId);
if (!operation) return;
// Store only the necessary data in completed operations
const completedData = {
id: operation.id,
status: operation.status,
startTime: operation.startTime,
endTime: operation.endTime,
result: operation.result,
error: operation.error,
};
this.completedOperations.set(operationId, completedData);
this.operations.delete(operationId);
// Trim completed operations if exceeding maximum
if (this.completedOperations.size > this.maxCompletedOperations) {
// Get the oldest operation (sorted by endTime)
const oldest = [...this.completedOperations.entries()]
.sort((a, b) => a[1].endTime - b[1].endTime)[0];
if (oldest) {
this.completedOperations.delete(oldest[0]);
}
}
}
/**
* Handles progress updates from the running operation and forwards them.
* @param {string} operationId - The ID of the operation reporting progress.
* @param {Object} progress - The progress object { progress, total? }.
*/
_handleProgress(operationId, progress) {
const operation = this.operations.get(operationId);
if (operation && operation.reportProgress) {
try {
// Use the reportProgress function captured from the original context
operation.reportProgress(progress);
this.log(operationId, 'debug', `Reported progress: ${JSON.stringify(progress)}`);
} catch(err) {
this.log(operationId, 'warn', `Failed to report progress: ${err.message}`);
// Don't stop the operation, just log the reporting failure
}
}
}
/**
* Retrieves the status and result/error of an operation.
* @param {string} operationId - The ID of the operation.
* @returns {Object | null} The operation details or null if not found.
*/
getStatus(operationId) {
// First check active operations
const operation = this.operations.get(operationId);
if (operation) {
return {
id: operation.id,
status: operation.status,
startTime: operation.startTime,
endTime: operation.endTime,
result: operation.result,
error: operation.error,
};
}
// Then check completed operations
const completedOperation = this.completedOperations.get(operationId);
if (completedOperation) {
return completedOperation;
}
// Operation not found in either active or completed
return {
error: {
code: 'OPERATION_NOT_FOUND',
message: `Operation ID ${operationId} not found. It may have been completed and removed from history, or the ID may be invalid.`
},
status: 'not_found'
};
}
/**
* Internal logging helper to prefix logs with the operation ID.
* @param {string} operationId - The ID of the operation.
* @param {'info'|'warn'|'error'|'debug'} level - Log level.
* @param {string} message - Log message.
* @param {Object} [meta] - Additional metadata.
*/
log(operationId, level, message, meta = {}) {
const operation = this.operations.get(operationId);
// Use the logger instance associated with the operation if available, otherwise console
const logger = operation?.log || console;
const logFn = logger[level] || logger.log || console.log; // Fallback
logFn(`[AsyncOp ${operationId}] ${message}`, meta);
}
// --- Basic Event Emitter ---
on(eventName, listener) {
if (!this.listeners.has(eventName)) {
this.listeners.set(eventName, []);
}
this.listeners.get(eventName).push(listener);
}
emit(eventName, data) {
if (this.listeners.has(eventName)) {
this.listeners.get(eventName).forEach(listener => listener(data));
}
}
}
// Export a singleton instance
const asyncOperationManager = new AsyncOperationManager();
// Export the manager and potentially the class if needed elsewhere
export { asyncOperationManager, AsyncOperationManager };

View File

@@ -5,6 +5,7 @@ import { fileURLToPath } from "url";
import fs from "fs";
import logger from "./logger.js";
import { registerTaskMasterTools } from "./tools/index.js";
import { asyncOperationManager } from './core/utils/async-manager.js';
// Load environment variables
dotenv.config();
@@ -34,6 +35,9 @@ class TaskMasterMCPServer {
this.server.addResourceTemplate({});
// Make the manager accessible (e.g., pass it to tool registration)
this.asyncManager = asyncOperationManager;
// Bind methods
this.init = this.init.bind(this);
this.start = this.start.bind(this);
@@ -49,8 +53,8 @@ class TaskMasterMCPServer {
async init() {
if (this.initialized) return;
// Register Task Master tools
registerTaskMasterTools(this.server);
// Pass the manager instance to the tool registration function
registerTaskMasterTools(this.server, this.asyncManager);
this.initialized = true;
@@ -83,4 +87,7 @@ class TaskMasterMCPServer {
}
}
// Export the manager from here as well, if needed elsewhere
export { asyncOperationManager };
export default TaskMasterMCPServer;

View File

@@ -11,7 +11,7 @@ const LOG_LEVELS = {
// Get log level from environment or default to info
const LOG_LEVEL = process.env.LOG_LEVEL
? LOG_LEVELS[process.env.LOG_LEVEL.toLowerCase()]
? LOG_LEVELS[process.env.LOG_LEVEL.toLowerCase()] ?? LOG_LEVELS.info
: LOG_LEVELS.info;
/**
@@ -20,43 +20,66 @@ const LOG_LEVEL = process.env.LOG_LEVEL
* @param {...any} args - Arguments to log
*/
function log(level, ...args) {
const icons = {
debug: chalk.gray("🔍"),
info: chalk.blue(""),
warn: chalk.yellow("⚠️"),
error: chalk.red("❌"),
success: chalk.green(""),
// Use text prefixes instead of emojis
const prefixes = {
debug: chalk.gray("[DEBUG]"),
info: chalk.blue("[INFO]"),
warn: chalk.yellow("[WARN]"),
error: chalk.red("[ERROR]"),
success: chalk.green("[SUCCESS]"),
};
if (LOG_LEVELS[level] >= LOG_LEVEL) {
const icon = icons[level] || "";
if (LOG_LEVELS[level] !== undefined && LOG_LEVELS[level] >= LOG_LEVEL) {
const prefix = prefixes[level] || "";
let coloredArgs = args;
if (level === "error") {
console.error(icon, chalk.red(...args));
} else if (level === "warn") {
console.warn(icon, chalk.yellow(...args));
} else if (level === "success") {
console.log(icon, chalk.green(...args));
} else if (level === "info") {
console.log(icon, chalk.blue(...args));
} else {
console.log(icon, ...args);
try {
switch(level) {
case "error":
coloredArgs = args.map(arg => typeof arg === 'string' ? chalk.red(arg) : arg);
break;
case "warn":
coloredArgs = args.map(arg => typeof arg === 'string' ? chalk.yellow(arg) : arg);
break;
case "success":
coloredArgs = args.map(arg => typeof arg === 'string' ? chalk.green(arg) : arg);
break;
case "info":
coloredArgs = args.map(arg => typeof arg === 'string' ? chalk.blue(arg) : arg);
break;
case "debug":
coloredArgs = args.map(arg => typeof arg === 'string' ? chalk.gray(arg) : arg);
break;
// default: use original args (no color)
}
} catch (colorError) {
// Fallback if chalk fails on an argument
// Use console.error here for internal logger errors, separate from normal logging
console.error("Internal Logger Error applying chalk color:", colorError);
coloredArgs = args;
}
// Revert to console.log - FastMCP's context logger (context.log)
// is responsible for directing logs correctly (e.g., to stderr)
// during tool execution without upsetting the client connection.
// Logs outside of tool execution (like startup) will go to stdout.
console.log(prefix, ...coloredArgs);
}
}
/**
* Create a logger object with methods for different log levels
* Can be used as a drop-in replacement for existing logger initialization
* @returns {Object} Logger object with info, error, debug, warn, and success methods
*/
export function createLogger() {
const createLogMethod = (level) => (...args) => log(level, ...args);
return {
debug: (message) => log("debug", message),
info: (message) => log("info", message),
warn: (message) => log("warn", message),
error: (message) => log("error", message),
success: (message) => log("success", message),
debug: createLogMethod("debug"),
info: createLogMethod("info"),
warn: createLogMethod("warn"),
error: createLogMethod("error"),
success: createLogMethod("success"),
log: log, // Also expose the raw log function
};
}

View File

@@ -7,6 +7,7 @@ import { z } from "zod";
import {
handleApiResult,
createErrorResponse,
createContentResponse,
getProjectRootFromSession
} from "./utils.js";
import { addTaskDirect } from "../core/task-master-core.js";
@@ -14,11 +15,12 @@ import { addTaskDirect } from "../core/task-master-core.js";
/**
* Register the add-task tool with the MCP server
* @param {Object} server - FastMCP server instance
* @param {AsyncOperationManager} asyncManager - The async operation manager instance.
*/
export function registerAddTaskTool(server) {
export function registerAddTaskTool(server, asyncManager) {
server.addTool({
name: "add_task",
description: "Add a new task using AI",
description: "Starts adding a new task using AI in the background.",
parameters: z.object({
prompt: z.string().describe("Description of the task to add"),
dependencies: z.string().optional().describe("Comma-separated list of task IDs this task depends on"),
@@ -26,29 +28,38 @@ export function registerAddTaskTool(server) {
file: z.string().optional().describe("Path to the tasks file"),
projectRoot: z.string().optional().describe("Root directory of the project (default: current working directory)")
}),
execute: async (args, { log, reportProgress, session }) => {
execute: async (args, context) => {
const { log, reportProgress, session } = context;
try {
log.info(`MCP add_task called with prompt: "${args.prompt}"`);
log.info(`MCP add_task request received with prompt: \"${args.prompt}\"`);
// Get project root using the utility function
if (!args.prompt) {
return createErrorResponse("Prompt is required for add_task.", "VALIDATION_ERROR");
}
let rootFolder = getProjectRootFromSession(session, log);
// Fallback to args.projectRoot if session didn't provide one
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
// Call the direct function with the resolved rootFolder
const result = await addTaskDirect({
projectRoot: rootFolder, // Pass the resolved root
const directArgs = {
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
};
const operationId = asyncManager.addOperation(addTaskDirect, directArgs, context);
return handleApiResult(result, log);
log.info(`Started background operation for add_task. Operation ID: ${operationId}`);
return createContentResponse({
message: "Add task operation started successfully.",
operationId: operationId
});
} catch (error) {
log.error(`Error in add_task MCP tool: ${error.message}`);
return createErrorResponse(error.message, "ADD_TASK_ERROR");
log.error(`Error initiating add_task operation: ${error.message}`, { stack: error.stack });
return createErrorResponse(`Failed to start add task operation: ${error.message}`, "ADD_TASK_INIT_ERROR");
}
}
});

View File

@@ -30,7 +30,7 @@ export function registerAnalyzeTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Analyzing task complexity with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -42,9 +42,9 @@ export function registerAnalyzeTool(server) {
const result = await analyzeTaskComplexityDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Task complexity analysis complete: ${result.data.message}`);

View File

@@ -26,7 +26,7 @@ export function registerComplexityReportTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Getting complexity report with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -38,9 +38,9 @@ export function registerComplexityReportTool(server) {
const result = await complexityReportDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully retrieved complexity report${result.fromCache ? ' (from cache)' : ''}`);

View File

@@ -30,7 +30,7 @@ export function registerExpandAllTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Expanding all tasks with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -42,19 +42,19 @@ export function registerExpandAllTool(server) {
const result = await expandAllTasksDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`All tasks expanded successfully: ${result.data.message}`);
log.info(`Successfully expanded all tasks: ${result.data.message}`);
} else {
log.error(`Failed to expand tasks: ${result.error.message}`);
log.error(`Failed to expand all tasks: ${result.error?.message || 'Unknown error'}`);
}
return handleApiResult(result, log, 'Error expanding tasks');
return handleApiResult(result, log, 'Error expanding all tasks');
} catch (error) {
log.error(`Error in expandAll tool: ${error.message}`);
log.error(`Error in expand-all tool: ${error.message}`);
return createErrorResponse(error.message);
}
},

View File

@@ -36,7 +36,7 @@ export function registerExpandTaskTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Expanding task with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -48,20 +48,20 @@ export function registerExpandTaskTool(server) {
const result = await expandTaskDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully expanded task ID: ${args.id} with ${result.data.subtasksAdded} new subtasks${result.data.hasExistingSubtasks ? ' (appended to existing subtasks)' : ''}`);
log.info(`Successfully expanded task with ID ${args.id}`);
} else {
log.error(`Failed to expand task: ${result.error.message}`);
log.error(`Failed to expand task: ${result.error?.message || 'Unknown error'}`);
}
return handleApiResult(result, log, 'Error expanding task');
} catch (error) {
log.error(`Error in expand-task tool: ${error.message}`);
return createErrorResponse(`Failed to expand task: ${error.message}`);
log.error(`Error in expand task tool: ${error.message}`);
return createErrorResponse(error.message);
}
},
});

View File

@@ -32,7 +32,7 @@ export function registerGenerateTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Generating task files with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -44,14 +44,14 @@ export function registerGenerateTool(server) {
const result = await generateTaskFilesDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully generated task files: ${result.data.message}`);
} else {
log.error(`Failed to generate task files: ${result.error.message}`);
log.error(`Failed to generate task files: ${result.error?.message || 'Unknown error'}`);
}
return handleApiResult(result, log, 'Error generating task files');

View File

@@ -0,0 +1,42 @@
// mcp-server/src/tools/get-operation-status.js
import { z } from 'zod';
import { createErrorResponse, createContentResponse } from './utils.js'; // Assuming these utils exist
/**
* Register the get_operation_status tool.
* @param {FastMCP} server - FastMCP server instance.
* @param {AsyncOperationManager} asyncManager - The async operation manager.
*/
export function registerGetOperationStatusTool(server, asyncManager) {
server.addTool({
name: 'get_operation_status',
description: 'Retrieves the status and result/error of a background operation.',
parameters: z.object({
operationId: z.string().describe('The ID of the operation to check.'),
}),
execute: async (args, { log }) => {
try {
const { operationId } = args;
log.info(`Checking status for operation ID: ${operationId}`);
const status = asyncManager.getStatus(operationId);
// Status will now always return an object, but it might have status='not_found'
if (status.status === 'not_found') {
log.warn(`Operation ID not found: ${operationId}`);
return createErrorResponse(
status.error?.message || `Operation ID not found: ${operationId}`,
status.error?.code || 'OPERATION_NOT_FOUND'
);
}
log.info(`Status for ${operationId}: ${status.status}`);
return createContentResponse(status);
} catch (error) {
log.error(`Error in get_operation_status tool: ${error.message}`, { stack: error.stack });
return createErrorResponse(`Failed to get operation status: ${error.message}`, 'GET_STATUS_ERROR');
}
},
});
}

View File

@@ -36,7 +36,7 @@ export function registerListTasksTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Getting tasks with filters: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -48,9 +48,9 @@ export function registerListTasksTool(server) {
const result = await listTasksDirect({
projectRoot: rootFolder,
...args
}, log);
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
log.info(`Retrieved ${result.success ? (result.data?.tasks?.length || 0) : 0} tasks${result.fromCache ? ' (from cache)' : ''}`);
return handleApiResult(result, log, 'Error getting tasks');

View File

@@ -27,12 +27,15 @@ import { registerComplexityReportTool } from "./complexity-report.js";
import { registerAddDependencyTool } from "./add-dependency.js";
import { registerRemoveTaskTool } from './remove-task.js';
import { registerInitializeProjectTool } from './initialize-project.js';
import { asyncOperationManager } from '../core/utils/async-manager.js';
import { registerGetOperationStatusTool } from './get-operation-status.js';
/**
* Register all Task Master tools with the MCP server
* @param {Object} server - FastMCP server instance
* @param {asyncOperationManager} asyncManager - The async operation manager instance
*/
export function registerTaskMasterTools(server) {
export function registerTaskMasterTools(server, asyncManager) {
try {
// Register each tool
registerListTasksTool(server);
@@ -45,7 +48,7 @@ export function registerTaskMasterTools(server) {
registerShowTaskTool(server);
registerNextTaskTool(server);
registerExpandTaskTool(server);
registerAddTaskTool(server);
registerAddTaskTool(server, asyncManager);
registerAddSubtaskTool(server);
registerRemoveSubtaskTool(server);
registerAnalyzeTool(server);
@@ -58,10 +61,13 @@ export function registerTaskMasterTools(server) {
registerAddDependencyTool(server);
registerRemoveTaskTool(server);
registerInitializeProjectTool(server);
registerGetOperationStatusTool(server, asyncManager);
} catch (error) {
logger.error(`Error registering Task Master tools: ${error.message}`);
throw error;
}
logger.info('Registered Task Master MCP tools');
}
export default {

View File

@@ -31,7 +31,7 @@ export function registerNextTaskTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Finding next task with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -43,24 +43,20 @@ export function registerNextTaskTool(server) {
const result = await nextTaskDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
if (result.data.nextTask) {
log.info(`Successfully found next task ID: ${result.data.nextTask.id}${result.fromCache ? ' (from cache)' : ''}`);
} else {
log.info(`No eligible next task found${result.fromCache ? ' (from cache)' : ''}`);
}
log.info(`Successfully found next task: ${result.data?.task?.id || 'No available tasks'}`);
} else {
log.error(`Failed to find next task: ${result.error.message}`);
log.error(`Failed to find next task: ${result.error?.message || 'Unknown error'}`);
}
return handleApiResult(result, log, 'Error finding next task');
} catch (error) {
log.error(`Error in next-task tool: ${error.message}`);
return createErrorResponse(`Failed to find next task: ${error.message}`);
log.error(`Error in nextTask tool: ${error.message}`);
return createErrorResponse(error.message);
}
},
});

View File

@@ -33,7 +33,7 @@ export function registerParsePRDTool(server) {
}),
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Parsing PRD document with args: ${JSON.stringify(args)}`);
log.info(`Parsing PRD with args: ${JSON.stringify(args)}`);
let rootFolder = getProjectRootFromSession(session, log);
@@ -45,19 +45,19 @@ export function registerParsePRDTool(server) {
const result = await parsePRDDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully generated ${result.data?.taskCount || 0} tasks from PRD at ${result.data?.outputPath}`);
log.info(`Successfully parsed PRD: ${result.data.message}`);
} else {
log.error(`Failed to parse PRD: ${result.error?.message || 'Unknown error'}`);
}
return handleApiResult(result, log, 'Error parsing PRD document');
return handleApiResult(result, log, 'Error parsing PRD');
} catch (error) {
log.error(`Error in parse_prd tool: ${error.message}`);
log.error(`Error in parse-prd tool: ${error.message}`);
return createErrorResponse(error.message);
}
},

View File

@@ -28,7 +28,7 @@ export function registerRemoveDependencyTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Removing dependency for task ${args.id} from ${args.dependsOn} with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -40,9 +40,9 @@ export function registerRemoveDependencyTool(server) {
const result = await removeDependencyDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully removed dependency: ${result.data.message}`);

View File

@@ -29,7 +29,7 @@ export function registerRemoveSubtaskTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -41,9 +41,9 @@ export function registerRemoveSubtaskTool(server) {
const result = await removeSubtaskDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Subtask removed successfully: ${result.data.message}`);

View File

@@ -37,7 +37,7 @@ export function registerSetTaskStatusTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Setting status of task(s) ${args.id} to: ${args.status}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -49,9 +49,9 @@ export function registerSetTaskStatusTool(server) {
const result = await setTaskStatusDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully updated status for task(s) ${args.id} to "${args.status}": ${result.data.message}`);

View File

@@ -34,7 +34,7 @@ export function registerUpdateSubtaskTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -46,9 +46,9 @@ export function registerUpdateSubtaskTool(server) {
const result = await updateSubtaskByIdDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully updated subtask with ID ${args.id}`);

View File

@@ -34,7 +34,7 @@ export function registerUpdateTaskTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Updating task with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -46,9 +46,9 @@ export function registerUpdateTaskTool(server) {
const result = await updateTaskByIdDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully updated task with ID ${args.id}`);

View File

@@ -34,7 +34,7 @@ export function registerUpdateTool(server) {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Updating tasks with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
@@ -46,9 +46,9 @@ export function registerUpdateTool(server) {
const result = await updateTasksDirect({
projectRoot: rootFolder,
...args
}, log, { reportProgress, mcpLog: log, session});
}, log/*, { reportProgress, mcpLog: log, session}*/);
await reportProgress({ progress: 100 });
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully updated tasks from ID ${args.from}: ${result.data.message}`);