diff --git a/.changeset/smart-owls-relax.md b/.changeset/smart-owls-relax.md new file mode 100644 index 00000000..99cb5827 --- /dev/null +++ b/.changeset/smart-owls-relax.md @@ -0,0 +1,16 @@ +--- +"task-master-ai": minor +--- + +Enhance `expand_all` to intelligently use complexity analysis recommendations when expanding tasks. + +The expand-all operation now automatically leverages recommendations from `analyze-complexity` to determine optimal subtask counts for each task, resulting in more accurate and context-aware task breakdowns. + +Key improvements: +- Automatic integration with complexity analysis reports +- Tag-aware complexity report path resolution +- Intelligent subtask count determination based on task complexity +- Falls back to defaults when complexity analysis is unavailable +- Enhanced logging for better visibility into expansion decisions + +When you run `task-master expand --all` after `task-master analyze-complexity`, Task Master now uses the recommended subtask counts from the complexity analysis instead of applying uniform defaults, ensuring each task is broken down according to its actual complexity. diff --git a/mcp-server/src/core/direct-functions/expand-all-tasks.js b/mcp-server/src/core/direct-functions/expand-all-tasks.js index 531b847e..be63ced3 100644 --- a/mcp-server/src/core/direct-functions/expand-all-tasks.js +++ b/mcp-server/src/core/direct-functions/expand-all-tasks.js @@ -8,6 +8,7 @@ import { disableSilentMode } from '../../../../scripts/modules/utils.js'; import { createLogWrapper } from '../../tools/utils.js'; +import { resolveComplexityReportOutputPath } from '../../../../src/utils/path-utils.js'; /** * Expand all pending tasks with subtasks (Direct Function Wrapper) @@ -25,13 +26,30 @@ import { createLogWrapper } from '../../tools/utils.js'; */ export async function expandAllTasksDirect(args, log, context = {}) { const { session } = context; // Extract session - // Destructure expected args, including projectRoot - const { tasksJsonPath, num, research, prompt, force, projectRoot, tag } = - args; + // Destructure expected args, including projectRoot and complexityReportPath + const { + tasksJsonPath, + num, + research, + prompt, + force, + projectRoot, + tag, + complexityReportPath: providedComplexityReportPath + } = args; // Create logger wrapper using the utility const mcpLog = createLogWrapper(log); + // Use provided complexity report path or compute it + const complexityReportPath = + providedComplexityReportPath || + resolveComplexityReportOutputPath(null, { projectRoot, tag }, log); + + log.info( + `Expand all tasks will use complexity report at: ${complexityReportPath}` + ); + if (!tasksJsonPath) { log.error('expandAllTasksDirect called without tasksJsonPath'); return { @@ -55,14 +73,14 @@ export async function expandAllTasksDirect(args, log, context = {}) { const additionalContext = prompt || ''; const forceFlag = force === true; - // Call the core function, passing options and the context object { session, mcpLog, projectRoot } + // Call the core function, passing options and the context object { session, mcpLog, projectRoot, tag, complexityReportPath } const result = await expandAllTasks( tasksJsonPath, numSubtasks, useResearch, additionalContext, forceFlag, - { session, mcpLog, projectRoot, tag }, + { session, mcpLog, projectRoot, tag, complexityReportPath }, 'json' ); diff --git a/mcp-server/src/core/utils/path-utils.js b/mcp-server/src/core/utils/path-utils.js index af47ba5f..722edb92 100644 --- a/mcp-server/src/core/utils/path-utils.js +++ b/mcp-server/src/core/utils/path-utils.js @@ -3,6 +3,7 @@ import { findTasksPath as coreFindTasksPath, findPRDPath as coreFindPrdPath, findComplexityReportPath as coreFindComplexityReportPath, + resolveComplexityReportOutputPath as coreResolveComplexityReportOutputPath, findProjectRoot as coreFindProjectRoot, normalizeProjectRoot } from '../../../../src/utils/path-utils.js'; @@ -224,6 +225,21 @@ export function findComplexityReportPath(args, log = silentLogger) { return resolveComplexityReportPath(args, log); } +/** + * Resolve complexity report output path (create if needed) - primary MCP function + * @param {string|null} [explicitPath] - Explicit path to complexity report + * @param {Object} args - Arguments object containing projectRoot and tag + * @param {Object} [log] - Log function to prevent console logging + * @returns {string} - Resolved output path for complexity report + */ +export function resolveComplexityReportOutputPath( + explicitPath, + args, + log = silentLogger +) { + return coreResolveComplexityReportOutputPath(explicitPath, args, log); +} + /** * Find PRD path - primary MCP function * @param {string} [explicitPath] - Explicit path to PRD file diff --git a/mcp-server/src/tools/expand-all.js b/mcp-server/src/tools/expand-all.js index 08823016..ee52607a 100644 --- a/mcp-server/src/tools/expand-all.js +++ b/mcp-server/src/tools/expand-all.js @@ -10,7 +10,10 @@ import { withNormalizedProjectRoot } from './utils.js'; import { expandAllTasksDirect } from '../core/task-master-core.js'; -import { findTasksPath } from '../core/utils/path-utils.js'; +import { + findTasksPath, + resolveComplexityReportOutputPath +} from '../core/utils/path-utils.js'; import { resolveTag } from '../../../scripts/modules/utils.js'; /** @@ -85,6 +88,14 @@ export function registerExpandAllTool(server) { ); } + // Resolve complexity report path to use recommendations from analyze-complexity + const complexityReportPath = resolveComplexityReportOutputPath( + null, + { projectRoot: args.projectRoot, tag: resolvedTag }, + log + ); + log.info(`Using complexity report path: ${complexityReportPath}`); + const result = await expandAllTasksDirect( { tasksJsonPath: tasksJsonPath, @@ -93,7 +104,8 @@ export function registerExpandAllTool(server) { prompt: args.prompt, force: args.force, projectRoot: args.projectRoot, - tag: resolvedTag + tag: resolvedTag, + complexityReportPath }, log, { session } diff --git a/tests/unit/scripts/modules/task-manager/expand-all-tasks.test.js b/tests/unit/scripts/modules/task-manager/expand-all-tasks.test.js index e05b9355..1008b11e 100644 --- a/tests/unit/scripts/modules/task-manager/expand-all-tasks.test.js +++ b/tests/unit/scripts/modules/task-manager/expand-all-tasks.test.js @@ -244,6 +244,53 @@ describe('expandAllTasks', () => { ); }); + test('should pass complexityReportPath to expandTask when provided in context', async () => { + // Arrange + const mockComplexityReportPath = + '/test/project/.taskmaster/reports/task-complexity-report.json'; + mockExpandTask.mockResolvedValue({ + telemetryData: { commandName: 'expand-task', totalCost: 0.05 } + }); + + // Act + const result = await expandAllTasks( + mockTasksPath, + undefined, // numSubtasks not specified, should use complexity report + false, + '', + false, + { + session: mockSession, + mcpLog: mockMcpLog, + projectRoot: mockProjectRoot, + tag: 'master', + complexityReportPath: mockComplexityReportPath + }, + 'json' + ); + + // Assert + expect(result.success).toBe(true); + expect(result.expandedCount).toBe(2); // Tasks 1 and 2 + + // Verify expandTask was called with complexityReportPath in context + expect(mockExpandTask).toHaveBeenCalledWith( + mockTasksPath, + expect.any(Number), // task id + undefined, // numSubtasks + false, // useResearch + '', // additionalContext + expect.objectContaining({ + session: mockSession, + mcpLog: mockMcpLog, + projectRoot: mockProjectRoot, + tag: 'master', + complexityReportPath: mockComplexityReportPath + }), + false // force + ); + }); + test('should return success with message when no tasks are eligible', async () => { // Arrange - Mock tasks data with no eligible tasks const noEligibleTasksData = {