Ralph Khreish 5da5b59bde
Fix/expand command tag corruption (#827)
* fix(expand): Fix tag corruption in expand command - Fix tag parameter passing through MCP expand-task flow - Add tag parameter to direct function and tool registration - Fix contextGatherer method name from _buildDependencyContext to _buildDependencyGraphs - Add comprehensive test coverage for tag handling in expand-task - Ensures tagged task structure is preserved during expansion - Prevents corruption when tag is undefined. Fixes expand command causing tag corruption in tagged task lists. All existing tests pass and new test coverage added.

* test(e2e): Add comprehensive tag-aware expand testing to verify tag corruption fix - Add new test section for feature-expand tag creation and testing - Verify tag preservation during expand, force expand, and expand --all operations - Test that master tag remains intact and feature-expand tag receives subtasks correctly - Fix file path references to use correct .taskmaster/tasks/tasks.json location - Fix config file check to use .taskmaster/config.json instead of .taskmasterconfig - All tag corruption verification tests pass successfully in E2E test

* fix(changeset): Update E2E test improvements changeset to properly reflect tag corruption fix verification

* chore(changeset): combine duplicate changesets for expand tag corruption fix

Merge eighty-breads-wonder.md into bright-llamas-enter.md to consolidate
the expand command fix and its comprehensive E2E testing enhancements
into a single changeset entry.

* Delete .changeset/eighty-breads-wonder.md

* Version Packages

* chore: fix package.json

* fix(expand): Enhance context handling in expandAllTasks function
- Added `tag` to context destructuring for better context management.
- Updated `readJSON` call to include `contextTag` for improved data integrity.
- Ensured the correct tag is passed during task expansion to prevent tag corruption.

---------

Co-authored-by: Parththipan Thaniperumkarunai <parththipan.thaniperumkarunai@milkmonkey.de>
Co-authored-by: Parthy <52548018+mm-parthy@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
2025-06-20 15:12:40 +02:00

219 lines
7.1 KiB
JavaScript

import { log, readJSON, isSilentMode, findProjectRoot } from '../utils.js';
import {
startLoadingIndicator,
stopLoadingIndicator,
displayAiUsageSummary
} from '../ui.js';
import expandTask from './expand-task.js';
import { getDebugFlag } from '../config-manager.js';
import { aggregateTelemetry } from '../utils.js';
import chalk from 'chalk';
import boxen from 'boxen';
/**
* Expand all eligible pending or in-progress tasks using the expandTask function.
* @param {string} tasksPath - Path to the tasks.json file
* @param {number} [numSubtasks] - Optional: Target number of subtasks per task.
* @param {boolean} [useResearch=false] - Whether to use the research AI role.
* @param {string} [additionalContext=''] - Optional additional context.
* @param {boolean} [force=false] - Force expansion even if tasks already have subtasks.
* @param {Object} context - Context object containing session and mcpLog.
* @param {Object} [context.session] - Session object from MCP.
* @param {Object} [context.mcpLog] - MCP logger object.
* @param {string} [outputFormat='text'] - Output format ('text' or 'json'). MCP calls should use 'json'.
* @returns {Promise<{success: boolean, expandedCount: number, failedCount: number, skippedCount: number, tasksToExpand: number, telemetryData: Array<Object>}>} - Result summary.
*/
async function expandAllTasks(
tasksPath,
numSubtasks, // Keep this signature, expandTask handles defaults
useResearch = false,
additionalContext = '',
force = false, // Keep force here for the filter logic
context = {},
outputFormat = 'text' // Assume text default for CLI
) {
const {
session,
mcpLog,
projectRoot: providedProjectRoot,
tag: contextTag
} = context;
const isMCPCall = !!mcpLog; // Determine if called from MCP
const projectRoot = providedProjectRoot || findProjectRoot();
if (!projectRoot) {
throw new Error('Could not determine project root directory');
}
// Use mcpLog if available, otherwise use the default console log wrapper respecting silent mode
const logger =
mcpLog ||
(outputFormat === 'json'
? {
// Basic logger for JSON output mode
info: (msg) => {},
warn: (msg) => {},
error: (msg) => console.error(`ERROR: ${msg}`), // Still log errors
debug: (msg) => {}
}
: {
// CLI logger respecting silent mode
info: (msg) => !isSilentMode() && log('info', msg),
warn: (msg) => !isSilentMode() && log('warn', msg),
error: (msg) => !isSilentMode() && log('error', msg),
debug: (msg) =>
!isSilentMode() && getDebugFlag(session) && log('debug', msg)
});
let loadingIndicator = null;
let expandedCount = 0;
let failedCount = 0;
let tasksToExpandCount = 0;
const allTelemetryData = []; // Still collect individual data first
if (!isMCPCall && outputFormat === 'text') {
loadingIndicator = startLoadingIndicator(
'Analyzing tasks for expansion...'
);
}
try {
logger.info(`Reading tasks from ${tasksPath}`);
const data = readJSON(tasksPath, projectRoot, contextTag);
if (!data || !data.tasks) {
throw new Error(`Invalid tasks data in ${tasksPath}`);
}
// --- Restore Original Filtering Logic ---
const tasksToExpand = data.tasks.filter(
(task) =>
(task.status === 'pending' || task.status === 'in-progress') && // Include 'in-progress'
(!task.subtasks || task.subtasks.length === 0 || force) // Check subtasks/force here
);
tasksToExpandCount = tasksToExpand.length; // Get the count from the filtered array
logger.info(`Found ${tasksToExpandCount} tasks eligible for expansion.`);
// --- End Restored Filtering Logic ---
if (loadingIndicator) {
stopLoadingIndicator(loadingIndicator, 'Analysis complete.');
}
if (tasksToExpandCount === 0) {
logger.info('No tasks eligible for expansion.');
// --- Fix: Restore success: true and add message ---
return {
success: true, // Indicate overall success despite no action
expandedCount: 0,
failedCount: 0,
skippedCount: 0,
tasksToExpand: 0,
telemetryData: allTelemetryData,
message: 'No tasks eligible for expansion.'
};
// --- End Fix ---
}
// Iterate over the already filtered tasks
for (const task of tasksToExpand) {
// Start indicator for individual task expansion in CLI mode
let taskIndicator = null;
if (!isMCPCall && outputFormat === 'text') {
taskIndicator = startLoadingIndicator(`Expanding task ${task.id}...`);
}
try {
// Call the refactored expandTask function AND capture result
const result = await expandTask(
tasksPath,
task.id,
numSubtasks,
useResearch,
additionalContext,
{ ...context, projectRoot, tag: data.tag || contextTag }, // Pass the whole context object with projectRoot and resolved tag
force
);
expandedCount++;
// Collect individual telemetry data
if (result && result.telemetryData) {
allTelemetryData.push(result.telemetryData);
}
if (taskIndicator) {
stopLoadingIndicator(taskIndicator, `Task ${task.id} expanded.`);
}
logger.info(`Successfully expanded task ${task.id}.`);
} catch (error) {
failedCount++;
if (taskIndicator) {
stopLoadingIndicator(
taskIndicator,
`Failed to expand task ${task.id}.`,
false
);
}
logger.error(`Failed to expand task ${task.id}: ${error.message}`);
// Continue to the next task
}
}
// --- AGGREGATION AND DISPLAY ---
logger.info(
`Expansion complete: ${expandedCount} expanded, ${failedCount} failed.`
);
// Aggregate the collected telemetry data
const aggregatedTelemetryData = aggregateTelemetry(
allTelemetryData,
'expand-all-tasks'
);
if (outputFormat === 'text') {
const summaryContent =
`${chalk.white.bold('Expansion Summary:')}\n\n` +
`${chalk.cyan('-')} Attempted: ${chalk.bold(tasksToExpandCount)}\n` +
`${chalk.green('-')} Expanded: ${chalk.bold(expandedCount)}\n` +
// Skipped count is always 0 now due to pre-filtering
`${chalk.gray('-')} Skipped: ${chalk.bold(0)}\n` +
`${chalk.red('-')} Failed: ${chalk.bold(failedCount)}`;
console.log(
boxen(summaryContent, {
padding: 1,
margin: { top: 1 },
borderColor: failedCount > 0 ? 'red' : 'green', // Red if failures, green otherwise
borderStyle: 'round'
})
);
}
if (outputFormat === 'text' && aggregatedTelemetryData) {
displayAiUsageSummary(aggregatedTelemetryData, 'cli');
}
// Return summary including the AGGREGATED telemetry data
return {
success: true,
expandedCount,
failedCount,
skippedCount: 0,
tasksToExpand: tasksToExpandCount,
telemetryData: aggregatedTelemetryData
};
} catch (error) {
if (loadingIndicator)
stopLoadingIndicator(loadingIndicator, 'Error.', false);
logger.error(`Error during expand all operation: ${error.message}`);
if (!isMCPCall && getDebugFlag(session)) {
console.error(error); // Log full stack in debug CLI mode
}
// Re-throw error for the caller to handle, the direct function will format it
throw error; // Let direct function wrapper handle formatting
/* Original re-throw:
throw new Error(`Failed to expand all tasks: ${error.message}`);
*/
}
}
export default expandAllTasks;