chore: changeset + update rules.

This commit is contained in:
Eyal Toledano
2025-04-03 04:09:27 -04:00
parent 68f0bfc811
commit a908109cf7
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 };