fix merge conflicts to prep for merge with branch next

- Enhance E2E testing and LLM analysis report and:
  - Add --analyze-log flag to run_e2e.sh to re-run LLM analysis on existing logs.
  - Add test:e2e and analyze-log scripts to package.json for easier execution.

- Correct display errors and dependency validation output:
  - Update chalk usage in add-task.js to use bracket notation (chalk[color]) compatible with v5, resolving 'chalk.keyword is not a function' error.
  - Modify fix-dependencies command output to show red failure box with issue count instead of green success box when validation fails.

- Refactor interactive model setup:
  - Verify inclusion of 'No change' option during interactive model setup flow (task-master models --setup).

- Update model definitions:
  - Add max_tokens field for gpt-4o in supported-models.json.

- Remove unused scripts:
  - Delete prepare-package.js and rule-transformer.test.js.

Release candidate
This commit is contained in:
Eyal Toledano
2025-04-29 01:54:42 -04:00
48 changed files with 4744 additions and 1237 deletions

View File

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

View File

@@ -101,8 +101,12 @@ 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}`
);
// --- Logger Wrapper ---
@@ -117,29 +121,61 @@ export async function parsePRDDirect(args, log, context = {}) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
try {
// Execute core parsePRD function - It now handles AI internally
const tasksDataResult = await parsePRD(inputPath, numTasks, options);
// Check the result from the core function (assuming it might return data or null/undefined)
if (!tasksDataResult || !tasksDataResult.tasks) {
throw new Error(
'Core parsePRD function did not return valid task data.'
);
// Make sure the output directory exists
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
log.info(`Creating output directory: ${outputDir}`);
fs.mkdirSync(outputDir, { recursive: true });
}
log.info(
`Successfully parsed PRD and generated ${tasksDataResult.tasks?.length || 0} tasks`
// Execute core parsePRD function with AI client
const tasksDataResult = await parsePRD(
inputPath,
outputPath,
numTasks,
{
mcpLog: logWrapper,
session,
append
},
aiClient,
modelConfig
);
return {
success: true,
data: {
message: `Successfully generated ${tasksDataResult.tasks?.length || 0} tasks from PRD`,
taskCount: tasksDataResult.tasks?.length || 0,
outputPath
},
fromCache: false // This operation always modifies state
};
// 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)) {
const tasksData = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
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,
taskCount: tasksDataResult.tasks?.length || 0,
outputPath,
appended: append
},
fromCache: false // This operation always modifies state and should never be cached
};
} else {
const errorMessage = `Tasks file was not created at ${outputPath}`;
log.error(errorMessage);
return {
success: false,
error: { code: 'OUTPUT_FILE_NOT_CREATED', message: errorMessage },
fromCache: false
};
}
} finally {
// Always restore normal logging
disableSilentMode();

View File

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