Files
claude-task-master/mcp-server/src/core/direct-functions/expand-task.js
2025-06-03 01:09:29 +02:00

255 lines
7.2 KiB
JavaScript

/**
* expand-task.js
* Direct function implementation for expanding a task into subtasks
*/
import expandTask from '../../../../scripts/modules/task-manager/expand-task.js';
import {
readJSON,
writeJSON,
enableSilentMode,
disableSilentMode,
isSilentMode
} from '../../../../scripts/modules/utils.js';
import path from 'path';
import fs from 'fs';
import { createLogWrapper } from '../../tools/utils.js';
/**
* Direct function wrapper for expanding a task into subtasks with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - The ID of the task to expand.
* @param {number|string} [args.num] - Number of subtasks to generate.
* @param {boolean} [args.research] - Enable research role for subtask generation.
* @param {string} [args.prompt] - Additional context to guide subtask generation.
* @param {boolean} [args.force] - Force expansion even if subtasks exist.
* @param {string} [args.projectRoot] - Project root directory.
* @param {Object} log - Logger object
* @param {Object} context - Context object containing session
* @param {Object} [context.session] - MCP Session object
* @returns {Promise<Object>} - Task expansion result { success: boolean, data?: any, error?: { code: string, message: string } }
*/
export async function expandTaskDirect(args, log, context = {}) {
const { session } = context; // Extract session
// Destructure expected args, including projectRoot
const { tasksJsonPath, id, num, research, prompt, force, projectRoot } = args;
// Log session root data for debugging
log.info(
`Session data in expandTaskDirect: ${JSON.stringify({
hasSession: !!session,
sessionKeys: session ? Object.keys(session) : [],
roots: session?.roots,
rootsStr: JSON.stringify(session?.roots)
})}`
);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('expandTaskDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Use provided path
const tasksPath = tasksJsonPath;
log.info(`[expandTaskDirect] Using tasksPath: ${tasksPath}`);
// Validate task ID
const taskId = id ? parseInt(id, 10) : null;
if (!taskId) {
log.error('Task ID is required');
return {
success: false,
error: {
code: 'INPUT_VALIDATION_ERROR',
message: 'Task ID is required'
}
};
}
// Process other parameters
const numSubtasks = num ? parseInt(num, 10) : undefined;
const useResearch = research === true;
const additionalContext = prompt || '';
const forceFlag = force === true;
try {
log.info(
`[expandTaskDirect] Expanding task ${taskId} into ${numSubtasks || 'default'} subtasks. Research: ${useResearch}, Force: ${forceFlag}`
);
// Read tasks data
log.info(`[expandTaskDirect] Attempting to read JSON from: ${tasksPath}`);
const data = readJSON(tasksPath);
log.info(
`[expandTaskDirect] Result of readJSON: ${data ? 'Data read successfully' : 'readJSON returned null or undefined'}`
);
if (!data || !data.tasks) {
log.error(
`[expandTaskDirect] readJSON failed or returned invalid data for path: ${tasksPath}`
);
return {
success: false,
error: {
code: 'INVALID_TASKS_FILE',
message: `No valid tasks found in ${tasksPath}. readJSON returned: ${JSON.stringify(data)}`
}
};
}
// Find the specific task
log.info(`[expandTaskDirect] Searching for task ID ${taskId} in data`);
const task = data.tasks.find((t) => t.id === taskId);
log.info(`[expandTaskDirect] Task found: ${task ? 'Yes' : 'No'}`);
if (!task) {
return {
success: false,
error: {
code: 'TASK_NOT_FOUND',
message: `Task with ID ${taskId} not found`
}
};
}
// Check if task is completed
if (task.status === 'done' || task.status === 'completed') {
return {
success: false,
error: {
code: 'TASK_COMPLETED',
message: `Task ${taskId} is already marked as ${task.status} and cannot be expanded`
}
};
}
// Check for existing subtasks and force flag
const hasExistingSubtasks = task.subtasks && task.subtasks.length > 0;
if (hasExistingSubtasks && !forceFlag) {
log.info(
`Task ${taskId} already has ${task.subtasks.length} subtasks. Use --force to overwrite.`
);
return {
success: true,
data: {
message: `Task ${taskId} already has subtasks. Expansion skipped.`,
task,
subtasksAdded: 0,
hasExistingSubtasks
}
};
}
// If force flag is set, clear existing subtasks
if (hasExistingSubtasks && forceFlag) {
log.info(
`Force flag set. Clearing existing subtasks for task ${taskId}.`
);
task.subtasks = [];
}
// Keep a copy of the task before modification
const originalTask = JSON.parse(JSON.stringify(task));
// Tracking subtasks count before expansion
const subtasksCountBefore = task.subtasks ? task.subtasks.length : 0;
// Create a backup of the tasks.json file
const backupPath = path.join(path.dirname(tasksPath), 'tasks.json.bak');
fs.copyFileSync(tasksPath, backupPath);
// Directly modify the data instead of calling the CLI function
if (!task.subtasks) {
task.subtasks = [];
}
// Save tasks.json with potentially empty subtasks array
writeJSON(tasksPath, data);
// Create logger wrapper using the utility
const mcpLog = createLogWrapper(log);
let wasSilent; // Declare wasSilent outside the try block
// Process the request
try {
// Enable silent mode to prevent console logs from interfering with JSON response
wasSilent = isSilentMode(); // Assign inside the try block
if (!wasSilent) enableSilentMode();
// Call the core expandTask function with the wrapped logger and projectRoot
const coreResult = await expandTask(
tasksPath,
taskId,
numSubtasks,
useResearch,
additionalContext,
{
mcpLog,
session,
projectRoot,
commandName: 'expand-task',
outputType: 'mcp'
},
forceFlag
);
// Restore normal logging
if (!wasSilent && isSilentMode()) 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, including telemetryData
log.info(
`Successfully expanded task ${taskId} with ${subtasksAdded} new subtasks`
);
return {
success: true,
data: {
task: coreResult.task,
subtasksAdded,
hasExistingSubtasks,
telemetryData: coreResult.telemetryData
}
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
if (!wasSilent && isSilentMode()) disableSilentMode();
log.error(`Error expanding task: ${error.message}`);
return {
success: false,
error: {
code: 'CORE_FUNCTION_ERROR',
message: error.message || 'Failed to expand task'
}
};
}
} catch (error) {
log.error(`Error expanding task: ${error.message}`);
return {
success: false,
error: {
code: 'CORE_FUNCTION_ERROR',
message: error.message || 'Failed to expand task'
}
};
}
}