Merge branch 'next' of github.com:eyaltoledano/claude-task-master into add-complexity-score-to-task
This commit is contained in:
@@ -8,15 +8,7 @@ import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import {
|
||||
getAnthropicClientForMCP,
|
||||
getModelConfig
|
||||
} from '../utils/ai-client-utils.js';
|
||||
import {
|
||||
_buildAddTaskPrompt,
|
||||
parseTaskJsonResponse,
|
||||
_handleAnthropicStream
|
||||
} from '../../../../scripts/modules/ai-services.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for adding a new task with error handling.
|
||||
@@ -29,20 +21,24 @@ import {
|
||||
* @param {string} [args.testStrategy] - Test strategy (for manual task creation)
|
||||
* @param {string} [args.dependencies] - Comma-separated list of task IDs this task depends on
|
||||
* @param {string} [args.priority='medium'] - Task priority (high, medium, low)
|
||||
* @param {string} [args.file='tasks/tasks.json'] - Path to the tasks file
|
||||
* @param {string} [args.projectRoot] - Project root directory
|
||||
* @param {string} [args.tasksJsonPath] - Path to the tasks.json file (resolved by tool)
|
||||
* @param {boolean} [args.research=false] - Whether to use research capabilities for task creation
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Additional context (reportProgress, session)
|
||||
* @param {Object} context - Additional context (session)
|
||||
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function addTaskDirect(args, log, context = {}) {
|
||||
// Destructure expected args
|
||||
// Destructure expected args (including research)
|
||||
const { tasksJsonPath, prompt, dependencies, priority, research } = args;
|
||||
try {
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
const { session } = context; // Destructure session from context
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
try {
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('addTaskDirect called without tasksJsonPath');
|
||||
@@ -79,20 +75,17 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
}
|
||||
|
||||
// Extract and prepare parameters
|
||||
const taskPrompt = prompt;
|
||||
const taskDependencies = Array.isArray(dependencies)
|
||||
? dependencies
|
||||
: dependencies
|
||||
? dependencies // Already an array if passed directly
|
||||
: dependencies // Check if dependencies exist and are a string
|
||||
? String(dependencies)
|
||||
.split(',')
|
||||
.map((id) => parseInt(id.trim(), 10))
|
||||
: [];
|
||||
const taskPriority = priority || 'medium';
|
||||
|
||||
// Extract context parameters for advanced functionality
|
||||
const { session } = context;
|
||||
.map((id) => parseInt(id.trim(), 10)) // Split, trim, and parse
|
||||
: []; // Default to empty array if null/undefined
|
||||
const taskPriority = priority || 'medium'; // Default priority
|
||||
|
||||
let manualTaskData = null;
|
||||
let newTaskId;
|
||||
|
||||
if (isManualCreation) {
|
||||
// Create manual task data object
|
||||
@@ -108,150 +101,61 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
);
|
||||
|
||||
// Call the addTask function with manual task data
|
||||
const newTaskId = await addTask(
|
||||
newTaskId = await addTask(
|
||||
tasksPath,
|
||||
null, // No prompt needed for manual creation
|
||||
null, // prompt is null for manual creation
|
||||
taskDependencies,
|
||||
priority,
|
||||
taskPriority,
|
||||
{
|
||||
mcpLog: log,
|
||||
session
|
||||
session,
|
||||
mcpLog
|
||||
},
|
||||
'json', // Use JSON output format to prevent console output
|
||||
null, // No custom environment
|
||||
manualTaskData // Pass the manual task data
|
||||
'json', // outputFormat
|
||||
manualTaskData, // Pass the manual task data
|
||||
false // research flag is false for manual creation
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
taskId: newTaskId,
|
||||
message: `Successfully added new task #${newTaskId}`
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// AI-driven task creation
|
||||
log.info(
|
||||
`Adding new task with prompt: "${prompt}", dependencies: [${taskDependencies.join(', ')}], priority: ${priority}`
|
||||
`Adding new task with prompt: "${prompt}", dependencies: [${taskDependencies.join(', ')}], priority: ${taskPriority}, research: ${research}`
|
||||
);
|
||||
|
||||
// Initialize AI client with session environment
|
||||
let localAnthropic;
|
||||
try {
|
||||
localAnthropic = getAnthropicClientForMCP(session, log);
|
||||
} catch (error) {
|
||||
log.error(`Failed to initialize Anthropic client: ${error.message}`);
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'AI_CLIENT_ERROR',
|
||||
message: `Cannot initialize AI client: ${error.message}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Get model configuration from session
|
||||
const modelConfig = getModelConfig(session);
|
||||
|
||||
// Read existing tasks to provide context
|
||||
let tasksData;
|
||||
try {
|
||||
const fs = await import('fs');
|
||||
tasksData = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
|
||||
} catch (error) {
|
||||
log.warn(`Could not read existing tasks for context: ${error.message}`);
|
||||
tasksData = { tasks: [] };
|
||||
}
|
||||
|
||||
// Build prompts for AI
|
||||
const { systemPrompt, userPrompt } = _buildAddTaskPrompt(
|
||||
prompt,
|
||||
tasksData.tasks
|
||||
);
|
||||
|
||||
// Make the AI call using the streaming helper
|
||||
let responseText;
|
||||
try {
|
||||
responseText = await _handleAnthropicStream(
|
||||
localAnthropic,
|
||||
{
|
||||
model: modelConfig.model,
|
||||
max_tokens: modelConfig.maxTokens,
|
||||
temperature: modelConfig.temperature,
|
||||
messages: [{ role: 'user', content: userPrompt }],
|
||||
system: systemPrompt
|
||||
},
|
||||
{
|
||||
mcpLog: log
|
||||
}
|
||||
);
|
||||
} catch (error) {
|
||||
log.error(`AI processing failed: ${error.message}`);
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'AI_PROCESSING_ERROR',
|
||||
message: `Failed to generate task with AI: ${error.message}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Parse the AI response
|
||||
let taskDataFromAI;
|
||||
try {
|
||||
taskDataFromAI = parseTaskJsonResponse(responseText);
|
||||
} catch (error) {
|
||||
log.error(`Failed to parse AI response: ${error.message}`);
|
||||
disableSilentMode();
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'RESPONSE_PARSING_ERROR',
|
||||
message: `Failed to parse AI response: ${error.message}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Call the addTask function with 'json' outputFormat to prevent console output when called via MCP
|
||||
const newTaskId = await addTask(
|
||||
// Call the addTask function, passing the research flag
|
||||
newTaskId = await addTask(
|
||||
tasksPath,
|
||||
prompt,
|
||||
prompt, // Use the prompt for AI creation
|
||||
taskDependencies,
|
||||
priority,
|
||||
taskPriority,
|
||||
{
|
||||
mcpLog: log,
|
||||
session
|
||||
session,
|
||||
mcpLog
|
||||
},
|
||||
'json',
|
||||
null,
|
||||
taskDataFromAI // Pass the parsed AI result as the manual task data
|
||||
'json', // outputFormat
|
||||
null, // manualTaskData is null for AI creation
|
||||
research // Pass the research flag
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
taskId: newTaskId,
|
||||
message: `Successfully added new task #${newTaskId}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
taskId: newTaskId,
|
||||
message: `Successfully added new task #${newTaskId}`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error in addTaskDirect: ${error.message}`);
|
||||
// Add specific error code checks if needed
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'ADD_TASK_ERROR',
|
||||
code: error.code || 'ADD_TASK_ERROR', // Use error code if available
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,37 +2,36 @@
|
||||
* Direct function wrapper for analyzeTaskComplexity
|
||||
*/
|
||||
|
||||
import { analyzeTaskComplexity } from '../../../../scripts/modules/task-manager.js';
|
||||
import analyzeTaskComplexity from '../../../../scripts/modules/task-manager/analyze-task-complexity.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode,
|
||||
isSilentMode,
|
||||
readJSON
|
||||
isSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { createLogWrapper } from '../../tools/utils.js'; // Import the new utility
|
||||
|
||||
/**
|
||||
* Analyze task complexity and generate recommendations
|
||||
* @param {Object} args - Function arguments
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
|
||||
* @param {string} args.outputPath - Explicit absolute path to save the report.
|
||||
* @param {string} [args.model] - LLM model to use for analysis
|
||||
* @param {string|number} [args.threshold] - Minimum complexity score to recommend expansion (1-10)
|
||||
* @param {boolean} [args.research] - Use Perplexity AI for research-backed complexity analysis
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} [context={}] - Context object containing session data
|
||||
* @param {Object} [context.session] - MCP session object
|
||||
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
|
||||
*/
|
||||
export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
||||
const { session } = context; // Only extract session, not reportProgress
|
||||
const { session } = context; // Extract session
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, outputPath, model, threshold, research } = args;
|
||||
const { tasksJsonPath, outputPath, model, threshold, research } = args; // Model is ignored by core function now
|
||||
|
||||
// --- Initial Checks (remain the same) ---
|
||||
try {
|
||||
log.info(`Analyzing task complexity with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Check if required paths were provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('analyzeTaskComplexityDirect called without tasksJsonPath');
|
||||
return {
|
||||
@@ -51,7 +50,6 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
// Use the provided paths
|
||||
const tasksPath = tasksJsonPath;
|
||||
const resolvedOutputPath = outputPath;
|
||||
|
||||
@@ -59,78 +57,91 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
||||
log.info(`Output report will be saved to: ${resolvedOutputPath}`);
|
||||
|
||||
if (research) {
|
||||
log.info('Using Perplexity AI for research-backed complexity analysis');
|
||||
log.info('Using research role for complexity analysis');
|
||||
}
|
||||
|
||||
// Create options object for analyzeTaskComplexity using provided paths
|
||||
// Prepare options for the core function
|
||||
const options = {
|
||||
file: tasksPath,
|
||||
output: resolvedOutputPath,
|
||||
model: model,
|
||||
// model: model, // No longer needed
|
||||
threshold: threshold,
|
||||
research: research === true
|
||||
research: research === true // Ensure boolean
|
||||
};
|
||||
// --- End Initial Checks ---
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
// --- Silent Mode and Logger Wrapper (remain the same) ---
|
||||
const wasSilent = isSilentMode();
|
||||
if (!wasSilent) {
|
||||
enableSilentMode();
|
||||
}
|
||||
|
||||
// Create a logWrapper that matches the expected mcpLog interface as specified in utilities.mdc
|
||||
const logWrapper = {
|
||||
info: (message, ...args) => log.info(message, ...args),
|
||||
warn: (message, ...args) => log.warn(message, ...args),
|
||||
error: (message, ...args) => log.error(message, ...args),
|
||||
debug: (message, ...args) => log.debug && log.debug(message, ...args),
|
||||
success: (message, ...args) => log.info(message, ...args) // Map success to info
|
||||
};
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
let report; // To store the result from the core function
|
||||
|
||||
try {
|
||||
// Call the core function with session and logWrapper as mcpLog
|
||||
await analyzeTaskComplexity(options, {
|
||||
session,
|
||||
mcpLog: logWrapper // Use the wrapper instead of passing log directly
|
||||
// --- Call Core Function (Updated Context Passing) ---
|
||||
// Call the core function, passing options and the context object { session, mcpLog }
|
||||
report = await analyzeTaskComplexity(options, {
|
||||
session, // Pass the session object
|
||||
mcpLog // Pass the logger wrapper
|
||||
});
|
||||
// --- End Core Function Call ---
|
||||
} catch (error) {
|
||||
log.error(`Error in analyzeTaskComplexity: ${error.message}`);
|
||||
log.error(
|
||||
`Error in analyzeTaskComplexity core function: ${error.message}`
|
||||
);
|
||||
// Restore logging if we changed it
|
||||
if (!wasSilent && isSilentMode()) {
|
||||
disableSilentMode();
|
||||
}
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'ANALYZE_ERROR',
|
||||
message: `Error running complexity analysis: ${error.message}`
|
||||
code: 'ANALYZE_CORE_ERROR', // More specific error code
|
||||
message: `Error running core complexity analysis: ${error.message}`
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
// Always restore normal logging in finally block, but only if we enabled it
|
||||
if (!wasSilent) {
|
||||
// Always restore normal logging in finally block if we enabled silent mode
|
||||
if (!wasSilent && isSilentMode()) {
|
||||
disableSilentMode();
|
||||
}
|
||||
}
|
||||
|
||||
// Verify the report file was created
|
||||
// --- Result Handling (remains largely the same) ---
|
||||
// Verify the report file was created (core function writes it)
|
||||
if (!fs.existsSync(resolvedOutputPath)) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'ANALYZE_ERROR',
|
||||
message: 'Analysis completed but no report file was created'
|
||||
code: 'ANALYZE_REPORT_MISSING', // Specific code
|
||||
message:
|
||||
'Analysis completed but no report file was created at the expected path.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// The core function now returns the report object directly
|
||||
if (!report || !report.complexityAnalysis) {
|
||||
log.error(
|
||||
'Core analyzeTaskComplexity function did not return a valid report object.'
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INVALID_CORE_RESPONSE',
|
||||
message: 'Core analysis function returned an invalid response.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Read the report file
|
||||
let report;
|
||||
try {
|
||||
report = JSON.parse(fs.readFileSync(resolvedOutputPath, 'utf8'));
|
||||
const analysisArray = report.complexityAnalysis; // Already an array
|
||||
|
||||
// Important: Handle different report formats
|
||||
// The core function might return an array or an object with a complexityAnalysis property
|
||||
const analysisArray = Array.isArray(report)
|
||||
? report
|
||||
: report.complexityAnalysis || [];
|
||||
|
||||
// Count tasks by complexity
|
||||
// Count tasks by complexity (remains the same)
|
||||
const highComplexityTasks = analysisArray.filter(
|
||||
(t) => t.complexityScore >= 8
|
||||
).length;
|
||||
@@ -152,29 +163,33 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
||||
mediumComplexityTasks,
|
||||
lowComplexityTasks
|
||||
}
|
||||
// Include the full report data if needed by the client
|
||||
// fullReport: report
|
||||
}
|
||||
};
|
||||
} catch (parseError) {
|
||||
log.error(`Error parsing report file: ${parseError.message}`);
|
||||
// Should not happen if core function returns object, but good safety check
|
||||
log.error(`Internal error processing report data: ${parseError.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'REPORT_PARSE_ERROR',
|
||||
message: `Error parsing complexity report: ${parseError.message}`
|
||||
code: 'REPORT_PROCESS_ERROR',
|
||||
message: `Internal error processing complexity report: ${parseError.message}`
|
||||
}
|
||||
};
|
||||
}
|
||||
// --- End Result Handling ---
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
// Catch errors from initial checks or path resolution
|
||||
// Make sure to restore normal logging if silent mode was enabled
|
||||
if (isSilentMode()) {
|
||||
disableSilentMode();
|
||||
}
|
||||
|
||||
log.error(`Error in analyzeTaskComplexityDirect: ${error.message}`);
|
||||
log.error(`Error in analyzeTaskComplexityDirect setup: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'CORE_FUNCTION_ERROR',
|
||||
code: 'DIRECT_FUNCTION_SETUP_ERROR',
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
|
||||
@@ -9,7 +9,6 @@ import {
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { getCachedOrExecute } from '../../tools/utils.js';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for displaying the complexity report with error handling and caching.
|
||||
|
||||
@@ -5,138 +5,85 @@
|
||||
import { expandAllTasks } from '../../../../scripts/modules/task-manager.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode,
|
||||
isSilentMode
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { getAnthropicClientForMCP } from '../utils/ai-client-utils.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Expand all pending tasks with subtasks
|
||||
* Expand all pending tasks with subtasks (Direct Function Wrapper)
|
||||
* @param {Object} args - Function arguments
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
|
||||
* @param {number|string} [args.num] - Number of subtasks to generate
|
||||
* @param {boolean} [args.research] - Enable Perplexity AI for research-backed subtask generation
|
||||
* @param {boolean} [args.research] - Enable research-backed subtask generation
|
||||
* @param {string} [args.prompt] - Additional context to guide subtask generation
|
||||
* @param {boolean} [args.force] - Force regeneration of subtasks for tasks that already have them
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} log - Logger object from FastMCP
|
||||
* @param {Object} context - Context object containing session
|
||||
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
|
||||
*/
|
||||
export async function expandAllTasksDirect(args, log, context = {}) {
|
||||
const { session } = context; // Only extract session, not reportProgress
|
||||
const { session } = context; // Extract session
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, num, research, prompt, force } = args;
|
||||
|
||||
try {
|
||||
log.info(`Expanding all tasks with args: ${JSON.stringify(args)}`);
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('expandAllTasksDirect called without tasksJsonPath');
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Enable silent mode early to prevent any console output
|
||||
enableSilentMode();
|
||||
|
||||
try {
|
||||
// Remove internal path finding
|
||||
/*
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
*/
|
||||
// Use provided path
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
// Parse parameters
|
||||
const numSubtasks = num ? parseInt(num, 10) : undefined;
|
||||
const useResearch = research === true;
|
||||
const additionalContext = prompt || '';
|
||||
const forceFlag = force === true;
|
||||
|
||||
log.info(
|
||||
`Expanding all tasks with ${numSubtasks || 'default'} subtasks each...`
|
||||
);
|
||||
|
||||
if (useResearch) {
|
||||
log.info('Using Perplexity AI for research-backed subtask generation');
|
||||
|
||||
// Initialize AI client for research-backed expansion
|
||||
try {
|
||||
await getAnthropicClientForMCP(session, log);
|
||||
} catch (error) {
|
||||
// Ensure silent mode is disabled before returning error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Failed to initialize AI client: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'AI_CLIENT_ERROR',
|
||||
message: `Cannot initialize AI client: ${error.message}`
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalContext) {
|
||||
log.info(`Additional context: "${additionalContext}"`);
|
||||
}
|
||||
if (forceFlag) {
|
||||
log.info('Force regeneration of subtasks is enabled');
|
||||
}
|
||||
|
||||
// Call the core function with session context for AI operations
|
||||
// and outputFormat as 'json' to prevent UI elements
|
||||
const result = await expandAllTasks(
|
||||
tasksPath,
|
||||
numSubtasks,
|
||||
useResearch,
|
||||
additionalContext,
|
||||
forceFlag,
|
||||
{ mcpLog: log, session },
|
||||
'json' // Use JSON output format to prevent UI elements
|
||||
);
|
||||
|
||||
// The expandAllTasks function now returns a result object
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: 'Successfully expanded all pending tasks with subtasks',
|
||||
details: {
|
||||
numSubtasks: numSubtasks,
|
||||
research: useResearch,
|
||||
prompt: additionalContext,
|
||||
force: forceFlag,
|
||||
tasksExpanded: result.expandedCount,
|
||||
totalEligibleTasks: result.tasksToExpand
|
||||
}
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
// Restore normal logging in finally block to ensure it runs even if there's an error
|
||||
disableSilentMode();
|
||||
}
|
||||
} catch (error) {
|
||||
// Ensure silent mode is disabled if an error occurs
|
||||
if (isSilentMode()) {
|
||||
disableSilentMode();
|
||||
}
|
||||
|
||||
log.error(`Error in expandAllTasksDirect: ${error.message}`);
|
||||
if (!tasksJsonPath) {
|
||||
log.error('expandAllTasksDirect called without tasksJsonPath');
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'CORE_FUNCTION_ERROR',
|
||||
message: error.message
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
enableSilentMode(); // Enable silent mode for the core function call
|
||||
try {
|
||||
log.info(
|
||||
`Calling core expandAllTasks with args: ${JSON.stringify({ num, research, prompt, force })}`
|
||||
);
|
||||
|
||||
// Parse parameters (ensure correct types)
|
||||
const numSubtasks = num ? parseInt(num, 10) : undefined;
|
||||
const useResearch = research === true;
|
||||
const additionalContext = prompt || '';
|
||||
const forceFlag = force === true;
|
||||
|
||||
// Call the core function, passing options and the context object { session, mcpLog }
|
||||
const result = await expandAllTasks(
|
||||
tasksJsonPath,
|
||||
numSubtasks,
|
||||
useResearch,
|
||||
additionalContext,
|
||||
forceFlag,
|
||||
{ session, mcpLog }
|
||||
);
|
||||
|
||||
// Core function now returns a summary object
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: `Expand all operation completed. Expanded: ${result.expandedCount}, Failed: ${result.failedCount}, Skipped: ${result.skippedCount}`,
|
||||
details: result // Include the full result details
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Log the error using the MCP logger
|
||||
log.error(`Error during core expandAllTasks execution: ${error.message}`);
|
||||
// Optionally log stack trace if available and debug enabled
|
||||
// if (error.stack && log.debug) { log.debug(error.stack); }
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'CORE_FUNCTION_ERROR', // Or a more specific code if possible
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
disableSilentMode(); // IMPORTANT: Ensure silent mode is always disabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Direct function implementation for expanding a task into subtasks
|
||||
*/
|
||||
|
||||
import { expandTask } from '../../../../scripts/modules/task-manager.js';
|
||||
import expandTask from '../../../../scripts/modules/task-manager/expand-task.js';
|
||||
import {
|
||||
readJSON,
|
||||
writeJSON,
|
||||
@@ -11,12 +11,9 @@ import {
|
||||
disableSilentMode,
|
||||
isSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import {
|
||||
getAnthropicClientForMCP,
|
||||
getModelConfig
|
||||
} from '../utils/ai-client-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.
|
||||
@@ -25,15 +22,16 @@ import fs from 'fs';
|
||||
* @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 Perplexity AI for research-backed subtask generation.
|
||||
* @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 {Object} log - Logger object
|
||||
* @param {Object} context - Context object containing session and reportProgress
|
||||
* @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 }, fromCache: boolean }
|
||||
*/
|
||||
export async function expandTaskDirect(args, log, context = {}) {
|
||||
const { session } = context;
|
||||
const { session } = context; // Extract session
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, id, num, research, prompt, force } = args;
|
||||
|
||||
@@ -85,28 +83,9 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
const additionalContext = prompt || '';
|
||||
const forceFlag = force === true;
|
||||
|
||||
// Initialize AI client if needed (for expandTask function)
|
||||
try {
|
||||
// This ensures the AI client is available by checking it
|
||||
if (useResearch) {
|
||||
log.info('Verifying AI client for research-backed expansion');
|
||||
await getAnthropicClientForMCP(session, log);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`Failed to initialize AI client: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'AI_CLIENT_ERROR',
|
||||
message: `Cannot initialize AI client: ${error.message}`
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
log.info(
|
||||
`[expandTaskDirect] Expanding task ${taskId} into ${numSubtasks || 'default'} subtasks. Research: ${useResearch}`
|
||||
`[expandTaskDirect] Expanding task ${taskId} into ${numSubtasks || 'default'} subtasks. Research: ${useResearch}, Force: ${forceFlag}`
|
||||
);
|
||||
|
||||
// Read tasks data
|
||||
@@ -202,23 +181,27 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
// Save tasks.json with potentially empty subtasks array
|
||||
writeJSON(tasksPath, data);
|
||||
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
// Process the request
|
||||
try {
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
const wasSilent = isSilentMode();
|
||||
if (!wasSilent) enableSilentMode();
|
||||
|
||||
// Call expandTask with session context to ensure AI client is properly initialized
|
||||
// Call the core expandTask function with the wrapped logger
|
||||
const result = await expandTask(
|
||||
tasksPath,
|
||||
taskId,
|
||||
numSubtasks,
|
||||
useResearch,
|
||||
additionalContext,
|
||||
{ mcpLog: log, session } // Only pass mcpLog and session, NOT reportProgress
|
||||
{ mcpLog, session }
|
||||
);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
if (!wasSilent && isSilentMode()) disableSilentMode();
|
||||
|
||||
// Read the updated data
|
||||
const updatedData = readJSON(tasksPath);
|
||||
@@ -244,7 +227,7 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
if (!wasSilent && isSilentMode()) disableSilentMode();
|
||||
|
||||
log.error(`Error expanding task: ${error.message}`);
|
||||
return {
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import path from 'path';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for generateTaskFiles with error handling.
|
||||
|
||||
@@ -10,7 +10,7 @@ import os from 'os'; // Import os module for home directory check
|
||||
/**
|
||||
* Direct function wrapper for initializing a project.
|
||||
* Derives target directory from session, sets CWD, and calls core init logic.
|
||||
* @param {object} args - Arguments containing project details and options (projectName, projectDescription, yes, etc.)
|
||||
* @param {object} args - Arguments containing initialization options (addAliases, skipInstall, yes, projectRoot)
|
||||
* @param {object} log - The FastMCP logger instance.
|
||||
* @param {object} context - The context object, must contain { session }.
|
||||
* @returns {Promise<{success: boolean, data?: any, error?: {code: string, message: string}}>} - Standard result object.
|
||||
@@ -92,12 +92,8 @@ export async function initializeProjectDirect(args, log, context = {}) {
|
||||
try {
|
||||
// Always force yes: true when called via MCP to avoid interactive prompts
|
||||
const options = {
|
||||
name: args.projectName,
|
||||
description: args.projectDescription,
|
||||
version: args.projectVersion,
|
||||
author: args.authorName,
|
||||
skipInstall: args.skipInstall,
|
||||
aliases: args.addAliases,
|
||||
skipInstall: args.skipInstall,
|
||||
yes: true // Force yes mode
|
||||
};
|
||||
|
||||
|
||||
121
mcp-server/src/core/direct-functions/models.js
Normal file
121
mcp-server/src/core/direct-functions/models.js
Normal file
@@ -0,0 +1,121 @@
|
||||
/**
|
||||
* models.js
|
||||
* Direct function for managing AI model configurations via MCP
|
||||
*/
|
||||
|
||||
import {
|
||||
getModelConfiguration,
|
||||
getAvailableModelsList,
|
||||
setModel
|
||||
} from '../../../../scripts/modules/task-manager/models.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Get or update model configuration
|
||||
* @param {Object} args - Arguments passed by the MCP tool
|
||||
* @param {Object} log - MCP logger
|
||||
* @param {Object} context - MCP context (contains session)
|
||||
* @returns {Object} Result object with success, data/error fields
|
||||
*/
|
||||
export async function modelsDirect(args, log, context = {}) {
|
||||
const { session } = context;
|
||||
const { projectRoot } = args; // Extract projectRoot from args
|
||||
|
||||
// Create a logger wrapper that the core functions can use
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
log.info(`Executing models_direct with args: ${JSON.stringify(args)}`);
|
||||
log.info(`Using project root: ${projectRoot}`);
|
||||
|
||||
// Validate flags: cannot use both openrouter and ollama simultaneously
|
||||
if (args.openrouter && args.ollama) {
|
||||
log.error(
|
||||
'Error: Cannot use both openrouter and ollama flags simultaneously.'
|
||||
);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INVALID_ARGS',
|
||||
message: 'Cannot use both openrouter and ollama flags simultaneously.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
enableSilentMode();
|
||||
|
||||
try {
|
||||
// Check for the listAvailableModels flag
|
||||
if (args.listAvailableModels === true) {
|
||||
return await getAvailableModelsList({
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot // Pass projectRoot to function
|
||||
});
|
||||
}
|
||||
|
||||
// Handle setting a specific model
|
||||
if (args.setMain) {
|
||||
return await setModel('main', args.setMain, {
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot, // Pass projectRoot to function
|
||||
providerHint: args.openrouter
|
||||
? 'openrouter'
|
||||
: args.ollama
|
||||
? 'ollama'
|
||||
: undefined // Pass hint
|
||||
});
|
||||
}
|
||||
|
||||
if (args.setResearch) {
|
||||
return await setModel('research', args.setResearch, {
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot, // Pass projectRoot to function
|
||||
providerHint: args.openrouter
|
||||
? 'openrouter'
|
||||
: args.ollama
|
||||
? 'ollama'
|
||||
: undefined // Pass hint
|
||||
});
|
||||
}
|
||||
|
||||
if (args.setFallback) {
|
||||
return await setModel('fallback', args.setFallback, {
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot, // Pass projectRoot to function
|
||||
providerHint: args.openrouter
|
||||
? 'openrouter'
|
||||
: args.ollama
|
||||
? 'ollama'
|
||||
: undefined // Pass hint
|
||||
});
|
||||
}
|
||||
|
||||
// Default action: get current configuration
|
||||
return await getModelConfiguration({
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot // Pass projectRoot to function
|
||||
});
|
||||
} finally {
|
||||
disableSilentMode();
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`Error in models_direct: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'DIRECT_FUNCTION_ERROR',
|
||||
message: error.message,
|
||||
details: error.stack
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,48 +5,27 @@
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os'; // Import os module for home directory check
|
||||
import { parsePRD } from '../../../../scripts/modules/task-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import {
|
||||
getAnthropicClientForMCP,
|
||||
getModelConfig
|
||||
} from '../utils/ai-client-utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for parsing PRD documents and generating tasks.
|
||||
*
|
||||
* @param {Object} args - Command arguments containing input, numTasks or tasks, and output options.
|
||||
* @param {Object} args - Command arguments containing projectRoot, input, output, numTasks options.
|
||||
* @param {Object} log - Logger object.
|
||||
* @param {Object} context - Context object containing session data.
|
||||
* @returns {Promise<Object>} - Result object with success status and data/error information.
|
||||
*/
|
||||
export async function parsePRDDirect(args, log, context = {}) {
|
||||
const { session } = context; // Only extract session, not reportProgress
|
||||
const { session } = context; // Only extract session
|
||||
|
||||
try {
|
||||
log.info(`Parsing PRD document with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Initialize AI client for PRD parsing
|
||||
let aiClient;
|
||||
try {
|
||||
aiClient = getAnthropicClientForMCP(session, log);
|
||||
} catch (error) {
|
||||
log.error(`Failed to initialize AI client: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'AI_CLIENT_ERROR',
|
||||
message: `Cannot initialize AI client: ${error.message}`
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// Validate required parameters
|
||||
if (!args.projectRoot) {
|
||||
const errorMessage = 'Project root is required for parsePRDDirect';
|
||||
@@ -57,7 +36,6 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
if (!args.input) {
|
||||
const errorMessage = 'Input file path is required for parsePRDDirect';
|
||||
log.error(errorMessage);
|
||||
@@ -67,7 +45,6 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
if (!args.output) {
|
||||
const errorMessage = 'Output file path is required for parsePRDDirect';
|
||||
log.error(errorMessage);
|
||||
@@ -124,21 +101,22 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the append flag from args
|
||||
const append = Boolean(args.append) === true;
|
||||
|
||||
// Log key parameters including append flag
|
||||
log.info(
|
||||
`Preparing to parse PRD from ${inputPath} and output to ${outputPath} with ${numTasks} tasks`
|
||||
`Preparing to parse PRD from ${inputPath} and output to ${outputPath} with ${numTasks} tasks, append mode: ${append}`
|
||||
);
|
||||
|
||||
// Create the logger wrapper for proper logging in the core function
|
||||
const logWrapper = {
|
||||
info: (message, ...args) => log.info(message, ...args),
|
||||
warn: (message, ...args) => log.warn(message, ...args),
|
||||
error: (message, ...args) => log.error(message, ...args),
|
||||
debug: (message, ...args) => log.debug && log.debug(message, ...args),
|
||||
success: (message, ...args) => log.info(message, ...args) // Map success to info
|
||||
};
|
||||
// --- Logger Wrapper ---
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
// Get model config from session
|
||||
const modelConfig = getModelConfig(session);
|
||||
// Prepare options for the core function
|
||||
const options = {
|
||||
mcpLog,
|
||||
session
|
||||
};
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
@@ -151,13 +129,14 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
}
|
||||
|
||||
// Execute core parsePRD function with AI client
|
||||
await parsePRD(
|
||||
const tasksDataResult = await parsePRD(
|
||||
inputPath,
|
||||
outputPath,
|
||||
numTasks,
|
||||
{
|
||||
mcpLog: logWrapper,
|
||||
session
|
||||
session,
|
||||
append
|
||||
},
|
||||
aiClient,
|
||||
modelConfig
|
||||
@@ -167,16 +146,24 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
// to return it to the caller
|
||||
if (fs.existsSync(outputPath)) {
|
||||
const tasksData = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
|
||||
log.info(
|
||||
`Successfully parsed PRD and generated ${tasksData.tasks?.length || 0} tasks`
|
||||
);
|
||||
const actionVerb = append ? 'appended' : 'generated';
|
||||
const message = `Successfully ${actionVerb} ${tasksData.tasks?.length || 0} tasks from PRD`;
|
||||
|
||||
if (!tasksDataResult || !tasksDataResult.tasks || !tasksData) {
|
||||
throw new Error(
|
||||
'Core parsePRD function did not return valid task data.'
|
||||
);
|
||||
}
|
||||
|
||||
log.info(message);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: `Successfully generated ${tasksData.tasks?.length || 0} tasks from PRD`,
|
||||
taskCount: tasksData.tasks?.length || 0,
|
||||
outputPath
|
||||
message,
|
||||
taskCount: tasksDataResult.tasks?.length || 0,
|
||||
outputPath,
|
||||
appended: append
|
||||
},
|
||||
fromCache: false // This operation always modifies state and should never be cached
|
||||
};
|
||||
@@ -201,7 +188,7 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'PARSE_PRD_ERROR',
|
||||
code: error.code || 'PARSE_PRD_ERROR', // Use error code if available
|
||||
message: error.message || 'Unknown error parsing PRD'
|
||||
},
|
||||
fromCache: false
|
||||
|
||||
@@ -3,18 +3,23 @@
|
||||
* Direct function implementation for removing a task
|
||||
*/
|
||||
|
||||
import { removeTask } from '../../../../scripts/modules/task-manager.js';
|
||||
import {
|
||||
removeTask,
|
||||
taskExists
|
||||
} from '../../../../scripts/modules/task-manager.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
disableSilentMode,
|
||||
readJSON
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for removeTask with error handling.
|
||||
* Supports removing multiple tasks at once with comma-separated IDs.
|
||||
*
|
||||
* @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 or subtask to remove.
|
||||
* @param {string} args.id - The ID(s) of the task(s) or subtask(s) to remove (comma-separated for multiple).
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: false }
|
||||
*/
|
||||
@@ -36,8 +41,7 @@ export async function removeTaskDirect(args, log) {
|
||||
}
|
||||
|
||||
// Validate task ID parameter
|
||||
const taskId = id;
|
||||
if (!taskId) {
|
||||
if (!id) {
|
||||
log.error('Task ID is required');
|
||||
return {
|
||||
success: false,
|
||||
@@ -49,46 +53,103 @@ export async function removeTaskDirect(args, log) {
|
||||
};
|
||||
}
|
||||
|
||||
// Skip confirmation in the direct function since it's handled by the client
|
||||
log.info(`Removing task with ID: ${taskId} from ${tasksJsonPath}`);
|
||||
// Split task IDs if comma-separated
|
||||
const taskIdArray = id.split(',').map((taskId) => taskId.trim());
|
||||
|
||||
try {
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
log.info(
|
||||
`Removing ${taskIdArray.length} task(s) with ID(s): ${taskIdArray.join(', ')} from ${tasksJsonPath}`
|
||||
);
|
||||
|
||||
// Call the core removeTask function using the provided path
|
||||
const result = await removeTask(tasksJsonPath, taskId);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
log.info(`Successfully removed task: ${taskId}`);
|
||||
|
||||
// Return the result
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: result.message,
|
||||
taskId: taskId,
|
||||
tasksPath: tasksJsonPath,
|
||||
removedTask: result.removedTask
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error removing task: ${error.message}`);
|
||||
// Validate all task IDs exist before proceeding
|
||||
const data = readJSON(tasksJsonPath);
|
||||
if (!data || !data.tasks) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'REMOVE_TASK_ERROR',
|
||||
message: error.message || 'Failed to remove task'
|
||||
code: 'INVALID_TASKS_FILE',
|
||||
message: `No valid tasks found in ${tasksJsonPath}`
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
const invalidTasks = taskIdArray.filter(
|
||||
(taskId) => !taskExists(data.tasks, taskId)
|
||||
);
|
||||
|
||||
if (invalidTasks.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INVALID_TASK_ID',
|
||||
message: `The following tasks were not found: ${invalidTasks.join(', ')}`
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// Remove tasks one by one
|
||||
const results = [];
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
try {
|
||||
for (const taskId of taskIdArray) {
|
||||
try {
|
||||
const result = await removeTask(tasksJsonPath, taskId);
|
||||
results.push({
|
||||
taskId,
|
||||
success: true,
|
||||
message: result.message,
|
||||
removedTask: result.removedTask
|
||||
});
|
||||
log.info(`Successfully removed task: ${taskId}`);
|
||||
} catch (error) {
|
||||
results.push({
|
||||
taskId,
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
log.error(`Error removing task ${taskId}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
}
|
||||
|
||||
// Check if all tasks were successfully removed
|
||||
const successfulRemovals = results.filter((r) => r.success);
|
||||
const failedRemovals = results.filter((r) => !r.success);
|
||||
|
||||
if (successfulRemovals.length === 0) {
|
||||
// All removals failed
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'REMOVE_TASK_ERROR',
|
||||
message: 'Failed to remove any tasks',
|
||||
details: failedRemovals
|
||||
.map((r) => `${r.taskId}: ${r.error}`)
|
||||
.join('; ')
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// At least some tasks were removed successfully
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
totalTasks: taskIdArray.length,
|
||||
successful: successfulRemovals.length,
|
||||
failed: failedRemovals.length,
|
||||
results: results,
|
||||
tasksPath: tasksJsonPath
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
} catch (error) {
|
||||
// Ensure silent mode is disabled even if an outer error occurs
|
||||
disableSilentMode();
|
||||
|
||||
@@ -21,12 +21,13 @@ import {
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
|
||||
* @param {string} args.id - The ID of the task or subtask to show.
|
||||
* @param {string} args.reportPath - Explicit path to the complexity report file.
|
||||
* @param {string} [args.status] - Optional status to filter subtasks by.
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<Object>} - Task details result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
|
||||
*/
|
||||
export async function showTaskDirect(args, log) {
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, reportPath, id } = args;
|
||||
const { tasksJsonPath, reportPath, id, status } = args;
|
||||
|
||||
if (!tasksJsonPath) {
|
||||
log.error('showTaskDirect called without tasksJsonPath');
|
||||
@@ -54,8 +55,8 @@ export async function showTaskDirect(args, log) {
|
||||
};
|
||||
}
|
||||
|
||||
// Generate cache key using the provided task path and ID
|
||||
const cacheKey = `showTask:${tasksJsonPath}:${taskId}:${reportPath}`;
|
||||
// Generate cache key using the provided task path, ID, report path, and status filter
|
||||
const cacheKey = `showTask:${tasksJsonPath}:${taskId}:${reportPath}:${status || 'all'}`;
|
||||
|
||||
// Define the action function to be executed on cache miss
|
||||
const coreShowTaskAction = async () => {
|
||||
@@ -64,7 +65,7 @@ export async function showTaskDirect(args, log) {
|
||||
enableSilentMode();
|
||||
|
||||
log.info(
|
||||
`Retrieving task details for ID: ${taskId} from ${tasksJsonPath}`
|
||||
`Retrieving task details for ID: ${taskId} from ${tasksJsonPath}${status ? ` (filtering by status: ${status})` : ''}`
|
||||
);
|
||||
|
||||
// Read tasks data using the provided path
|
||||
@@ -83,8 +84,13 @@ export async function showTaskDirect(args, log) {
|
||||
// Read the complexity report
|
||||
const complexityReport = readComplexityReport(reportPath);
|
||||
|
||||
// Find the specific task
|
||||
const task = findTaskById(data.tasks, taskId, complexityReport);
|
||||
// Find the specific task, passing the status filter
|
||||
const { task, originalSubtaskCount } = findTaskById(
|
||||
data.tasks,
|
||||
taskId,
|
||||
complexityReport,
|
||||
status
|
||||
);
|
||||
|
||||
if (!task) {
|
||||
disableSilentMode(); // Disable before returning
|
||||
@@ -92,7 +98,7 @@ export async function showTaskDirect(args, log) {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'TASK_NOT_FOUND',
|
||||
message: `Task with ID ${taskId} not found`
|
||||
message: `Task with ID ${taskId} not found${status ? ` or no subtasks match status '${status}'` : ''}`
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -100,13 +106,16 @@ 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}`);
|
||||
// Return the task data, the original subtask count (if applicable),
|
||||
// and the full tasks array for reference (needed for formatDependenciesWithStatus function in UI)
|
||||
log.info(
|
||||
`Successfully found task ${taskId}${status ? ` (with status filter: ${status})` : ''}`
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
task,
|
||||
originalSubtaskCount,
|
||||
allTasks: data.tasks
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,10 +8,7 @@ import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import {
|
||||
getAnthropicClientForMCP,
|
||||
getPerplexityClientForMCP
|
||||
} from '../utils/ai-client-utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for updateSubtaskById with error handling.
|
||||
@@ -95,40 +92,12 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
`Updating subtask with ID ${subtaskIdStr} with prompt "${prompt}" and research: ${useResearch}`
|
||||
);
|
||||
|
||||
// Initialize the appropriate AI client based on research flag
|
||||
try {
|
||||
if (useResearch) {
|
||||
// Initialize Perplexity client
|
||||
await getPerplexityClientForMCP(session);
|
||||
} else {
|
||||
// Initialize Anthropic client
|
||||
await getAnthropicClientForMCP(session);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`AI client initialization error: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'AI_CLIENT_ERROR',
|
||||
message: error.message || 'Failed to initialize AI client'
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create a logger wrapper object to handle logging without breaking the mcpLog[level] calls
|
||||
// This ensures outputFormat is set to 'json' while still supporting proper logging
|
||||
const logWrapper = {
|
||||
info: (message) => log.info(message),
|
||||
warn: (message) => log.warn(message),
|
||||
error: (message) => log.error(message),
|
||||
debug: (message) => log.debug && log.debug(message),
|
||||
success: (message) => log.info(message) // Map success to info if needed
|
||||
};
|
||||
// Create the logger wrapper using the utility function
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
// Execute core updateSubtaskById function
|
||||
// Pass both session and logWrapper as mcpLog to ensure outputFormat is 'json'
|
||||
@@ -139,7 +108,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
useResearch,
|
||||
{
|
||||
session,
|
||||
mcpLog: logWrapper
|
||||
mcpLog
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
@@ -8,10 +8,7 @@ import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import {
|
||||
getAnthropicClientForMCP,
|
||||
getPerplexityClientForMCP
|
||||
} from '../utils/ai-client-utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for updateTaskById with error handling.
|
||||
@@ -92,28 +89,6 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
// Get research flag
|
||||
const useResearch = research === true;
|
||||
|
||||
// Initialize appropriate AI client based on research flag
|
||||
let aiClient;
|
||||
try {
|
||||
if (useResearch) {
|
||||
log.info('Using Perplexity AI for research-backed task update');
|
||||
aiClient = await getPerplexityClientForMCP(session, log);
|
||||
} else {
|
||||
log.info('Using Claude AI for task update');
|
||||
aiClient = getAnthropicClientForMCP(session, log);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`Failed to initialize AI client: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'AI_CLIENT_ERROR',
|
||||
message: `Cannot initialize AI client: ${error.message}`
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
log.info(
|
||||
`Updating task with ID ${taskId} with prompt "${prompt}" and research: ${useResearch}`
|
||||
);
|
||||
@@ -122,14 +97,8 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Create a logger wrapper that matches what updateTaskById expects
|
||||
const logWrapper = {
|
||||
info: (message) => log.info(message),
|
||||
warn: (message) => log.warn(message),
|
||||
error: (message) => log.error(message),
|
||||
debug: (message) => log.debug && log.debug(message),
|
||||
success: (message) => log.info(message) // Map success to info since many loggers don't have success
|
||||
};
|
||||
// Create the logger wrapper using the utility function
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
// Execute core updateTaskById function with proper parameters
|
||||
await updateTaskById(
|
||||
@@ -138,7 +107,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
prompt,
|
||||
useResearch,
|
||||
{
|
||||
mcpLog: logWrapper, // Use our wrapper object that has the expected method structure
|
||||
mcpLog, // Pass the wrapped logger
|
||||
session
|
||||
},
|
||||
'json'
|
||||
|
||||
@@ -8,180 +8,123 @@ import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import {
|
||||
getAnthropicClientForMCP,
|
||||
getPerplexityClientForMCP
|
||||
} from '../utils/ai-client-utils.js';
|
||||
import { createLogWrapper } from '../../tools/utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for updating tasks based on new context/prompt.
|
||||
*
|
||||
* @param {Object} args - Command arguments containing fromId, prompt, useResearch and tasksJsonPath.
|
||||
* @param {Object} args - Command arguments containing from, prompt, research and tasksJsonPath.
|
||||
* @param {Object} log - Logger object.
|
||||
* @param {Object} context - Context object containing session data.
|
||||
* @returns {Promise<Object>} - Result object with success status and data/error information.
|
||||
*/
|
||||
export async function updateTasksDirect(args, log, context = {}) {
|
||||
const { session } = context; // Only extract session, not reportProgress
|
||||
const { session } = context; // Extract session
|
||||
const { tasksJsonPath, from, prompt, research } = args;
|
||||
|
||||
try {
|
||||
log.info(`Updating tasks with args: ${JSON.stringify(args)}`);
|
||||
// Create the standard logger wrapper
|
||||
const logWrapper = {
|
||||
info: (message, ...args) => log.info(message, ...args),
|
||||
warn: (message, ...args) => log.warn(message, ...args),
|
||||
error: (message, ...args) => log.error(message, ...args),
|
||||
debug: (message, ...args) => log.debug && log.debug(message, ...args),
|
||||
success: (message, ...args) => log.info(message, ...args)
|
||||
};
|
||||
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
const errorMessage = 'tasksJsonPath is required but was not provided.';
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// Check for the common mistake of using 'id' instead of 'from'
|
||||
if (args.id !== undefined && from === undefined) {
|
||||
const errorMessage =
|
||||
"You specified 'id' parameter but 'update' requires 'from' parameter. Use 'from' for this tool or use 'update_task' tool if you want to update a single task.";
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'PARAMETER_MISMATCH',
|
||||
message: errorMessage,
|
||||
suggestion:
|
||||
"Use 'from' parameter instead of 'id', or use the 'update_task' tool for single task updates"
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// Check required parameters
|
||||
if (!from) {
|
||||
const errorMessage =
|
||||
'No from ID specified. Please provide a task ID to start updating from.';
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_FROM_ID', message: errorMessage },
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
if (!prompt) {
|
||||
const errorMessage =
|
||||
'No prompt specified. Please provide a prompt with new context for task updates.';
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_PROMPT', message: errorMessage },
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// Parse fromId - handle both string and number values
|
||||
let fromId;
|
||||
if (typeof from === 'string') {
|
||||
fromId = parseInt(from, 10);
|
||||
if (isNaN(fromId)) {
|
||||
const errorMessage = `Invalid from ID: ${from}. Task ID must be a positive integer.`;
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'INVALID_FROM_ID', message: errorMessage },
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
} else {
|
||||
fromId = from;
|
||||
}
|
||||
|
||||
// Get research flag
|
||||
const useResearch = research === true;
|
||||
|
||||
// Initialize appropriate AI client based on research flag
|
||||
let aiClient;
|
||||
try {
|
||||
if (useResearch) {
|
||||
log.info('Using Perplexity AI for research-backed task updates');
|
||||
aiClient = await getPerplexityClientForMCP(session, log);
|
||||
} else {
|
||||
log.info('Using Claude AI for task updates');
|
||||
aiClient = getAnthropicClientForMCP(session, log);
|
||||
}
|
||||
} catch (error) {
|
||||
log.error(`Failed to initialize AI client: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'AI_CLIENT_ERROR',
|
||||
message: `Cannot initialize AI client: ${error.message}`
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
log.info(
|
||||
`Updating tasks from ID ${fromId} with prompt "${prompt}" and research: ${useResearch}`
|
||||
);
|
||||
|
||||
// Create the logger wrapper to ensure compatibility with core functions
|
||||
const logWrapper = {
|
||||
info: (message, ...args) => log.info(message, ...args),
|
||||
warn: (message, ...args) => log.warn(message, ...args),
|
||||
error: (message, ...args) => log.error(message, ...args),
|
||||
debug: (message, ...args) => log.debug && log.debug(message, ...args), // Handle optional debug
|
||||
success: (message, ...args) => log.info(message, ...args) // Map success to info if needed
|
||||
// --- Input Validation (Keep existing checks) ---
|
||||
if (!tasksJsonPath) {
|
||||
log.error('updateTasksDirect called without tasksJsonPath');
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_ARGUMENT', message: 'tasksJsonPath is required' },
|
||||
fromCache: false
|
||||
};
|
||||
|
||||
try {
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Execute core updateTasks function, passing the AI client and session
|
||||
await updateTasks(tasksJsonPath, fromId, prompt, useResearch, {
|
||||
mcpLog: logWrapper, // Pass the wrapper instead of the raw log object
|
||||
session
|
||||
});
|
||||
|
||||
// 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: tasksJsonPath,
|
||||
useResearch
|
||||
},
|
||||
fromCache: false // This operation always modifies state and should never be cached
|
||||
};
|
||||
} catch (error) {
|
||||
log.error(`Error updating tasks: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'UPDATE_TASKS_ERROR',
|
||||
message: error.message || 'Unknown error updating tasks'
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
} finally {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
}
|
||||
} catch (error) {
|
||||
// Ensure silent mode is disabled
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error updating tasks: ${error.message}`);
|
||||
}
|
||||
if (args.id !== undefined && from === undefined) {
|
||||
// Keep 'from' vs 'id' check
|
||||
const errorMessage =
|
||||
"Use 'from' parameter, not 'id', or use 'update_task' tool.";
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'PARAMETER_MISMATCH', message: errorMessage },
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
if (!from) {
|
||||
log.error('Missing from ID.');
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_FROM_ID', message: 'No from ID specified.' },
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
if (!prompt) {
|
||||
log.error('Missing prompt.');
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_PROMPT', message: 'No prompt specified.' },
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
let fromId;
|
||||
try {
|
||||
fromId = parseInt(from, 10);
|
||||
if (isNaN(fromId) || fromId <= 0) throw new Error();
|
||||
} catch {
|
||||
log.error(`Invalid from ID: ${from}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'UPDATE_TASKS_ERROR',
|
||||
message: error.message || 'Unknown error updating tasks'
|
||||
code: 'INVALID_FROM_ID',
|
||||
message: `Invalid from ID: ${from}. Must be a positive integer.`
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
const useResearch = research === true;
|
||||
// --- End Input Validation ---
|
||||
|
||||
log.info(`Updating tasks from ID ${fromId}. Research: ${useResearch}`);
|
||||
|
||||
enableSilentMode(); // Enable silent mode
|
||||
try {
|
||||
// Create logger wrapper using the utility
|
||||
const mcpLog = createLogWrapper(log);
|
||||
|
||||
// Execute core updateTasks function, passing session context
|
||||
await updateTasks(
|
||||
tasksJsonPath,
|
||||
fromId,
|
||||
prompt,
|
||||
useResearch,
|
||||
// Pass context with logger wrapper and session
|
||||
{ mcpLog, session },
|
||||
'json' // Explicitly request JSON format for MCP
|
||||
);
|
||||
|
||||
// Since updateTasks modifies file and doesn't return data, create success message
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: `Successfully initiated update for tasks from ID ${fromId} based on the prompt.`,
|
||||
fromId,
|
||||
tasksPath: tasksJsonPath,
|
||||
useResearch
|
||||
},
|
||||
fromCache: false // Modifies state
|
||||
};
|
||||
} catch (error) {
|
||||
log.error(`Error executing core updateTasks: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'UPDATE_TASKS_CORE_ERROR',
|
||||
message: error.message || 'Unknown error updating tasks'
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
} finally {
|
||||
disableSilentMode(); // Ensure silent mode is disabled
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user