fix: expand_all now uses complexity analysis recommendations (#1287)

Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
Ralph Khreish
2025-10-11 11:54:31 +02:00
committed by GitHub
parent 25a00dca67
commit 90e6bdcf1c
5 changed files with 116 additions and 7 deletions

View File

@@ -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.

View File

@@ -8,6 +8,7 @@ import {
disableSilentMode disableSilentMode
} from '../../../../scripts/modules/utils.js'; } from '../../../../scripts/modules/utils.js';
import { createLogWrapper } from '../../tools/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) * 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 = {}) { export async function expandAllTasksDirect(args, log, context = {}) {
const { session } = context; // Extract session const { session } = context; // Extract session
// Destructure expected args, including projectRoot // Destructure expected args, including projectRoot and complexityReportPath
const { tasksJsonPath, num, research, prompt, force, projectRoot, tag } = const {
args; tasksJsonPath,
num,
research,
prompt,
force,
projectRoot,
tag,
complexityReportPath: providedComplexityReportPath
} = args;
// Create logger wrapper using the utility // Create logger wrapper using the utility
const mcpLog = createLogWrapper(log); 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) { if (!tasksJsonPath) {
log.error('expandAllTasksDirect called without tasksJsonPath'); log.error('expandAllTasksDirect called without tasksJsonPath');
return { return {
@@ -55,14 +73,14 @@ export async function expandAllTasksDirect(args, log, context = {}) {
const additionalContext = prompt || ''; const additionalContext = prompt || '';
const forceFlag = force === true; 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( const result = await expandAllTasks(
tasksJsonPath, tasksJsonPath,
numSubtasks, numSubtasks,
useResearch, useResearch,
additionalContext, additionalContext,
forceFlag, forceFlag,
{ session, mcpLog, projectRoot, tag }, { session, mcpLog, projectRoot, tag, complexityReportPath },
'json' 'json'
); );

View File

@@ -3,6 +3,7 @@ import {
findTasksPath as coreFindTasksPath, findTasksPath as coreFindTasksPath,
findPRDPath as coreFindPrdPath, findPRDPath as coreFindPrdPath,
findComplexityReportPath as coreFindComplexityReportPath, findComplexityReportPath as coreFindComplexityReportPath,
resolveComplexityReportOutputPath as coreResolveComplexityReportOutputPath,
findProjectRoot as coreFindProjectRoot, findProjectRoot as coreFindProjectRoot,
normalizeProjectRoot normalizeProjectRoot
} from '../../../../src/utils/path-utils.js'; } from '../../../../src/utils/path-utils.js';
@@ -224,6 +225,21 @@ export function findComplexityReportPath(args, log = silentLogger) {
return resolveComplexityReportPath(args, log); 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 * Find PRD path - primary MCP function
* @param {string} [explicitPath] - Explicit path to PRD file * @param {string} [explicitPath] - Explicit path to PRD file

View File

@@ -10,7 +10,10 @@ import {
withNormalizedProjectRoot withNormalizedProjectRoot
} from './utils.js'; } from './utils.js';
import { expandAllTasksDirect } from '../core/task-master-core.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'; 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( const result = await expandAllTasksDirect(
{ {
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
@@ -93,7 +104,8 @@ export function registerExpandAllTool(server) {
prompt: args.prompt, prompt: args.prompt,
force: args.force, force: args.force,
projectRoot: args.projectRoot, projectRoot: args.projectRoot,
tag: resolvedTag tag: resolvedTag,
complexityReportPath
}, },
log, log,
{ session } { session }

View File

@@ -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 () => { test('should return success with message when no tasks are eligible', async () => {
// Arrange - Mock tasks data with no eligible tasks // Arrange - Mock tasks data with no eligible tasks
const noEligibleTasksData = { const noEligibleTasksData = {