Compare commits

...

31 Commits

Author SHA1 Message Date
Ralph Khreish
834dfb86ac chore: cleanup 2025-05-16 23:22:34 +02:00
Ralph Khreish
b984af0606 Merge remote-tracking branch 'origin/next' into add-complexity-score-to-task 2025-05-16 23:07:16 +02:00
Shrey Paharia
d7ebfe30fc fix: fixed mcp server project root input 2025-05-07 23:00:42 +05:30
Shrey Paharia
126abb9631 fix: fixed addComplexityToTask util 2025-05-07 11:30:37 +05:30
Shrey Paharia
e917fd16c0 fix: fixed findTaskById and tests 2025-05-07 11:17:55 +05:30
Shrey Paharia
07a710d88e fix: fixed findTaskById 2025-05-07 11:03:20 +05:30
Shrey Paharia
0ca41443de Merge branch 'next' of github.com:eyaltoledano/claude-task-master into add-complexity-score-to-task 2025-05-07 10:21:09 +05:30
Shrey Paharia
7c543cd8c3 fix: fixed running tests 2025-05-03 18:38:36 +05:30
Shrey Paharia
c2865b81f8 feat: update list and find next task 2025-05-03 18:15:25 +05:30
Shrey Paharia
a0ac50ffd7 Merge branch 'next' of github.com:eyaltoledano/claude-task-master into add-complexity-score-to-task 2025-05-03 16:34:47 +05:30
Shrey Paharia
b6b0dd1e29 Merge branch 'next' of github.com:eyaltoledano/claude-task-master into add-complexity-score-to-task 2025-05-01 22:21:02 +05:30
Shrey Paharia
0f37cf0851 ref: remove unecessary comments 2025-04-24 00:33:09 +05:30
Shrey Paharia
abb5063b3e chore: remove unecessary changeset 2025-04-24 00:29:47 +05:30
Shrey Paharia
6b0ec458e8 feat: add handling for report path override 2025-04-24 00:27:25 +05:30
Shrey Paharia
8047ec756c feat: added handling for next-task in mcp 2025-04-24 00:17:19 +05:30
Shrey Paharia
be8fe8092f feat: added handling to get-task 2025-04-24 00:13:09 +05:30
Shrey Paharia
33d2569ace fix: fixed handling for complexity report path in mcp 2025-04-23 23:39:03 +05:30
Shrey Paharia
fdbb25e185 fix: fixed next cli command handling 2025-04-23 21:25:42 +05:30
Shrey Paharia
deaf4a6ff4 fix: added handling for show cli 2025-04-23 21:11:49 +05:30
Shrey Paharia
3628acab78 fix: add complexity handling to next task in list command 2025-04-23 20:51:53 +05:30
Shrey Paharia
463de0035c fix: moved complexity report handling to list tasks rather than list tasks direct 2025-04-23 10:08:57 +05:30
Shrey Paharia
fffcc5a89d chore: add changeset 2025-04-19 13:29:35 +05:30
Shrey Paharia
11506ddc0e fix: added handling for complexity report path 2025-04-19 13:28:36 +05:30
Shrey Paharia
dcb3f2f9f9 test: fix findTaskById complexity report testcases 2025-04-19 13:24:50 +05:30
Shrey Paharia
e045a5268c feat: updated handling for findTaskById to take complexityReport as input 2025-04-19 13:09:14 +05:30
Shrey Paharia
8911bf4d49 ref: reorder imports 2025-04-19 04:24:16 +05:30
Shrey Paharia
5e5e20391a format: fixed formatting issues 2025-04-19 04:06:14 +05:30
Shrey Paharia
521cf0e5f0 chore: add changeset 2025-04-19 04:03:05 +05:30
Shrey Paharia
92bd0e4395 test: remove console dir 2025-04-19 03:45:43 +05:30
Shrey Paharia
3162ac49ec feat: added handling for complexity score in find task by id 2025-04-19 03:45:22 +05:30
Shrey Paharia
b98af1541e feat: added complexity score handling to list tasks 2025-04-19 02:17:38 +05:30
16 changed files with 490 additions and 74 deletions

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': minor
---
Display task complexity scores in task lists, next task, and task details views.

View File

@@ -18,7 +18,7 @@ import {
*/ */
export async function listTasksDirect(args, log) { export async function listTasksDirect(args, log) {
// Destructure the explicit tasksJsonPath from args // Destructure the explicit tasksJsonPath from args
const { tasksJsonPath, status, withSubtasks } = args; const { tasksJsonPath, reportPath, status, withSubtasks } = args;
if (!tasksJsonPath) { if (!tasksJsonPath) {
log.error('listTasksDirect called without tasksJsonPath'); log.error('listTasksDirect called without tasksJsonPath');
@@ -49,6 +49,7 @@ export async function listTasksDirect(args, log) {
const resultData = listTasks( const resultData = listTasks(
tasksJsonPath, tasksJsonPath,
statusFilter, statusFilter,
reportPath,
withSubtasksFilter, withSubtasksFilter,
'json' 'json'
); );
@@ -63,6 +64,7 @@ export async function listTasksDirect(args, log) {
} }
}; };
} }
log.info( log.info(
`Core listTasks function retrieved ${resultData.tasks.length} tasks` `Core listTasks function retrieved ${resultData.tasks.length} tasks`
); );

View File

@@ -4,7 +4,10 @@
*/ */
import { findNextTask } from '../../../../scripts/modules/task-manager.js'; import { findNextTask } from '../../../../scripts/modules/task-manager.js';
import { readJSON } from '../../../../scripts/modules/utils.js'; import {
readJSON,
readComplexityReport
} from '../../../../scripts/modules/utils.js';
import { import {
enableSilentMode, enableSilentMode,
disableSilentMode disableSilentMode
@@ -20,7 +23,7 @@ import {
*/ */
export async function nextTaskDirect(args, log) { export async function nextTaskDirect(args, log) {
// Destructure expected args // Destructure expected args
const { tasksJsonPath } = args; const { tasksJsonPath, reportPath } = args;
if (!tasksJsonPath) { if (!tasksJsonPath) {
log.error('nextTaskDirect called without tasksJsonPath'); log.error('nextTaskDirect called without tasksJsonPath');
@@ -55,8 +58,11 @@ export async function nextTaskDirect(args, log) {
}; };
} }
// Read the complexity report
const complexityReport = readComplexityReport(reportPath);
// Find the next task // Find the next task
const nextTask = findNextTask(data.tasks); const nextTask = findNextTask(data.tasks, complexityReport);
if (!nextTask) { if (!nextTask) {
log.info( log.info(

View File

@@ -3,7 +3,11 @@
* Direct function implementation for showing task details * Direct function implementation for showing task details
*/ */
import { findTaskById, readJSON } from '../../../../scripts/modules/utils.js'; import {
findTaskById,
readComplexityReport,
readJSON
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js'; import { findTasksJsonPath } from '../utils/path-utils.js';
/** /**
@@ -12,6 +16,7 @@ import { findTasksJsonPath } from '../utils/path-utils.js';
* @param {Object} args - Command arguments. * @param {Object} args - Command arguments.
* @param {string} args.id - Task ID to show. * @param {string} args.id - Task ID to show.
* @param {string} [args.file] - Optional path to the tasks file (passed to findTasksJsonPath). * @param {string} [args.file] - Optional path to the tasks file (passed to findTasksJsonPath).
* @param {string} args.reportPath - Explicit path to the complexity report file.
* @param {string} [args.status] - Optional status to filter subtasks by. * @param {string} [args.status] - Optional status to filter subtasks by.
* @param {string} args.projectRoot - Absolute path to the project root directory (already normalized by tool). * @param {string} args.projectRoot - Absolute path to the project root directory (already normalized by tool).
* @param {Object} log - Logger object. * @param {Object} log - Logger object.
@@ -22,7 +27,7 @@ export async function showTaskDirect(args, log) {
// Destructure session from context if needed later, otherwise ignore // Destructure session from context if needed later, otherwise ignore
// const { session } = context; // const { session } = context;
// Destructure projectRoot and other args. projectRoot is assumed normalized. // Destructure projectRoot and other args. projectRoot is assumed normalized.
const { id, file, status, projectRoot } = args; const { id, file, reportPath, status, projectRoot } = args;
log.info( log.info(
`Showing task direct function. ID: ${id}, File: ${file}, Status Filter: ${status}, ProjectRoot: ${projectRoot}` `Showing task direct function. ID: ${id}, File: ${file}, Status Filter: ${status}, ProjectRoot: ${projectRoot}`
@@ -59,9 +64,12 @@ export async function showTaskDirect(args, log) {
}; };
} }
const complexityReport = readComplexityReport(reportPath);
const { task, originalSubtaskCount } = findTaskById( const { task, originalSubtaskCount } = findTaskById(
tasksData.tasks, tasksData.tasks,
id, id,
complexityReport,
status status
); );

View File

@@ -339,6 +339,49 @@ export function findPRDDocumentPath(projectRoot, explicitPath, log) {
return null; return null;
} }
export function findComplexityReportPath(projectRoot, explicitPath, log) {
// If explicit path is provided, check if it exists
if (explicitPath) {
const fullPath = path.isAbsolute(explicitPath)
? explicitPath
: path.resolve(projectRoot, explicitPath);
if (fs.existsSync(fullPath)) {
log.info(`Using provided PRD document path: ${fullPath}`);
return fullPath;
} else {
log.warn(
`Provided PRD document path not found: ${fullPath}, will search for alternatives`
);
}
}
// Common locations and file patterns for PRD documents
const commonLocations = [
'', // Project root
'scripts/'
];
const commonFileNames = [
'complexity-report.json',
'task-complexity-report.json'
];
// Check all possible combinations
for (const location of commonLocations) {
for (const fileName of commonFileNames) {
const potentialPath = path.join(projectRoot, location, fileName);
if (fs.existsSync(potentialPath)) {
log.info(`Found PRD document at: ${potentialPath}`);
return potentialPath;
}
}
}
log.warn(`No PRD document found in common locations within ${projectRoot}`);
return null;
}
/** /**
* Resolves the tasks output directory path * Resolves the tasks output directory path
* @param {string} projectRoot - The project root directory * @param {string} projectRoot - The project root directory

View File

@@ -10,7 +10,10 @@ import {
withNormalizedProjectRoot withNormalizedProjectRoot
} from './utils.js'; } from './utils.js';
import { showTaskDirect } from '../core/task-master-core.js'; import { showTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js'; import {
findTasksJsonPath,
findComplexityReportPath
} from '../core/utils/path-utils.js';
/** /**
* Custom processor function that removes allTasks from the response * Custom processor function that removes allTasks from the response
@@ -50,6 +53,12 @@ export function registerShowTaskTool(server) {
.string() .string()
.optional() .optional()
.describe('Path to the tasks file relative to project root'), .describe('Path to the tasks file relative to project root'),
complexityReport: z
.string()
.optional()
.describe(
'Path to the complexity report file (relative to project root or absolute)'
),
projectRoot: z projectRoot: z
.string() .string()
.optional() .optional()
@@ -81,9 +90,22 @@ export function registerShowTaskTool(server) {
} }
// Call the direct function, passing the normalized projectRoot // Call the direct function, passing the normalized projectRoot
// Resolve the path to complexity report
let complexityReportPath;
try {
complexityReportPath = findComplexityReportPath(
projectRoot,
args.complexityReport,
log
);
} catch (error) {
log.error(`Error finding complexity report: ${error.message}`);
}
const result = await showTaskDirect( const result = await showTaskDirect(
{ {
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
reportPath: complexityReportPath,
// Pass other relevant args
id: id, id: id,
status: status, status: status,
projectRoot: projectRoot projectRoot: projectRoot

View File

@@ -10,7 +10,10 @@ import {
withNormalizedProjectRoot withNormalizedProjectRoot
} from './utils.js'; } from './utils.js';
import { listTasksDirect } from '../core/task-master-core.js'; import { listTasksDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js'; import {
findTasksJsonPath,
findComplexityReportPath
} from '../core/utils/path-utils.js';
/** /**
* Register the getTasks tool with the MCP server * Register the getTasks tool with the MCP server
@@ -38,6 +41,12 @@ export function registerListTasksTool(server) {
.describe( .describe(
'Path to the tasks file (relative to project root or absolute)' 'Path to the tasks file (relative to project root or absolute)'
), ),
complexityReport: z
.string()
.optional()
.describe(
'Path to the complexity report file (relative to project root or absolute)'
),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.')
@@ -60,11 +69,23 @@ export function registerListTasksTool(server) {
); );
} }
// Resolve the path to complexity report
let complexityReportPath;
try {
complexityReportPath = findComplexityReportPath(
args.projectRoot,
args.complexityReport,
log
);
} catch (error) {
log.error(`Error finding complexity report: ${error.message}`);
}
const result = await listTasksDirect( const result = await listTasksDirect(
{ {
tasksJsonPath: tasksJsonPath, tasksJsonPath: tasksJsonPath,
status: args.status, status: args.status,
withSubtasks: args.withSubtasks withSubtasks: args.withSubtasks,
reportPath: complexityReportPath
}, },
log log
); );

View File

@@ -10,7 +10,10 @@ import {
withNormalizedProjectRoot withNormalizedProjectRoot
} from './utils.js'; } from './utils.js';
import { nextTaskDirect } from '../core/task-master-core.js'; import { nextTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js'; import {
findTasksJsonPath,
findComplexityReportPath
} from '../core/utils/path-utils.js';
/** /**
* Register the next-task tool with the MCP server * Register the next-task tool with the MCP server
@@ -23,6 +26,12 @@ export function registerNextTaskTool(server) {
'Find the next task to work on based on dependencies and status', 'Find the next task to work on based on dependencies and status',
parameters: z.object({ parameters: z.object({
file: z.string().optional().describe('Absolute path to the tasks file'), file: z.string().optional().describe('Absolute path to the tasks file'),
complexityReport: z
.string()
.optional()
.describe(
'Path to the complexity report file (relative to project root or absolute)'
),
projectRoot: z projectRoot: z
.string() .string()
.describe('The directory of the project. Must be an absolute path.') .describe('The directory of the project. Must be an absolute path.')
@@ -45,9 +54,21 @@ export function registerNextTaskTool(server) {
); );
} }
// Resolve the path to complexity report
let complexityReportPath;
try {
complexityReportPath = findComplexityReportPath(
args.projectRoot,
args.complexityReport,
log
);
} catch (error) {
log.error(`Error finding complexity report: ${error.message}`);
}
const result = await nextTaskDirect( const result = await nextTaskDirect(
{ {
tasksJsonPath: tasksJsonPath tasksJsonPath: tasksJsonPath,
reportPath: complexityReportPath
}, },
log log
); );

View File

@@ -1072,10 +1072,16 @@ function registerCommands(programInstance) {
.command('list') .command('list')
.description('List all tasks') .description('List all tasks')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json') .option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-r, --report <report>',
'Path to the complexity report file',
'scripts/task-complexity-report.json'
)
.option('-s, --status <status>', 'Filter by status') .option('-s, --status <status>', 'Filter by status')
.option('--with-subtasks', 'Show subtasks for each task') .option('--with-subtasks', 'Show subtasks for each task')
.action(async (options) => { .action(async (options) => {
const tasksPath = options.file; const tasksPath = options.file;
const reportPath = options.report;
const statusFilter = options.status; const statusFilter = options.status;
const withSubtasks = options.withSubtasks || false; const withSubtasks = options.withSubtasks || false;
@@ -1087,7 +1093,7 @@ function registerCommands(programInstance) {
console.log(chalk.blue('Including subtasks in listing')); console.log(chalk.blue('Including subtasks in listing'));
} }
await listTasks(tasksPath, statusFilter, withSubtasks); await listTasks(tasksPath, statusFilter, reportPath, withSubtasks);
}); });
// expand command // expand command
@@ -1393,9 +1399,15 @@ function registerCommands(programInstance) {
`Show the next task to work on based on dependencies and status${chalk.reset('')}` `Show the next task to work on based on dependencies and status${chalk.reset('')}`
) )
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json') .option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-r, --report <report>',
'Path to the complexity report file',
'scripts/task-complexity-report.json'
)
.action(async (options) => { .action(async (options) => {
const tasksPath = options.file; const tasksPath = options.file;
await displayNextTask(tasksPath); const reportPath = options.report;
await displayNextTask(tasksPath, reportPath);
}); });
// show command // show command
@@ -1408,6 +1420,11 @@ function registerCommands(programInstance) {
.option('-i, --id <id>', 'Task ID to show') .option('-i, --id <id>', 'Task ID to show')
.option('-s, --status <status>', 'Filter subtasks by status') // ADDED status option .option('-s, --status <status>', 'Filter subtasks by status') // ADDED status option
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json') .option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-r, --report <report>',
'Path to the complexity report file',
'scripts/task-complexity-report.json'
)
.action(async (taskId, options) => { .action(async (taskId, options) => {
const idArg = taskId || options.id; const idArg = taskId || options.id;
const statusFilter = options.status; // ADDED: Capture status filter const statusFilter = options.status; // ADDED: Capture status filter
@@ -1418,8 +1435,9 @@ function registerCommands(programInstance) {
} }
const tasksPath = options.file; const tasksPath = options.file;
const reportPath = options.report;
// PASS statusFilter to the display function // PASS statusFilter to the display function
await displayTaskById(tasksPath, idArg, statusFilter); await displayTaskById(tasksPath, idArg, reportPath, statusFilter);
}); });
// add-dependency command // add-dependency command

View File

@@ -23,7 +23,7 @@ import updateSubtaskById from './task-manager/update-subtask-by-id.js';
import removeTask from './task-manager/remove-task.js'; import removeTask from './task-manager/remove-task.js';
import taskExists from './task-manager/task-exists.js'; import taskExists from './task-manager/task-exists.js';
import isTaskDependentOn from './task-manager/is-task-dependent.js'; import isTaskDependentOn from './task-manager/is-task-dependent.js';
import { readComplexityReport } from './utils.js';
// Export task manager functions // Export task manager functions
export { export {
parsePRD, parsePRD,
@@ -45,5 +45,6 @@ export {
removeTask, removeTask,
findTaskById, findTaskById,
taskExists, taskExists,
isTaskDependentOn isTaskDependentOn,
readComplexityReport
}; };

View File

@@ -1,3 +1,6 @@
import { log } from '../utils.js';
import { addComplexityToTask } from '../utils.js';
/** /**
* Return the next work item: * Return the next work item:
* • Prefer an eligible SUBTASK that belongs to any parent task * • Prefer an eligible SUBTASK that belongs to any parent task
@@ -15,9 +18,10 @@
* ─ parentId → number (present only when it's a subtask) * ─ parentId → number (present only when it's a subtask)
* *
* @param {Object[]} tasks full array of top-level tasks, each may contain .subtasks[] * @param {Object[]} tasks full array of top-level tasks, each may contain .subtasks[]
* @param {Object} [complexityReport=null] - Optional complexity report object
* @returns {Object|null} next work item or null if nothing is eligible * @returns {Object|null} next work item or null if nothing is eligible
*/ */
function findNextTask(tasks) { function findNextTask(tasks, complexityReport = null) {
// ---------- helpers ---------------------------------------------------- // ---------- helpers ----------------------------------------------------
const priorityValues = { high: 3, medium: 2, low: 1 }; const priorityValues = { high: 3, medium: 2, low: 1 };
@@ -91,7 +95,14 @@ function findNextTask(tasks) {
if (aPar !== bPar) return aPar - bPar; if (aPar !== bPar) return aPar - bPar;
return aSub - bSub; return aSub - bSub;
}); });
return candidateSubtasks[0]; const nextTask = candidateSubtasks[0];
// Add complexity to the task before returning
if (nextTask && complexityReport) {
addComplexityToTask(nextTask, complexityReport);
}
return nextTask;
} }
// ---------- 2) fall back to top-level tasks (original logic) ------------ // ---------- 2) fall back to top-level tasks (original logic) ------------
@@ -116,6 +127,11 @@ function findNextTask(tasks) {
return a.id - b.id; return a.id - b.id;
})[0]; })[0];
// Add complexity to the task before returning
if (nextTask && complexityReport) {
addComplexityToTask(nextTask, complexityReport);
}
return nextTask; return nextTask;
} }

View File

@@ -2,13 +2,20 @@ import chalk from 'chalk';
import boxen from 'boxen'; import boxen from 'boxen';
import Table from 'cli-table3'; import Table from 'cli-table3';
import { log, readJSON, truncate } from '../utils.js'; import {
log,
readJSON,
truncate,
readComplexityReport,
addComplexityToTask
} from '../utils.js';
import findNextTask from './find-next-task.js'; import findNextTask from './find-next-task.js';
import { import {
displayBanner, displayBanner,
getStatusWithColor, getStatusWithColor,
formatDependenciesWithStatus, formatDependenciesWithStatus,
getComplexityWithColor,
createProgressBar createProgressBar
} from '../ui.js'; } from '../ui.js';
@@ -16,6 +23,7 @@ import {
* List all tasks * List all tasks
* @param {string} tasksPath - Path to the tasks.json file * @param {string} tasksPath - Path to the tasks.json file
* @param {string} statusFilter - Filter by status * @param {string} statusFilter - Filter by status
* @param {string} reportPath - Path to the complexity report
* @param {boolean} withSubtasks - Whether to show subtasks * @param {boolean} withSubtasks - Whether to show subtasks
* @param {string} outputFormat - Output format (text or json) * @param {string} outputFormat - Output format (text or json)
* @returns {Object} - Task list result for json format * @returns {Object} - Task list result for json format
@@ -23,6 +31,7 @@ import {
function listTasks( function listTasks(
tasksPath, tasksPath,
statusFilter, statusFilter,
reportPath = null,
withSubtasks = false, withSubtasks = false,
outputFormat = 'text' outputFormat = 'text'
) { ) {
@@ -37,6 +46,13 @@ function listTasks(
throw new Error(`No valid tasks found in ${tasksPath}`); throw new Error(`No valid tasks found in ${tasksPath}`);
} }
// Add complexity scores to tasks if report exists
const complexityReport = readComplexityReport(reportPath);
// Apply complexity scores to tasks
if (complexityReport && complexityReport.complexityAnalysis) {
data.tasks.forEach((task) => addComplexityToTask(task, complexityReport));
}
// Filter tasks by status if specified // Filter tasks by status if specified
const filteredTasks = const filteredTasks =
statusFilter && statusFilter.toLowerCase() !== 'all' // <-- Added check for 'all' statusFilter && statusFilter.toLowerCase() !== 'all' // <-- Added check for 'all'
@@ -257,8 +273,8 @@ function listTasks(
); );
const avgDependenciesPerTask = totalDependencies / data.tasks.length; const avgDependenciesPerTask = totalDependencies / data.tasks.length;
// Find next task to work on // Find next task to work on, passing the complexity report
const nextItem = findNextTask(data.tasks); const nextItem = findNextTask(data.tasks, complexityReport);
// Get terminal width - more reliable method // Get terminal width - more reliable method
let terminalWidth; let terminalWidth;
@@ -301,8 +317,11 @@ function listTasks(
`${chalk.blue('•')} ${chalk.white('Avg dependencies per task:')} ${avgDependenciesPerTask.toFixed(1)}\n\n` + `${chalk.blue('•')} ${chalk.white('Avg dependencies per task:')} ${avgDependenciesPerTask.toFixed(1)}\n\n` +
chalk.cyan.bold('Next Task to Work On:') + chalk.cyan.bold('Next Task to Work On:') +
'\n' + '\n' +
`ID: ${chalk.cyan(nextItem ? nextItem.id : 'N/A')} - ${nextItem ? chalk.white.bold(truncate(nextItem.title, 40)) : chalk.yellow('No task available')}\n` + `ID: ${chalk.cyan(nextItem ? nextItem.id : 'N/A')} - ${nextItem ? chalk.white.bold(truncate(nextItem.title, 40)) : chalk.yellow('No task available')}
`Priority: ${nextItem ? chalk.white(nextItem.priority || 'medium') : ''} Dependencies: ${nextItem ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true) : ''}`; ` +
`Priority: ${nextItem ? chalk.white(nextItem.priority || 'medium') : ''} Dependencies: ${nextItem ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : ''}
` +
`Complexity: ${nextItem && nextItem.complexityScore ? getComplexityWithColor(nextItem.complexityScore) : chalk.gray('N/A')}`;
// Calculate width for side-by-side display // Calculate width for side-by-side display
// Box borders, padding take approximately 4 chars on each side // Box borders, padding take approximately 4 chars on each side
@@ -412,9 +431,16 @@ function listTasks(
// Make dependencies column smaller as requested (-20%) // Make dependencies column smaller as requested (-20%)
const depsWidthPct = 20; const depsWidthPct = 20;
const complexityWidthPct = 10;
// Calculate title/description width as remaining space (+20% from dependencies reduction) // Calculate title/description width as remaining space (+20% from dependencies reduction)
const titleWidthPct = const titleWidthPct =
100 - idWidthPct - statusWidthPct - priorityWidthPct - depsWidthPct; 100 -
idWidthPct -
statusWidthPct -
priorityWidthPct -
depsWidthPct -
complexityWidthPct;
// Allow 10 characters for borders and padding // Allow 10 characters for borders and padding
const availableWidth = terminalWidth - 10; const availableWidth = terminalWidth - 10;
@@ -424,6 +450,9 @@ function listTasks(
const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100));
const priorityWidth = Math.floor(availableWidth * (priorityWidthPct / 100)); const priorityWidth = Math.floor(availableWidth * (priorityWidthPct / 100));
const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100));
const complexityWidth = Math.floor(
availableWidth * (complexityWidthPct / 100)
);
const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100));
// Create a table with correct borders and spacing // Create a table with correct borders and spacing
@@ -433,9 +462,17 @@ function listTasks(
chalk.cyan.bold('Title'), chalk.cyan.bold('Title'),
chalk.cyan.bold('Status'), chalk.cyan.bold('Status'),
chalk.cyan.bold('Priority'), chalk.cyan.bold('Priority'),
chalk.cyan.bold('Dependencies') chalk.cyan.bold('Dependencies'),
chalk.cyan.bold('Complexity')
],
colWidths: [
idWidth,
titleWidth,
statusWidth,
priorityWidth,
depsWidth,
complexityWidth // Added complexity column width
], ],
colWidths: [idWidth, titleWidth, statusWidth, priorityWidth, depsWidth],
style: { style: {
head: [], // No special styling for header head: [], // No special styling for header
border: [], // No special styling for border border: [], // No special styling for border
@@ -454,7 +491,8 @@ function listTasks(
depText = formatDependenciesWithStatus( depText = formatDependenciesWithStatus(
task.dependencies, task.dependencies,
data.tasks, data.tasks,
true true,
complexityReport
); );
} else { } else {
depText = chalk.gray('None'); depText = chalk.gray('None');
@@ -480,7 +518,10 @@ function listTasks(
truncate(cleanTitle, titleWidth - 3), truncate(cleanTitle, titleWidth - 3),
status, status,
priorityColor(truncate(task.priority || 'medium', priorityWidth - 2)), priorityColor(truncate(task.priority || 'medium', priorityWidth - 2)),
depText // No truncation for dependencies depText,
task.complexityScore
? getComplexityWithColor(task.complexityScore)
: chalk.gray('N/A')
]); ]);
// Add subtasks if requested // Add subtasks if requested
@@ -516,6 +557,8 @@ function listTasks(
// Default to regular task dependency // Default to regular task dependency
const depTask = data.tasks.find((t) => t.id === depId); const depTask = data.tasks.find((t) => t.id === depId);
if (depTask) { if (depTask) {
// Add complexity to depTask before checking status
addComplexityToTask(depTask, complexityReport);
const isDone = const isDone =
depTask.status === 'done' || depTask.status === 'completed'; depTask.status === 'done' || depTask.status === 'completed';
const isInProgress = depTask.status === 'in-progress'; const isInProgress = depTask.status === 'in-progress';
@@ -541,7 +584,10 @@ function listTasks(
chalk.dim(`└─ ${truncate(subtask.title, titleWidth - 5)}`), chalk.dim(`└─ ${truncate(subtask.title, titleWidth - 5)}`),
getStatusWithColor(subtask.status, true), getStatusWithColor(subtask.status, true),
chalk.dim('-'), chalk.dim('-'),
subtaskDepText // No truncation for dependencies subtaskDepText,
subtask.complexityScore
? chalk.gray(`${subtask.complexityScore}`)
: chalk.gray('N/A')
]); ]);
}); });
} }
@@ -597,6 +643,8 @@ function listTasks(
subtasksSection = `\n\n${chalk.white.bold('Subtasks:')}\n`; subtasksSection = `\n\n${chalk.white.bold('Subtasks:')}\n`;
subtasksSection += parentTaskForSubtasks.subtasks subtasksSection += parentTaskForSubtasks.subtasks
.map((subtask) => { .map((subtask) => {
// Add complexity to subtask before display
addComplexityToTask(subtask, complexityReport);
// Using a more simplified format for subtask status display // Using a more simplified format for subtask status display
const status = subtask.status || 'pending'; const status = subtask.status || 'pending';
const statusColors = { const statusColors = {
@@ -625,8 +673,8 @@ function listTasks(
'\n\n' + '\n\n' +
// Use nextItem.priority, nextItem.status, nextItem.dependencies // Use nextItem.priority, nextItem.status, nextItem.dependencies
`${chalk.white('Priority:')} ${priorityColors[nextItem.priority || 'medium'](nextItem.priority || 'medium')} ${chalk.white('Status:')} ${getStatusWithColor(nextItem.status, true)}\n` + `${chalk.white('Priority:')} ${priorityColors[nextItem.priority || 'medium'](nextItem.priority || 'medium')} ${chalk.white('Status:')} ${getStatusWithColor(nextItem.status, true)}\n` +
`${chalk.white('Dependencies:')} ${nextItem.dependencies && nextItem.dependencies.length > 0 ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true) : chalk.gray('None')}\n\n` + `${chalk.white('Dependencies:')} ${nextItem.dependencies && nextItem.dependencies.length > 0 ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : chalk.gray('None')}\n\n` +
// Use nextItem.description (Note: findNextTask doesn't return description, need to fetch original task/subtask for this) // Use nextTask.description (Note: findNextTask doesn't return description, need to fetch original task/subtask for this)
// *** Fetching original item for description and details *** // *** Fetching original item for description and details ***
`${chalk.white('Description:')} ${getWorkItemDescription(nextItem, data.tasks)}` + `${chalk.white('Description:')} ${getWorkItemDescription(nextItem, data.tasks)}` +
subtasksSection + // <-- Subtasks are handled above now subtasksSection + // <-- Subtasks are handled above now

View File

@@ -17,7 +17,11 @@ import {
isSilentMode isSilentMode
} from './utils.js'; } from './utils.js';
import fs from 'fs'; import fs from 'fs';
import { findNextTask, analyzeTaskComplexity } from './task-manager.js'; import {
findNextTask,
analyzeTaskComplexity,
readComplexityReport
} from './task-manager.js';
import { getProjectName, getDefaultSubtasks } from './config-manager.js'; import { getProjectName, getDefaultSubtasks } from './config-manager.js';
import { TASK_STATUS_OPTIONS } from '../../src/constants/task-status.js'; import { TASK_STATUS_OPTIONS } from '../../src/constants/task-status.js';
import { getTaskMasterVersion } from '../../src/utils/getVersion.js'; import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
@@ -264,12 +268,14 @@ function getStatusWithColor(status, forTable = false) {
* @param {Array} dependencies - Array of dependency IDs * @param {Array} dependencies - Array of dependency IDs
* @param {Array} allTasks - Array of all tasks * @param {Array} allTasks - Array of all tasks
* @param {boolean} forConsole - Whether the output is for console display * @param {boolean} forConsole - Whether the output is for console display
* @param {Object|null} complexityReport - Optional pre-loaded complexity report
* @returns {string} Formatted dependencies string * @returns {string} Formatted dependencies string
*/ */
function formatDependenciesWithStatus( function formatDependenciesWithStatus(
dependencies, dependencies,
allTasks, allTasks,
forConsole = false forConsole = false,
complexityReport = null // Add complexityReport parameter
) { ) {
if ( if (
!dependencies || !dependencies ||
@@ -333,7 +339,11 @@ function formatDependenciesWithStatus(
typeof depId === 'string' ? parseInt(depId, 10) : depId; typeof depId === 'string' ? parseInt(depId, 10) : depId;
// Look up the task using the numeric ID // Look up the task using the numeric ID
const depTaskResult = findTaskById(allTasks, numericDepId); const depTaskResult = findTaskById(
allTasks,
numericDepId,
complexityReport
);
const depTask = depTaskResult.task; // Access the task object from the result const depTask = depTaskResult.task; // Access the task object from the result
if (!depTask) { if (!depTask) {
@@ -752,7 +762,7 @@ function truncateString(str, maxLength) {
* Display the next task to work on * Display the next task to work on
* @param {string} tasksPath - Path to the tasks.json file * @param {string} tasksPath - Path to the tasks.json file
*/ */
async function displayNextTask(tasksPath) { async function displayNextTask(tasksPath, complexityReportPath = null) {
displayBanner(); displayBanner();
// Read the tasks file // Read the tasks file
@@ -762,8 +772,11 @@ async function displayNextTask(tasksPath) {
process.exit(1); process.exit(1);
} }
// Read complexity report once
const complexityReport = readComplexityReport(complexityReportPath);
// Find the next task // Find the next task
const nextTask = findNextTask(data.tasks); const nextTask = findNextTask(data.tasks, complexityReport);
if (!nextTask) { if (!nextTask) {
console.log( console.log(
@@ -824,7 +837,18 @@ async function displayNextTask(tasksPath) {
], ],
[ [
chalk.cyan.bold('Dependencies:'), chalk.cyan.bold('Dependencies:'),
formatDependenciesWithStatus(nextTask.dependencies, data.tasks, true) formatDependenciesWithStatus(
nextTask.dependencies,
data.tasks,
true,
complexityReport
)
],
[
chalk.cyan.bold('Complexity:'),
nextTask.complexityScore
? getComplexityWithColor(nextTask.complexityScore)
: chalk.gray('N/A')
], ],
[chalk.cyan.bold('Description:'), nextTask.description] [chalk.cyan.bold('Description:'), nextTask.description]
); );
@@ -992,7 +1016,12 @@ async function displayNextTask(tasksPath) {
* @param {string|number} taskId - The ID of the task to display * @param {string|number} taskId - The ID of the task to display
* @param {string} [statusFilter] - Optional status to filter subtasks by * @param {string} [statusFilter] - Optional status to filter subtasks by
*/ */
async function displayTaskById(tasksPath, taskId, statusFilter = null) { async function displayTaskById(
tasksPath,
taskId,
complexityReportPath = null,
statusFilter = null
) {
displayBanner(); displayBanner();
// Read the tasks file // Read the tasks file
@@ -1002,11 +1031,15 @@ async function displayTaskById(tasksPath, taskId, statusFilter = null) {
process.exit(1); process.exit(1);
} }
// Read complexity report once
const complexityReport = readComplexityReport(complexityReportPath);
// Find the task by ID, applying the status filter if provided // Find the task by ID, applying the status filter if provided
// Returns { task, originalSubtaskCount, originalSubtasks } // Returns { task, originalSubtaskCount, originalSubtasks }
const { task, originalSubtaskCount, originalSubtasks } = findTaskById( const { task, originalSubtaskCount, originalSubtasks } = findTaskById(
data.tasks, data.tasks,
taskId, taskId,
complexityReport,
statusFilter statusFilter
); );
@@ -1061,6 +1094,12 @@ async function displayTaskById(tasksPath, taskId, statusFilter = null) {
chalk.cyan.bold('Status:'), chalk.cyan.bold('Status:'),
getStatusWithColor(task.status || 'pending', true) getStatusWithColor(task.status || 'pending', true)
], ],
[
chalk.cyan.bold('Complexity:'),
task.complexityScore
? getComplexityWithColor(task.complexityScore)
: chalk.gray('N/A')
],
[ [
chalk.cyan.bold('Description:'), chalk.cyan.bold('Description:'),
task.description || 'No description provided.' task.description || 'No description provided.'
@@ -1139,7 +1178,18 @@ async function displayTaskById(tasksPath, taskId, statusFilter = null) {
[chalk.cyan.bold('Priority:'), priorityColor(task.priority || 'medium')], [chalk.cyan.bold('Priority:'), priorityColor(task.priority || 'medium')],
[ [
chalk.cyan.bold('Dependencies:'), chalk.cyan.bold('Dependencies:'),
formatDependenciesWithStatus(task.dependencies, data.tasks, true) formatDependenciesWithStatus(
task.dependencies,
data.tasks,
true,
complexityReport
)
],
[
chalk.cyan.bold('Complexity:'),
task.complexityScore
? getComplexityWithColor(task.complexityScore)
: chalk.gray('N/A')
], ],
[chalk.cyan.bold('Description:'), task.description] [chalk.cyan.bold('Description:'), task.description]
); );

View File

@@ -275,6 +275,22 @@ function findTaskInComplexityReport(report, taskId) {
return report.complexityAnalysis.find((task) => task.taskId === taskId); return report.complexityAnalysis.find((task) => task.taskId === taskId);
} }
function addComplexityToTask(task, complexityReport) {
let taskId;
if (task.isSubtask) {
taskId = task.parentTask.id;
} else if (task.parentId) {
taskId = task.parentId;
} else {
taskId = task.id;
}
const taskAnalysis = findTaskInComplexityReport(complexityReport, taskId);
if (taskAnalysis) {
task.complexityScore = taskAnalysis.complexityScore;
}
}
/** /**
* Checks if a task exists in the tasks array * Checks if a task exists in the tasks array
* @param {Array} tasks - The tasks array * @param {Array} tasks - The tasks array
@@ -325,10 +341,17 @@ function formatTaskId(id) {
* Finds a task by ID in the tasks array. Optionally filters subtasks by status. * Finds a task by ID in the tasks array. Optionally filters subtasks by status.
* @param {Array} tasks - The tasks array * @param {Array} tasks - The tasks array
* @param {string|number} taskId - The task ID to find * @param {string|number} taskId - The task ID to find
* @param {Object|null} complexityReport - Optional pre-loaded complexity report
* @returns {Object|null} The task object or null if not found
* @param {string} [statusFilter] - Optional status to filter subtasks by * @param {string} [statusFilter] - Optional status to filter subtasks by
* @returns {{task: Object|null, originalSubtaskCount: number|null}} The task object (potentially with filtered subtasks) and the original subtask count if filtered, or nulls if not found. * @returns {{task: Object|null, originalSubtaskCount: number|null}} The task object (potentially with filtered subtasks) and the original subtask count if filtered, or nulls if not found.
*/ */
function findTaskById(tasks, taskId, statusFilter = null) { function findTaskById(
tasks,
taskId,
complexityReport = null,
statusFilter = null
) {
if (!taskId || !tasks || !Array.isArray(tasks)) { if (!taskId || !tasks || !Array.isArray(tasks)) {
return { task: null, originalSubtaskCount: null }; return { task: null, originalSubtaskCount: null };
} }
@@ -356,10 +379,17 @@ function findTaskById(tasks, taskId, statusFilter = null) {
subtask.isSubtask = true; subtask.isSubtask = true;
} }
// Return the found subtask (or null) and null for originalSubtaskCount // If we found a task, check for complexity data
if (subtask && complexityReport) {
addComplexityToTask(subtask, complexityReport);
}
return { task: subtask || null, originalSubtaskCount: null }; return { task: subtask || null, originalSubtaskCount: null };
} }
let taskResult = null;
let originalSubtaskCount = null;
// Find the main task // Find the main task
const id = parseInt(taskId, 10); const id = parseInt(taskId, 10);
const task = tasks.find((t) => t.id === id) || null; const task = tasks.find((t) => t.id === id) || null;
@@ -369,6 +399,8 @@ function findTaskById(tasks, taskId, statusFilter = null) {
return { task: null, originalSubtaskCount: null }; return { task: null, originalSubtaskCount: null };
} }
taskResult = task;
// If task found and statusFilter provided, filter its subtasks // If task found and statusFilter provided, filter its subtasks
if (statusFilter && task.subtasks && Array.isArray(task.subtasks)) { if (statusFilter && task.subtasks && Array.isArray(task.subtasks)) {
const originalSubtaskCount = task.subtasks.length; const originalSubtaskCount = task.subtasks.length;
@@ -379,12 +411,18 @@ function findTaskById(tasks, taskId, statusFilter = null) {
subtask.status && subtask.status &&
subtask.status.toLowerCase() === statusFilter.toLowerCase() subtask.status.toLowerCase() === statusFilter.toLowerCase()
); );
// Return the filtered task and the original count
return { task: filteredTask, originalSubtaskCount: originalSubtaskCount }; taskResult = filteredTask;
originalSubtaskCount = originalSubtaskCount;
} }
// Return original task and null count if no filter or no subtasks // If task found and complexityReport provided, add complexity data
return { task: task, originalSubtaskCount: null }; if (taskResult && complexityReport) {
addComplexityToTask(taskResult, complexityReport);
}
// Return the found task and original subtask count
return { task: taskResult, originalSubtaskCount };
} }
/** /**
@@ -524,10 +562,11 @@ export {
findCycles, findCycles,
toKebabCase, toKebabCase,
detectCamelCaseFlags, detectCamelCaseFlags,
enableSilentMode,
disableSilentMode, disableSilentMode,
isSilentMode, enableSilentMode,
resolveEnvVariable,
getTaskManager, getTaskManager,
isSilentMode,
addComplexityToTask,
resolveEnvVariable,
findProjectRoot findProjectRoot
}; };

View File

@@ -3,9 +3,8 @@
*/ */
import { jest } from '@jest/globals'; import { jest } from '@jest/globals';
import path from 'path'; import path, { dirname } from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { dirname } from 'path';
// Get the current module's directory // Get the current module's directory
const __filename = fileURLToPath(import.meta.url); const __filename = fileURLToPath(import.meta.url);
@@ -27,6 +26,7 @@ const mockReadJSON = jest.fn();
const mockWriteJSON = jest.fn(); const mockWriteJSON = jest.fn();
const mockEnableSilentMode = jest.fn(); const mockEnableSilentMode = jest.fn();
const mockDisableSilentMode = jest.fn(); const mockDisableSilentMode = jest.fn();
const mockReadComplexityReport = jest.fn().mockReturnValue(null);
const mockGetAnthropicClient = jest.fn().mockReturnValue({}); const mockGetAnthropicClient = jest.fn().mockReturnValue({});
const mockGetConfiguredAnthropicClient = jest.fn().mockReturnValue({}); const mockGetConfiguredAnthropicClient = jest.fn().mockReturnValue({});
@@ -130,6 +130,7 @@ jest.mock('../../../scripts/modules/utils.js', () => ({
writeJSON: mockWriteJSON, writeJSON: mockWriteJSON,
enableSilentMode: mockEnableSilentMode, enableSilentMode: mockEnableSilentMode,
disableSilentMode: mockDisableSilentMode, disableSilentMode: mockDisableSilentMode,
readComplexityReport: mockReadComplexityReport,
CONFIG: { CONFIG: {
model: 'claude-3-7-sonnet-20250219', model: 'claude-3-7-sonnet-20250219',
maxTokens: 64000, maxTokens: 64000,
@@ -160,15 +161,6 @@ jest.mock('../../../scripts/modules/task-manager.js', () => ({
})); }));
// Import dependencies after mocks are set up // Import dependencies after mocks are set up
import fs from 'fs';
import {
readJSON,
writeJSON,
enableSilentMode,
disableSilentMode
} from '../../../scripts/modules/utils.js';
import { expandTask } from '../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../../../mcp-server/src/core/utils/path-utils.js';
import { sampleTasks } from '../../fixtures/sample-tasks.js'; import { sampleTasks } from '../../fixtures/sample-tasks.js';
// Mock logger // Mock logger
@@ -220,6 +212,37 @@ describe('MCP Server Direct Functions', () => {
}); });
describe('listTasksDirect', () => { describe('listTasksDirect', () => {
// Sample complexity report for testing
const mockComplexityReport = {
meta: {
generatedAt: '2025-03-24T20:01:35.986Z',
tasksAnalyzed: 3,
thresholdScore: 5,
projectName: 'Test Project',
usedResearch: false
},
complexityAnalysis: [
{
taskId: 1,
taskTitle: 'Initialize Project',
complexityScore: 3,
recommendedSubtasks: 2
},
{
taskId: 2,
taskTitle: 'Create Core Functionality',
complexityScore: 8,
recommendedSubtasks: 5
},
{
taskId: 3,
taskTitle: 'Implement UI Components',
complexityScore: 6,
recommendedSubtasks: 4
}
]
};
// Test wrapper function that doesn't rely on the actual implementation // Test wrapper function that doesn't rely on the actual implementation
async function testListTasks(args, mockLogger) { async function testListTasks(args, mockLogger) {
// File not found case // File not found case
@@ -235,21 +258,35 @@ describe('MCP Server Direct Functions', () => {
}; };
} }
// Check for complexity report
const complexityReport = mockReadComplexityReport();
let tasksData = [...sampleTasks.tasks];
// Add complexity scores if report exists
if (complexityReport && complexityReport.complexityAnalysis) {
tasksData = tasksData.map((task) => {
const analysis = complexityReport.complexityAnalysis.find(
(a) => a.taskId === task.id
);
if (analysis) {
return { ...task, complexityScore: analysis.complexityScore };
}
return task;
});
}
// Success case // Success case
if (!args.status && !args.withSubtasks) { if (!args.status && !args.withSubtasks) {
return { return {
success: true, success: true,
data: { data: {
tasks: sampleTasks.tasks, tasks: tasksData,
stats: { stats: {
total: sampleTasks.tasks.length, total: tasksData.length,
completed: sampleTasks.tasks.filter((t) => t.status === 'done') completed: tasksData.filter((t) => t.status === 'done').length,
inProgress: tasksData.filter((t) => t.status === 'in-progress')
.length, .length,
inProgress: sampleTasks.tasks.filter( pending: tasksData.filter((t) => t.status === 'pending').length
(t) => t.status === 'in-progress'
).length,
pending: sampleTasks.tasks.filter((t) => t.status === 'pending')
.length
} }
}, },
fromCache: false fromCache: false
@@ -258,16 +295,14 @@ describe('MCP Server Direct Functions', () => {
// Status filter case // Status filter case
if (args.status) { if (args.status) {
const filteredTasks = sampleTasks.tasks.filter( const filteredTasks = tasksData.filter((t) => t.status === args.status);
(t) => t.status === args.status
);
return { return {
success: true, success: true,
data: { data: {
tasks: filteredTasks, tasks: filteredTasks,
filter: args.status, filter: args.status,
stats: { stats: {
total: sampleTasks.tasks.length, total: tasksData.length,
filtered: filteredTasks.length filtered: filteredTasks.length
} }
}, },
@@ -280,10 +315,10 @@ describe('MCP Server Direct Functions', () => {
return { return {
success: true, success: true,
data: { data: {
tasks: sampleTasks.tasks, tasks: tasksData,
includeSubtasks: true, includeSubtasks: true,
stats: { stats: {
total: sampleTasks.tasks.length total: tasksData.length
} }
}, },
fromCache: false fromCache: false
@@ -370,6 +405,29 @@ describe('MCP Server Direct Functions', () => {
expect(result.error.code).toBe('FILE_NOT_FOUND_ERROR'); expect(result.error.code).toBe('FILE_NOT_FOUND_ERROR');
expect(mockLogger.error).toHaveBeenCalled(); expect(mockLogger.error).toHaveBeenCalled();
}); });
test('should include complexity scores when complexity report exists', async () => {
// Arrange
mockReadComplexityReport.mockReturnValueOnce(mockComplexityReport);
const args = {
projectRoot: testProjectRoot,
file: testTasksPath,
withSubtasks: true
};
// Act
const result = await testListTasks(args, mockLogger);
// Assert
expect(result.success).toBe(true);
// Check that tasks have complexity scores from the report
mockComplexityReport.complexityAnalysis.forEach((analysis) => {
const task = result.data.tasks.find((t) => t.id === analysis.taskId);
if (task) {
expect(task.complexityScore).toBe(analysis.complexityScore);
}
});
});
}); });
describe('expandTaskDirect', () => { describe('expandTaskDirect', () => {

View File

@@ -2,8 +2,9 @@
* Task finder tests * Task finder tests
*/ */
// Import after mocks are set up - No mocks needed for readComplexityReport anymore
import { findTaskById } from '../../scripts/modules/utils.js'; import { findTaskById } from '../../scripts/modules/utils.js';
import { sampleTasks, emptySampleTasks } from '../fixtures/sample-tasks.js'; import { emptySampleTasks, sampleTasks } from '../fixtures/sample-tasks.js';
describe('Task Finder', () => { describe('Task Finder', () => {
describe('findTaskById function', () => { describe('findTaskById function', () => {
@@ -55,5 +56,62 @@ describe('Task Finder', () => {
expect(result.task).toBeNull(); expect(result.task).toBeNull();
expect(result.originalSubtaskCount).toBeNull(); expect(result.originalSubtaskCount).toBeNull();
}); });
test('should work correctly when no complexity report is provided', () => {
// Pass null as the complexity report
const result = findTaskById(sampleTasks.tasks, 2, null);
expect(result.task).toBeDefined();
expect(result.task.id).toBe(2);
expect(result.task.complexityScore).toBeUndefined();
});
test('should work correctly when task has no complexity data in the provided report', () => {
// Define a complexity report that doesn't include task 2
const complexityReport = {
complexityAnalysis: [{ taskId: 999, complexityScore: 5 }]
};
const result = findTaskById(sampleTasks.tasks, 2, complexityReport);
expect(result.task).toBeDefined();
expect(result.task.id).toBe(2);
expect(result.task.complexityScore).toBeUndefined();
});
test('should include complexity score when report is provided', () => {
// Define the complexity report for this test
const complexityReport = {
meta: {
generatedAt: '2023-01-01T00:00:00.000Z',
tasksAnalyzed: 3,
thresholdScore: 5
},
complexityAnalysis: [
{
taskId: 1,
taskTitle: 'Initialize Project',
complexityScore: 3,
recommendedSubtasks: 2
},
{
taskId: 2,
taskTitle: 'Create Core Functionality',
complexityScore: 8,
recommendedSubtasks: 5
},
{
taskId: 3,
taskTitle: 'Implement UI Components',
complexityScore: 6,
recommendedSubtasks: 4
}
]
};
const result = findTaskById(sampleTasks.tasks, 2, complexityReport);
expect(result.task).toBeDefined();
expect(result.task.id).toBe(2);
expect(result.task.complexityScore).toBe(8);
});
}); });
}); });