From f5bce3452e236a4b27752ba7ecf57fee28bf7238 Mon Sep 17 00:00:00 2001 From: Eyal Toledano Date: Mon, 24 Mar 2025 15:43:14 -0400 Subject: [PATCH 1/3] feat(cli): enhance task list display, CLI usability, responsive table, colored deps status, help output, expand cmd clarity, init instructions, version bump to 0.9.18 --- bin/task-master.js | 30 ++++- package.json | 4 +- scripts/init.js | 2 +- scripts/modules/task-manager.js | 223 ++++++++++++++++++++++---------- scripts/modules/ui.js | 203 ++++++++++++++++++++++++----- tasks/tasks.json | 4 +- 6 files changed, 358 insertions(+), 108 deletions(-) diff --git a/bin/task-master.js b/bin/task-master.js index 6da05f7b..a8640e9a 100755 --- a/bin/task-master.js +++ b/bin/task-master.js @@ -41,7 +41,29 @@ const program = new Command(); program .name('task-master') .description('Claude Task Master CLI') - .version(version); + .version(version) + .addHelpText('afterAll', () => { + // Add the same help output that dev.js uses + const child = spawn('node', [devScriptPath, '--help'], { + stdio: ['inherit', 'pipe', 'inherit'], + cwd: process.cwd() + }); + + let output = ''; + child.stdout.on('data', (data) => { + output += data.toString(); + }); + + child.on('close', () => { + // Only display the custom help text part, not the commander-generated part + const customHelpStart = output.indexOf('Task Master CLI'); + if (customHelpStart > -1) { + console.log('\n' + output.substring(customHelpStart)); + } + }); + + return ''; // Return empty string to prevent immediate display + }); program .command('init') @@ -164,12 +186,12 @@ program program .command('expand') - .description('Expand tasks with subtasks') + .description('Break down tasks into detailed subtasks') .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') .option('-i, --id ', 'Task ID to expand') .option('-a, --all', 'Expand all tasks') .option('-n, --num ', 'Number of subtasks to generate') - .option('-r, --no-research', 'Disable Perplexity AI for research-backed subtask generation') + .option('--research', 'Enable Perplexity AI for research-backed subtask generation') .option('-p, --prompt ', 'Additional context to guide subtask generation') .option('--force', 'Force regeneration of subtasks for tasks that already have them') .action((options) => { @@ -178,7 +200,7 @@ program if (options.id) args.push('--id', options.id); if (options.all) args.push('--all'); if (options.num) args.push('--num', options.num); - if (!options.research) args.push('--no-research'); + if (options.research) args.push('--research'); if (options.prompt) args.push('--prompt', options.prompt); if (options.force) args.push('--force'); runDevScript(args); diff --git a/package.json b/package.json index fc435d18..697b9116 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "task-master-ai", - "version": "0.9.16", + "version": "0.9.18", "description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.", "main": "index.js", "type": "module", @@ -72,4 +72,4 @@ "mock-fs": "^5.5.0", "supertest": "^7.1.0" } -} +} \ No newline at end of file diff --git a/scripts/init.js b/scripts/init.js index e45b9ba8..b269b089 100755 --- a/scripts/init.js +++ b/scripts/init.js @@ -542,7 +542,7 @@ function createProjectStructure(projectName, projectDescription, projectVersion, chalk.white('1. ') + chalk.yellow('Rename .env.example to .env and add your ANTHROPIC_API_KEY and PERPLEXITY_API_KEY') + '\n' + chalk.white('2. ') + chalk.yellow('Discuss your idea with AI, and once ready ask for a PRD using the example_prd.txt file, and save what you get to scripts/PRD.txt') + '\n' + chalk.white('3. ') + chalk.yellow('Ask Cursor Agent to parse your PRD.txt and generate tasks') + '\n' + - chalk.white(' └─ ') + chalk.dim('You can also run ') + chalk.cyan('npm run parse-prd -- --input=') + '\n' + + chalk.white(' └─ ') + chalk.dim('You can also run ') + chalk.cyan('task-master parse-prd ') + '\n' + chalk.white('4. ') + chalk.yellow('Ask Cursor to analyze the complexity of your tasks') + '\n' + chalk.white('5. ') + chalk.yellow('Ask Cursor which task is next to determine where to start') + '\n' + chalk.white('6. ') + chalk.yellow('Ask Cursor to expand any complex tasks that are too large or complex.') + '\n' + diff --git a/scripts/modules/task-manager.js b/scripts/modules/task-manager.js index e9b8c329..55df1047 100644 --- a/scripts/modules/task-manager.js +++ b/scripts/modules/task-manager.js @@ -379,7 +379,17 @@ function generateTaskFiles(tasksPath, outputDir) { // Handle numeric dependencies to other subtasks const foundSubtask = task.subtasks.find(st => st.id === depId); if (foundSubtask) { - return `${depId} (${foundSubtask.status || 'pending'})`; + const isDone = foundSubtask.status === 'done' || foundSubtask.status === 'completed'; + const isInProgress = foundSubtask.status === 'in-progress'; + + // Use consistent color formatting instead of emojis + if (isDone) { + return chalk.green.bold(`${task.id}.${depId}`); + } else if (isInProgress) { + return chalk.hex('#FFA500').bold(`${task.id}.${depId}`); + } else { + return chalk.red.bold(`${task.id}.${depId}`); + } } } return depId.toString(); @@ -675,8 +685,20 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false) { `Priority: ${chalk.white(nextTask.priority || 'medium')} Dependencies: ${formatDependenciesWithStatus(nextTask.dependencies, data.tasks, true)}` : chalk.yellow('No eligible tasks found. All tasks are either completed or have unsatisfied dependencies.'); - // Get terminal width - const terminalWidth = process.stdout.columns || 80; + // Get terminal width - more reliable method + let terminalWidth; + try { + // Try to get the actual terminal columns + terminalWidth = process.stdout.columns; + } catch (e) { + // Fallback if columns cannot be determined + log('debug', 'Could not determine terminal width, using default'); + } + // Ensure we have a reasonable default if detection fails + terminalWidth = terminalWidth || 80; + + // Ensure terminal width is at least a minimum value to prevent layout issues + terminalWidth = Math.max(terminalWidth, 80); // Create dashboard content const projectDashboardContent = @@ -794,30 +816,17 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false) { return; } - // Use the previously defined terminalWidth for responsive table + // COMPLETELY REVISED TABLE APPROACH + // Define fixed column widths based on terminal size + const idWidth = 10; + const statusWidth = 20; + const priorityWidth = 10; + const depsWidth = 25; - // Define column widths based on percentage of available space - // Reserve minimum widths for ID, Status, Priority and Dependencies - const minIdWidth = 4; - const minStatusWidth = 12; - const minPriorityWidth = 8; - const minDepsWidth = 15; + // Calculate title width from available space + const titleWidth = terminalWidth - idWidth - statusWidth - priorityWidth - depsWidth - 10; // 10 for borders and padding - // Calculate available space for the title column - const minFixedColumnsWidth = minIdWidth + minStatusWidth + minPriorityWidth + minDepsWidth; - const tableMargin = 10; // Account for table borders and padding - const availableTitleWidth = Math.max(30, terminalWidth - minFixedColumnsWidth - tableMargin); - - // Scale column widths proportionally - const colWidths = [ - minIdWidth, - availableTitleWidth, - minStatusWidth, - minPriorityWidth, - minDepsWidth - ]; - - // Create a table for tasks + // Create a table with correct borders and spacing const table = new Table({ head: [ chalk.cyan.bold('ID'), @@ -826,61 +835,118 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false) { chalk.cyan.bold('Priority'), chalk.cyan.bold('Dependencies') ], - colWidths: colWidths, - wordWrap: true + colWidths: [idWidth, titleWidth, statusWidth, priorityWidth, depsWidth], + style: { + head: [], // No special styling for header + border: [], // No special styling for border + compact: false // Use default spacing + }, + wordWrap: true, + wrapOnWordBoundary: true, }); - // Add tasks to the table + // Process tasks for the table filteredTasks.forEach(task => { - // Get a list of task dependencies - const formattedDeps = formatDependenciesWithStatus(task.dependencies, data.tasks, true); + // Format dependencies with status indicators (colored) + let depText = 'None'; + if (task.dependencies && task.dependencies.length > 0) { + // Use the proper formatDependenciesWithStatus function for colored status + depText = formatDependenciesWithStatus(task.dependencies, data.tasks, true); + } else { + depText = chalk.gray('None'); + } + // Clean up any ANSI codes or confusing characters + const cleanTitle = task.title.replace(/\n/g, ' '); + + // Get priority color + const priorityColor = { + 'high': chalk.red, + 'medium': chalk.yellow, + 'low': chalk.gray + }[task.priority || 'medium'] || chalk.white; + + // Format status + const status = getStatusWithColor(task.status, true); + + // Add the row without truncating dependencies table.push([ - task.id, - truncate(task.title, availableTitleWidth - 3), // -3 for table cell padding - getStatusWithColor(task.status), - chalk.white(task.priority || 'medium'), - formattedDeps + task.id.toString(), + truncate(cleanTitle, titleWidth - 3), + status, + priorityColor(truncate(task.priority || 'medium', priorityWidth - 2)), + depText // No truncation for dependencies ]); // Add subtasks if requested if (withSubtasks && task.subtasks && task.subtasks.length > 0) { task.subtasks.forEach(subtask => { - // Format subtask dependencies - let subtaskDeps = ''; - + // Format subtask dependencies with status indicators + let subtaskDepText = 'None'; if (subtask.dependencies && subtask.dependencies.length > 0) { - subtaskDeps = subtask.dependencies.map(depId => { + // Handle both subtask-to-subtask and subtask-to-task dependencies + const formattedDeps = subtask.dependencies.map(depId => { // Check if it's a dependency on another subtask - const foundSubtask = task.subtasks.find(st => st.id === depId); - - if (foundSubtask) { - const isDone = foundSubtask.status === 'done' || foundSubtask.status === 'completed'; - const statusIcon = isDone ? - chalk.green('✅') : - chalk.yellow('⏱️'); - - return `${statusIcon} ${chalk.cyan(`${task.id}.${depId}`)}`; + if (typeof depId === 'number' && depId < 100) { + const foundSubtask = task.subtasks.find(st => st.id === depId); + if (foundSubtask) { + const isDone = foundSubtask.status === 'done' || foundSubtask.status === 'completed'; + const isInProgress = foundSubtask.status === 'in-progress'; + + // Use consistent color formatting instead of emojis + if (isDone) { + return chalk.green.bold(`${task.id}.${depId}`); + } else if (isInProgress) { + return chalk.hex('#FFA500').bold(`${task.id}.${depId}`); + } else { + return chalk.red.bold(`${task.id}.${depId}`); + } + } + } + // Default to regular task dependency + const depTask = data.tasks.find(t => t.id === depId); + if (depTask) { + const isDone = depTask.status === 'done' || depTask.status === 'completed'; + const isInProgress = depTask.status === 'in-progress'; + // Use the same color scheme as in formatDependenciesWithStatus + if (isDone) { + return chalk.green.bold(`${depId}`); + } else if (isInProgress) { + return chalk.hex('#FFA500').bold(`${depId}`); + } else { + return chalk.red.bold(`${depId}`); + } } - return chalk.cyan(depId.toString()); }).join(', '); - } else { - subtaskDeps = chalk.gray('None'); + + subtaskDepText = formattedDeps || chalk.gray('None'); } + // Add the subtask row without truncating dependencies table.push([ `${task.id}.${subtask.id}`, - chalk.dim(`└─ ${truncate(subtask.title, availableTitleWidth - 5)}`), // -5 for the "└─ " prefix - getStatusWithColor(subtask.status), + chalk.dim(`└─ ${truncate(subtask.title, titleWidth - 5)}`), + getStatusWithColor(subtask.status, true), chalk.dim('-'), - subtaskDeps + subtaskDepText // No truncation for dependencies ]); }); } }); - console.log(table.toString()); + // Ensure we output the table even if it had to wrap + try { + console.log(table.toString()); + } catch (err) { + log('error', `Error rendering table: ${err.message}`); + + // Fall back to simpler output + console.log(chalk.yellow('\nFalling back to simple task list due to terminal width constraints:')); + filteredTasks.forEach(task => { + console.log(`${chalk.cyan(task.id)}: ${chalk.white(task.title)} - ${getStatusWithColor(task.status)}`); + }); + } // Show filter info if applied if (statusFilter) { @@ -891,8 +957,8 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false) { // Define priority colors const priorityColors = { 'high': chalk.red.bold, - 'medium': chalk.yellow, - 'low': chalk.gray + 'medium': chalk.yellow, + 'low': chalk.gray }; // Show next task box in a prominent color @@ -902,25 +968,35 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false) { if (nextTask.subtasks && nextTask.subtasks.length > 0) { subtasksSection = `\n\n${chalk.white.bold('Subtasks:')}\n`; subtasksSection += nextTask.subtasks.map(subtask => { - const subtaskStatus = getStatusWithColor(subtask.status || 'pending'); - return `${chalk.cyan(`${nextTask.id}.${subtask.id}`)} ${subtaskStatus} ${subtask.title}`; + // Using a more simplified format for subtask status display + const status = subtask.status || 'pending'; + const statusColors = { + 'done': chalk.green, + 'completed': chalk.green, + 'pending': chalk.yellow, + 'in-progress': chalk.blue, + 'deferred': chalk.gray, + 'blocked': chalk.red + }; + const statusColor = statusColors[status.toLowerCase()] || chalk.white; + return `${chalk.cyan(`${nextTask.id}.${subtask.id}`)} [${statusColor(status)}] ${subtask.title}`; }).join('\n'); } console.log(boxen( chalk.hex('#FF8800').bold(`🔥 Next Task to Work On: #${nextTask.id} - ${nextTask.title}`) + '\n\n' + - `${chalk.white('Priority:')} ${priorityColors[nextTask.priority || 'medium'](nextTask.priority || 'medium')} ${chalk.white('Status:')} ${getStatusWithColor(nextTask.status)}\n` + - `${chalk.white('Dependencies:')} ${formatDependenciesWithStatus(nextTask.dependencies, data.tasks, true)}\n\n` + + `${chalk.white('Priority:')} ${priorityColors[nextTask.priority || 'medium'](nextTask.priority || 'medium')} ${chalk.white('Status:')} ${getStatusWithColor(nextTask.status, true)}\n` + + `${chalk.white('Dependencies:')} ${nextTask.dependencies && nextTask.dependencies.length > 0 ? formatDependenciesWithStatus(nextTask.dependencies, data.tasks, true) : chalk.gray('None')}\n\n` + `${chalk.white('Description:')} ${nextTask.description}` + subtasksSection + '\n\n' + `${chalk.cyan('Start working:')} ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` + `${chalk.cyan('View details:')} ${chalk.yellow(`task-master show ${nextTask.id}`)}`, { - padding: 1, + padding: { left: 2, right: 2, top: 1, bottom: 1 }, borderColor: '#FF8800', borderStyle: 'round', margin: { top: 1, bottom: 1 }, - title: '⚡ RECOMMENDED NEXT ACTION ⚡', + title: '⚡ RECOMMENDED NEXT TASK ⚡', titleAlignment: 'center', width: terminalWidth - 4, // Use full terminal width minus a small margin fullscreen: false // Keep it expandable but not literally fullscreen @@ -935,7 +1011,7 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false) { borderColor: '#FF8800', borderStyle: 'round', margin: { top: 1, bottom: 1 }, - title: '⚡ NEXT ACTION ⚡', + title: '⚡ NEXT TASK ⚡', titleAlignment: 'center', width: terminalWidth - 4, // Use full terminal width minus a small margin } @@ -962,6 +1038,23 @@ function listTasks(tasksPath, statusFilter, withSubtasks = false) { } } +/** + * Safely apply chalk coloring, stripping ANSI codes when calculating string length + * @param {string} text - Original text + * @param {Function} colorFn - Chalk color function + * @param {number} maxLength - Maximum allowed length + * @returns {string} Colored text that won't break table layout + */ +function safeColor(text, colorFn, maxLength = 0) { + if (!text) return ''; + + // If maxLength is provided, truncate the text first + const baseText = maxLength > 0 ? truncate(text, maxLength) : text; + + // Apply color function if provided, otherwise return as is + return colorFn ? colorFn(baseText) : baseText; +} + /** * Expand a task with subtasks * @param {number} taskId - Task ID to expand @@ -1087,7 +1180,7 @@ async function expandTask(taskId, numSubtasks = CONFIG.defaultSubtasks, useResea `${taskId}.${subtask.id}`, truncate(subtask.title, 47), deps, - getStatusWithColor(subtask.status) + getStatusWithColor(subtask.status, true) ]); }); diff --git a/scripts/modules/ui.js b/scripts/modules/ui.js index e087a0d5..01816474 100644 --- a/scripts/modules/ui.js +++ b/scripts/modules/ui.js @@ -97,24 +97,42 @@ function createProgressBar(percent, length = 30) { /** * Get a colored status string based on the status value * @param {string} status - Task status (e.g., "done", "pending", "in-progress") + * @param {boolean} forTable - Whether the status is being displayed in a table * @returns {string} Colored status string */ -function getStatusWithColor(status) { +function getStatusWithColor(status, forTable = false) { if (!status) { return chalk.gray('❓ unknown'); } const statusConfig = { - 'done': { color: chalk.green, icon: '✅' }, - 'completed': { color: chalk.green, icon: '✅' }, - 'pending': { color: chalk.yellow, icon: '⏱️' }, - 'in-progress': { color: chalk.blue, icon: '🔄' }, - 'deferred': { color: chalk.gray, icon: '⏱️' }, - 'blocked': { color: chalk.red, icon: '❌' }, - 'review': { color: chalk.magenta, icon: '👀' } + 'done': { color: chalk.green, icon: '✅', tableIcon: '✓' }, + 'completed': { color: chalk.green, icon: '✅', tableIcon: '✓' }, + 'pending': { color: chalk.yellow, icon: '⏱️', tableIcon: '⏱' }, + 'in-progress': { color: chalk.hex('#FFA500'), icon: '🔄', tableIcon: '►' }, + 'deferred': { color: chalk.gray, icon: '⏱️', tableIcon: '⏱' }, + 'blocked': { color: chalk.red, icon: '❌', tableIcon: '✗' }, + 'review': { color: chalk.magenta, icon: '👀', tableIcon: '👁' } }; - const config = statusConfig[status.toLowerCase()] || { color: chalk.red, icon: '❌' }; + const config = statusConfig[status.toLowerCase()] || { color: chalk.red, icon: '❌', tableIcon: '✗' }; + + // Use simpler icons for table display to prevent border issues + if (forTable) { + // Use ASCII characters instead of Unicode for completely stable display + const simpleIcons = { + 'done': '✓', + 'completed': '✓', + 'pending': '○', + 'in-progress': '►', + 'deferred': 'x', + 'blocked': '!', // Using plain x character for better compatibility + 'review': '?' // Using circled dot symbol + }; + const simpleIcon = simpleIcons[status.toLowerCase()] || 'x'; + return config.color(`${simpleIcon} ${status}`); + } + return config.color(`${config.icon} ${status}`); } @@ -131,27 +149,91 @@ function formatDependenciesWithStatus(dependencies, allTasks, forConsole = false } const formattedDeps = dependencies.map(depId => { - const depTask = findTaskById(allTasks, depId); + const depIdStr = depId.toString(); // Ensure string format for display + + // Check if it's already a fully qualified subtask ID (like "22.1") + if (depIdStr.includes('.')) { + const [parentId, subtaskId] = depIdStr.split('.').map(id => parseInt(id, 10)); + + // Find the parent task + const parentTask = allTasks.find(t => t.id === parentId); + if (!parentTask || !parentTask.subtasks) { + return forConsole ? + chalk.red(`${depIdStr} (Not found)`) : + `${depIdStr} (Not found)`; + } + + // Find the subtask + const subtask = parentTask.subtasks.find(st => st.id === subtaskId); + if (!subtask) { + return forConsole ? + chalk.red(`${depIdStr} (Not found)`) : + `${depIdStr} (Not found)`; + } + + // Format with status + const status = subtask.status || 'pending'; + const isDone = status.toLowerCase() === 'done' || status.toLowerCase() === 'completed'; + const isInProgress = status.toLowerCase() === 'in-progress'; + + if (forConsole) { + if (isDone) { + return chalk.green.bold(depIdStr); + } else if (isInProgress) { + return chalk.hex('#FFA500').bold(depIdStr); + } else { + return chalk.red.bold(depIdStr); + } + } + + const statusIcon = isDone ? '✅' : '⏱️'; + return `${statusIcon} ${depIdStr} (${status})`; + } + + // If depId is a number less than 100, it's likely a reference to a subtask ID in the current task + // This case is typically handled elsewhere (in task-specific code) before calling this function + + // For regular task dependencies (not subtasks) + // Convert string depId to number if needed + const numericDepId = typeof depId === 'string' ? parseInt(depId, 10) : depId; + + // Look up the task using the numeric ID + const depTask = findTaskById(allTasks, numericDepId); if (!depTask) { return forConsole ? - chalk.red(`${depId} (Not found)`) : - `${depId} (Not found)`; + chalk.red(`${depIdStr} (Not found)`) : + `${depIdStr} (Not found)`; } const status = depTask.status || 'pending'; const isDone = status.toLowerCase() === 'done' || status.toLowerCase() === 'completed'; + const isInProgress = status.toLowerCase() === 'in-progress'; + // Apply colors for console output with more visible options if (forConsole) { - return isDone ? - chalk.green(`${depId}`) : - chalk.red(`${depId}`); + if (isDone) { + return chalk.green.bold(depIdStr); // Make completed dependencies bold green + } else if (isInProgress) { + return chalk.hex('#FFA500').bold(depIdStr); // Use bright orange for in-progress (more visible) + } else { + return chalk.red.bold(depIdStr); // Make pending dependencies bold red + } } const statusIcon = isDone ? '✅' : '⏱️'; - return `${statusIcon} ${depId} (${status})`; + return `${statusIcon} ${depIdStr} (${status})`; }); + if (forConsole) { + // Handle both single and multiple dependencies + if (dependencies.length === 1) { + return formattedDeps[0]; // Return the single colored dependency + } + // Join multiple dependencies with white commas + return formattedDeps.join(chalk.white(', ')); + } + return formattedDeps.join(', '); } @@ -342,6 +424,18 @@ function getComplexityWithColor(score) { return chalk.red(`🔴 ${score}`); } +/** + * Truncate a string to a maximum length and add ellipsis if needed + * @param {string} str - The string to truncate + * @param {number} maxLength - Maximum length + * @returns {string} Truncated string + */ +function truncateString(str, maxLength) { + if (!str) return ''; + if (str.length <= maxLength) return str; + return str.substring(0, maxLength - 3) + '...'; +} + /** * Display the next task to work on * @param {string} tasksPath - Path to the tasks.json file @@ -386,7 +480,8 @@ async function displayNextTask(tasksPath) { chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, - colWidths: [15, 75] + colWidths: [15, Math.min(75, (process.stdout.columns - 20) || 60)], + wordWrap: true }); // Priority with color @@ -424,15 +519,15 @@ async function displayNextTask(tasksPath) { { padding: { top: 0, bottom: 0, left: 1, right: 1 }, margin: { top: 1, bottom: 0 }, borderColor: 'magenta', borderStyle: 'round' } )); - // Create a table for subtasks + // Create a table for subtasks with improved handling const subtaskTable = new Table({ head: [ chalk.magenta.bold('ID'), chalk.magenta.bold('Status'), chalk.magenta.bold('Title'), - chalk.magenta.bold('Dependencies') + chalk.magenta.bold('Deps') ], - colWidths: [6, 12, 50, 20], + colWidths: [6, 12, Math.min(50, process.stdout.columns - 65 || 30), 30], style: { head: [], border: [], @@ -442,7 +537,8 @@ async function displayNextTask(tasksPath) { }, chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' - } + }, + wordWrap: true }); // Add subtasks to table @@ -460,11 +556,29 @@ async function displayNextTask(tasksPath) { // Format dependencies with correct notation const formattedDeps = st.dependencies.map(depId => { if (typeof depId === 'number' && depId < 100) { - return `${nextTask.id}.${depId}`; + const foundSubtask = nextTask.subtasks.find(st => st.id === depId); + if (foundSubtask) { + const isDone = foundSubtask.status === 'done' || foundSubtask.status === 'completed'; + const isInProgress = foundSubtask.status === 'in-progress'; + + // Use consistent color formatting instead of emojis + if (isDone) { + return chalk.green.bold(`${nextTask.id}.${depId}`); + } else if (isInProgress) { + return chalk.hex('#FFA500').bold(`${nextTask.id}.${depId}`); + } else { + return chalk.red.bold(`${nextTask.id}.${depId}`); + } + } + return chalk.red(`${nextTask.id}.${depId} (Not found)`); } return depId; }); - subtaskDeps = formatDependenciesWithStatus(formattedDeps, data.tasks, true); + + // Join the formatted dependencies directly instead of passing to formatDependenciesWithStatus again + subtaskDeps = formattedDeps.length === 1 + ? formattedDeps[0] + : formattedDeps.join(chalk.white(', ')); } subtaskTable.push([ @@ -542,7 +656,8 @@ async function displayTaskById(tasksPath, taskId) { chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, - colWidths: [15, 75] + colWidths: [15, Math.min(75, (process.stdout.columns - 20) || 60)], + wordWrap: true }); // Add subtask details to table @@ -550,7 +665,7 @@ async function displayTaskById(tasksPath, taskId) { [chalk.cyan.bold('ID:'), `${task.parentTask.id}.${task.id}`], [chalk.cyan.bold('Parent Task:'), `#${task.parentTask.id} - ${task.parentTask.title}`], [chalk.cyan.bold('Title:'), task.title], - [chalk.cyan.bold('Status:'), getStatusWithColor(task.status || 'pending')], + [chalk.cyan.bold('Status:'), getStatusWithColor(task.status || 'pending', true)], [chalk.cyan.bold('Description:'), task.description || 'No description provided.'] ); @@ -574,7 +689,7 @@ async function displayTaskById(tasksPath, taskId) { { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 0 } } )); - // Create a table with task details + // Create a table with task details with improved handling const taskTable = new Table({ style: { head: [], @@ -586,7 +701,8 @@ async function displayTaskById(tasksPath, taskId) { chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, - colWidths: [15, 75] + colWidths: [15, Math.min(75, (process.stdout.columns - 20) || 60)], + wordWrap: true }); // Priority with color @@ -601,7 +717,7 @@ async function displayTaskById(tasksPath, taskId) { taskTable.push( [chalk.cyan.bold('ID:'), task.id.toString()], [chalk.cyan.bold('Title:'), task.title], - [chalk.cyan.bold('Status:'), getStatusWithColor(task.status || 'pending')], + [chalk.cyan.bold('Status:'), getStatusWithColor(task.status || 'pending', true)], [chalk.cyan.bold('Priority:'), priorityColor(task.priority || 'medium')], [chalk.cyan.bold('Dependencies:'), formatDependenciesWithStatus(task.dependencies, data.tasks, true)], [chalk.cyan.bold('Description:'), task.description] @@ -634,15 +750,15 @@ async function displayTaskById(tasksPath, taskId) { { padding: { top: 0, bottom: 0, left: 1, right: 1 }, margin: { top: 1, bottom: 0 }, borderColor: 'magenta', borderStyle: 'round' } )); - // Create a table for subtasks + // Create a table for subtasks with improved handling const subtaskTable = new Table({ head: [ chalk.magenta.bold('ID'), chalk.magenta.bold('Status'), chalk.magenta.bold('Title'), - chalk.magenta.bold('Dependencies') + chalk.magenta.bold('Deps') ], - colWidths: [6, 12, 50, 20], + colWidths: [6, 12, Math.min(50, process.stdout.columns - 65 || 30), 30], style: { head: [], border: [], @@ -652,7 +768,8 @@ async function displayTaskById(tasksPath, taskId) { }, chars: { 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' - } + }, + wordWrap: true }); // Add subtasks to table @@ -670,11 +787,29 @@ async function displayTaskById(tasksPath, taskId) { // Format dependencies with correct notation const formattedDeps = st.dependencies.map(depId => { if (typeof depId === 'number' && depId < 100) { - return `${task.id}.${depId}`; + const foundSubtask = task.subtasks.find(st => st.id === depId); + if (foundSubtask) { + const isDone = foundSubtask.status === 'done' || foundSubtask.status === 'completed'; + const isInProgress = foundSubtask.status === 'in-progress'; + + // Use consistent color formatting instead of emojis + if (isDone) { + return chalk.green.bold(`${task.id}.${depId}`); + } else if (isInProgress) { + return chalk.hex('#FFA500').bold(`${task.id}.${depId}`); + } else { + return chalk.red.bold(`${task.id}.${depId}`); + } + } + return chalk.red(`${task.id}.${depId} (Not found)`); } return depId; }); - subtaskDeps = formatDependenciesWithStatus(formattedDeps, data.tasks, true); + + // Join the formatted dependencies directly instead of passing to formatDependenciesWithStatus again + subtaskDeps = formattedDeps.length === 1 + ? formattedDeps[0] + : formattedDeps.join(chalk.white(', ')); } subtaskTable.push([ diff --git a/tasks/tasks.json b/tasks/tasks.json index 2ce83b8f..c311f93e 100644 --- a/tasks/tasks.json +++ b/tasks/tasks.json @@ -1414,7 +1414,7 @@ "description": "Extend Task Master to function as an MCP server, allowing it to provide context management services to other applications following the Model Context Protocol specification.", "status": "pending", "dependencies": [ - "22" + 22 ], "priority": "medium", "details": "This task involves implementing the Model Context Protocol server capabilities within Task Master. The implementation should:\n\n1. Create a new module `mcp-server.js` that implements the core MCP server functionality\n2. Implement the required MCP endpoints:\n - `/context` - For retrieving and updating context\n - `/models` - For listing available models\n - `/execute` - For executing operations with context\n3. Develop a context management system that can:\n - Store and retrieve context data efficiently\n - Handle context windowing and truncation when limits are reached\n - Support context metadata and tagging\n4. Add authentication and authorization mechanisms for MCP clients\n5. Implement proper error handling and response formatting according to MCP specifications\n6. Create configuration options in Task Master to enable/disable the MCP server functionality\n7. Add documentation for how to use Task Master as an MCP server\n8. Ensure the implementation is compatible with existing MCP clients\n9. Optimize for performance, especially for context retrieval operations\n10. Add logging for MCP server operations\n\nThe implementation should follow RESTful API design principles and should be able to handle concurrent requests from multiple clients.", @@ -1426,7 +1426,7 @@ "description": "Create a new 'generate-test' command that leverages AI to automatically produce Jest test files for tasks based on their descriptions and subtasks.", "status": "pending", "dependencies": [ - "22" + 22 ], "priority": "high", "details": "Implement a new command in the Task Master CLI that generates comprehensive Jest test files for tasks. The command should be callable as 'task-master generate-test --id=1' and should:\n\n1. Accept a task ID parameter to identify which task to generate tests for\n2. Retrieve the task and its subtasks from the task store\n3. Analyze the task description, details, and subtasks to understand implementation requirements\n4. Construct an appropriate prompt for an AI service (e.g., OpenAI API) that requests generation of Jest tests\n5. Process the AI response to create a well-formatted test file named 'task_XXX.test.js' where XXX is the zero-padded task ID\n6. Include appropriate test cases that cover the main functionality described in the task\n7. Generate mocks for external dependencies identified in the task description\n8. Create assertions that validate the expected behavior\n9. Handle both parent tasks and subtasks appropriately (for subtasks, name the file 'task_XXX_YYY.test.js' where YYY is the subtask ID)\n10. Include error handling for API failures, invalid task IDs, etc.\n11. Add appropriate documentation for the command in the help system\n\nThe implementation should utilize the existing AI service integration in the codebase and maintain consistency with the current command structure and error handling patterns.", From 0c874f93e9a2ab8ac435be16ad41363ce5271c90 Mon Sep 17 00:00:00 2001 From: Eyal Toledano Date: Mon, 24 Mar 2025 16:30:27 -0400 Subject: [PATCH 2/3] fixes issue with perplexity model used by default (now sonar-pro in all cases). Fixes an issue preventing analyzeTaskComplexity to work as designed. Fixes an issue that prevented parse-prd from working. Stubs in the test for analyzeTaskComplexity to be done later. --- assets/env.example | 1 + bin/task-master.js | 46 ++++--- scripts/modules/ai-services.js | 6 +- scripts/modules/commands.js | 19 ++- scripts/modules/task-manager.js | 22 ++-- scripts/modules/ui.js | 2 +- scripts/task-complexity-report.json | 186 ++++++++++++++++------------ tests/unit/task-manager.test.js | 32 +++++ 8 files changed, 200 insertions(+), 114 deletions(-) diff --git a/assets/env.example b/assets/env.example index 3cea0529..7dc2f972 100644 --- a/assets/env.example +++ b/assets/env.example @@ -4,6 +4,7 @@ PERPLEXITY_API_KEY=pplx-abcde # For research (recommended but optional) # Optional - defaults shown MODEL=claude-3-7-sonnet-20250219 # Recommended models: claude-3-7-sonnet-20250219, claude-3-opus-20240229 +PERPLEXITY_MODEL=sonar-pro # Make sure you have access to sonar-pro otherwise you can use sonar regular. MAX_TOKENS=4000 # Maximum tokens for model responses TEMPERATURE=0.7 # Temperature for model responses (0.0-1.0) DEBUG=false # Enable debug logging (true/false) diff --git a/bin/task-master.js b/bin/task-master.js index a8640e9a..6364967f 100755 --- a/bin/task-master.js +++ b/bin/task-master.js @@ -10,6 +10,7 @@ import { dirname, resolve } from 'path'; import { createRequire } from 'module'; import { spawn } from 'child_process'; import { Command } from 'commander'; +import { displayHelp, displayBanner } from '../scripts/modules/ui.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); @@ -43,28 +44,17 @@ program .description('Claude Task Master CLI') .version(version) .addHelpText('afterAll', () => { - // Add the same help output that dev.js uses - const child = spawn('node', [devScriptPath, '--help'], { - stdio: ['inherit', 'pipe', 'inherit'], - cwd: process.cwd() - }); - - let output = ''; - child.stdout.on('data', (data) => { - output += data.toString(); - }); - - child.on('close', () => { - // Only display the custom help text part, not the commander-generated part - const customHelpStart = output.indexOf('Task Master CLI'); - if (customHelpStart > -1) { - console.log('\n' + output.substring(customHelpStart)); - } - }); - - return ''; // Return empty string to prevent immediate display + // Use the same help display function as dev.js for consistency + displayHelp(); + return ''; // Return empty string to prevent commander's default help }); +// Add custom help option to directly call our help display +program.helpOption('-h, --help', 'Display help information'); +program.on('--help', () => { + displayHelp(); +}); + program .command('init') .description('Initialize a new project') @@ -146,11 +136,12 @@ program program .command('parse-prd') .description('Parse a PRD file and generate tasks') - .argument('', 'Path to the PRD file') + .argument('[file]', 'Path to the PRD file') .option('-o, --output ', 'Output file path', 'tasks/tasks.json') .option('-n, --num-tasks ', 'Number of tasks to generate', '10') .action((file, options) => { - const args = ['parse-prd', file]; + const args = ['parse-prd']; + if (file) args.push(file); if (options.output) args.push('--output', options.output); if (options.numTasks) args.push('--num-tasks', options.numTasks); runDevScript(args); @@ -256,7 +247,7 @@ program program .command('show') - .description('Show details of a specific task by ID') + .description('Display detailed information about a specific task') .argument('[id]', 'Task ID to show') .option('-i, --id ', 'Task ID to show (alternative to argument)') .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') @@ -326,4 +317,11 @@ program runDevScript(args); }); -program.parse(process.argv); \ No newline at end of file +program.parse(process.argv); + +// Show help if no command was provided (just 'task-master' with no args) +if (process.argv.length <= 2) { + displayBanner(); + displayHelp(); + process.exit(0); +} \ No newline at end of file diff --git a/scripts/modules/ai-services.js b/scripts/modules/ai-services.js index 11326920..0128ad6f 100644 --- a/scripts/modules/ai-services.js +++ b/scripts/modules/ai-services.js @@ -305,7 +305,7 @@ async function generateSubtasksWithPerplexity(task, numSubtasks = 3, nextSubtask log('info', `Researching context for task ${task.id}: ${task.title}`); const perplexityClient = getPerplexityClient(); - const PERPLEXITY_MODEL = process.env.PERPLEXITY_MODEL || 'sonar-small-online'; + const PERPLEXITY_MODEL = process.env.PERPLEXITY_MODEL || 'sonar-pro'; const researchLoadingIndicator = startLoadingIndicator('Researching best practices with Perplexity AI...'); // Formulate research query based on task @@ -493,13 +493,13 @@ function parseSubtasksFromText(text, startId, expectedCount, parentTaskId) { /** * Generate a prompt for complexity analysis - * @param {Array} tasksData - Tasks data + * @param {Object} tasksData - Tasks data object containing tasks array * @returns {string} Generated prompt */ function generateComplexityAnalysisPrompt(tasksData) { return `Analyze the complexity of the following tasks and provide recommendations for subtask breakdown: -${tasksData.map(task => ` +${tasksData.tasks.map(task => ` Task ID: ${task.id} Title: ${task.title} Description: ${task.description} diff --git a/scripts/modules/commands.js b/scripts/modules/commands.js index e7b776d7..9d8549a8 100644 --- a/scripts/modules/commands.js +++ b/scripts/modules/commands.js @@ -52,10 +52,27 @@ function registerCommands(programInstance) { programInstance .command('parse-prd') .description('Parse a PRD file and generate tasks') - .argument('', 'Path to the PRD file') + .argument('[file]', 'Path to the PRD file') .option('-o, --output ', 'Output file path', 'tasks/tasks.json') .option('-n, --num-tasks ', 'Number of tasks to generate', '10') .action(async (file, options) => { + if (!file) { + console.log(chalk.yellow('No PRD file specified.')); + console.log(boxen( + chalk.white.bold('Parse PRD Help') + '\n\n' + + chalk.cyan('Usage:') + '\n' + + ` task-master parse-prd [options]\n\n` + + chalk.cyan('Options:') + '\n' + + ' -o, --output Output file path (default: "tasks/tasks.json")\n' + + ' -n, --num-tasks Number of tasks to generate (default: 10)\n\n' + + chalk.cyan('Example:') + '\n' + + ' task-master parse-prd requirements.txt --num-tasks 15\n\n' + + chalk.yellow('Note: This command will generate tasks from a PRD document and will overwrite any existing tasks.json file.'), + { padding: 1, borderColor: 'blue', borderStyle: 'round' } + )); + return; + } + const numTasks = parseInt(options.numTasks, 10); const outputPath = options.output; diff --git a/scripts/modules/task-manager.js b/scripts/modules/task-manager.js index 55df1047..3cc96220 100644 --- a/scripts/modules/task-manager.js +++ b/scripts/modules/task-manager.js @@ -207,7 +207,7 @@ The changes described in the prompt should be applied to ALL tasks in the list.` log('info', 'Using Perplexity AI for research-backed task updates'); // Call Perplexity AI using format consistent with ai-services.js - const perplexityModel = process.env.PERPLEXITY_MODEL || 'sonar-small-online'; + const perplexityModel = process.env.PERPLEXITY_MODEL || 'sonar-pro'; const result = await perplexity.chat.completions.create({ model: perplexityModel, messages: [ @@ -1756,7 +1756,7 @@ Your response must be a clean JSON array only, following exactly this format: DO NOT include any text before or after the JSON array. No explanations, no markdown formatting.`; const result = await perplexity.chat.completions.create({ - model: PERPLEXITY_MODEL, + model: process.env.PERPLEXITY_MODEL || 'sonar-pro', messages: [ { role: "system", @@ -1767,8 +1767,8 @@ DO NOT include any text before or after the JSON array. No explanations, no mark content: researchPrompt } ], - temperature: TEMPERATURE, - max_tokens: MAX_TOKENS, + temperature: CONFIG.temperature, + max_tokens: CONFIG.maxTokens, }); // Extract the response text @@ -1889,7 +1889,13 @@ DO NOT include any text before or after the JSON array. No explanations, no mark // 3. Replace single quotes with double quotes for property values cleanedResponse = cleanedResponse.replace(/:(\s*)'([^']*)'(\s*[,}])/g, ':$1"$2"$3'); - // 4. Add a special fallback option if we're still having issues + // 4. Fix unterminated strings - common with LLM responses + const untermStringPattern = /:(\s*)"([^"]*)(?=[,}])/g; + cleanedResponse = cleanedResponse.replace(untermStringPattern, ':$1"$2"'); + + // 5. Fix multi-line strings by replacing newlines + cleanedResponse = cleanedResponse.replace(/:(\s*)"([^"]*)\n([^"]*)"/g, ':$1"$2 $3"'); + try { complexityAnalysis = JSON.parse(cleanedResponse); console.log(chalk.green("Successfully parsed JSON after fixing common issues")); @@ -2006,7 +2012,7 @@ Your response must be a clean JSON array only, following exactly this format: DO NOT include any text before or after the JSON array. No explanations, no markdown formatting.`; const result = await perplexity.chat.completions.create({ - model: PERPLEXITY_MODEL, + model: process.env.PERPLEXITY_MODEL || 'sonar-pro', messages: [ { role: "system", @@ -2017,8 +2023,8 @@ DO NOT include any text before or after the JSON array. No explanations, no mark content: missingTasksResearchPrompt } ], - temperature: TEMPERATURE, - max_tokens: MAX_TOKENS, + temperature: CONFIG.temperature, + max_tokens: CONFIG.maxTokens, }); // Extract the response diff --git a/scripts/modules/ui.js b/scripts/modules/ui.js index 01816474..a98651fe 100644 --- a/scripts/modules/ui.js +++ b/scripts/modules/ui.js @@ -391,7 +391,7 @@ function displayHelp() { `${chalk.dim('Optional')}${chalk.reset('')}`], [`${chalk.yellow('PERPLEXITY_MODEL')}${chalk.reset('')}`, `${chalk.white('Perplexity model to use')}${chalk.reset('')}`, - `${chalk.dim('Default: sonar-small-online')}${chalk.reset('')}`], + `${chalk.dim('Default: sonar-pro')}${chalk.reset('')}`], [`${chalk.yellow('DEBUG')}${chalk.reset('')}`, `${chalk.white('Enable debug logging')}${chalk.reset('')}`, `${chalk.dim(`Default: ${CONFIG.debug}`)}${chalk.reset('')}`], diff --git a/scripts/task-complexity-report.json b/scripts/task-complexity-report.json index 6828b4cc..5b0b8e01 100644 --- a/scripts/task-complexity-report.json +++ b/scripts/task-complexity-report.json @@ -1,171 +1,203 @@ { "meta": { - "generatedAt": "2025-03-21T20:01:53.007Z", - "tasksAnalyzed": 20, + "generatedAt": "2025-03-24T20:01:35.986Z", + "tasksAnalyzed": 24, "thresholdScore": 5, "projectName": "Your Project Name", - "usedResearch": true + "usedResearch": false }, "complexityAnalysis": [ { "taskId": 1, "taskTitle": "Implement Task Data Structure", - "complexityScore": 8, + "complexityScore": 7, "recommendedSubtasks": 5, - "expansionPrompt": "Break down the task of creating the tasks.json structure into subtasks focusing on schema design, model creation, validation, file operations, and error handling.", - "reasoning": "This task involves multiple critical components including schema design, model creation, and file operations, each requiring detailed attention and validation." + "expansionPrompt": "Break down the implementation of the core tasks.json data structure into subtasks that cover schema design, model implementation, validation, file operations, and error handling. For each subtask, include specific technical requirements and acceptance criteria.", + "reasoning": "This task requires designing a foundational data structure that will be used throughout the system. It involves schema design, validation logic, and file system operations, which together represent moderate to high complexity. The task is critical as many other tasks depend on it." }, { "taskId": 2, "taskTitle": "Develop Command Line Interface Foundation", - "complexityScore": 7, - "recommendedSubtasks": 5, - "expansionPrompt": "Divide the CLI development into subtasks such as command parsing, help documentation, console output, logging system, and global options handling.", - "reasoning": "Creating a CLI involves several distinct functionalities that need to be implemented and integrated, each contributing to the overall complexity." + "complexityScore": 6, + "recommendedSubtasks": 4, + "expansionPrompt": "Divide the CLI foundation implementation into subtasks covering Commander.js setup, help documentation creation, console output formatting, and global options handling. Each subtask should specify implementation details and how it integrates with the overall CLI structure.", + "reasoning": "Setting up the CLI foundation requires integrating Commander.js, implementing various command-line options, and establishing the output formatting system. The complexity is moderate as it involves creating the interface layer that users will interact with." }, { "taskId": 3, "taskTitle": "Implement Basic Task Operations", - "complexityScore": 9, - "recommendedSubtasks": 6, - "expansionPrompt": "Break down the task operations into subtasks including listing, creating, updating, deleting, status changes, dependency management, and priority handling.", - "reasoning": "This task requires implementing a wide range of operations, each with its own logic and dependencies, increasing the complexity." + "complexityScore": 8, + "recommendedSubtasks": 5, + "expansionPrompt": "Break down the implementation of basic task operations into subtasks covering CRUD operations, status management, dependency handling, and priority management. Each subtask should detail the specific operations, validation requirements, and error cases to handle.", + "reasoning": "This task encompasses multiple operations (create, read, update, delete) along with status changes, dependency management, and priority handling. It represents high complexity due to the breadth of functionality and the need to ensure data integrity across operations." }, { "taskId": 4, "taskTitle": "Create Task File Generation System", "complexityScore": 7, - "recommendedSubtasks": 5, - "expansionPrompt": "Divide the file generation system into subtasks such as template creation, file generation, synchronization, file naming, and update handling.", - "reasoning": "The task involves creating a system that generates and synchronizes files, requiring careful handling of templates and updates." + "recommendedSubtasks": 4, + "expansionPrompt": "Divide the task file generation system into subtasks covering template creation, file generation logic, bi-directional synchronization, and file organization. Each subtask should specify the technical approach, edge cases to handle, and integration points with the task data structure.", + "reasoning": "Implementing file generation with bi-directional synchronization presents significant complexity due to the need to maintain consistency between individual files and the central tasks.json. The system must handle updates in either direction and resolve potential conflicts." }, { "taskId": 5, "taskTitle": "Integrate Anthropic Claude API", - "complexityScore": 8, - "recommendedSubtasks": 6, - "expansionPrompt": "Break down the API integration into subtasks including authentication, prompt templates, response handling, error management, token tracking, and model configuration.", - "reasoning": "Integrating an external API involves multiple steps from authentication to response handling, each adding to the complexity." + "complexityScore": 6, + "recommendedSubtasks": 4, + "expansionPrompt": "Break down the Claude API integration into subtasks covering authentication setup, prompt template creation, response handling, and error management with retries. Each subtask should detail the specific implementation approach, including security considerations and performance optimizations.", + "reasoning": "Integrating with the Claude API involves setting up authentication, creating effective prompts, and handling responses and errors. The complexity is moderate, focusing on establishing a reliable connection to the external service with proper error handling and retry logic." }, { "taskId": 6, "taskTitle": "Build PRD Parsing System", - "complexityScore": 9, - "recommendedSubtasks": 6, - "expansionPrompt": "Divide the PRD parsing system into subtasks such as file reading, prompt engineering, task conversion, dependency inference, priority assignment, and chunking.", - "reasoning": "Parsing PRDs and converting them into tasks requires handling various complexities including dependency inference and priority assignment." + "complexityScore": 8, + "recommendedSubtasks": 5, + "expansionPrompt": "Divide the PRD parsing system into subtasks covering file reading, prompt engineering, content-to-task conversion, dependency inference, priority assignment, and handling large documents. Each subtask should specify the AI interaction approach, data transformation steps, and validation requirements.", + "reasoning": "Parsing PRDs into structured tasks requires sophisticated prompt engineering and intelligent processing of unstructured text. The complexity is high due to the need to accurately extract tasks, infer dependencies, and handle potentially large documents with varying formats." }, { "taskId": 7, "taskTitle": "Implement Task Expansion with Claude", "complexityScore": 7, - "recommendedSubtasks": 5, - "expansionPrompt": "Break down the task expansion into subtasks including prompt creation, workflow implementation, context-aware expansion, relationship management, subtask specification, and regeneration.", - "reasoning": "Expanding tasks into subtasks using AI involves creating prompts and managing relationships, adding to the complexity." + "recommendedSubtasks": 4, + "expansionPrompt": "Break down the task expansion functionality into subtasks covering prompt creation for subtask generation, expansion workflow implementation, parent-child relationship management, and regeneration mechanisms. Each subtask should detail the AI interaction patterns, data structures, and user experience considerations.", + "reasoning": "Task expansion involves complex AI interactions to generate meaningful subtasks and manage their relationships with parent tasks. The complexity comes from creating effective prompts that produce useful subtasks and implementing a smooth workflow for users to generate and refine these subtasks." }, { "taskId": 8, "taskTitle": "Develop Implementation Drift Handling", - "complexityScore": 8, + "complexityScore": 9, "recommendedSubtasks": 5, - "expansionPrompt": "Divide the drift handling into subtasks including task updates, rewriting, dependency chain updates, completed work preservation, and update analysis.", - "reasoning": "Handling implementation drift requires updating tasks and dependencies while preserving completed work, increasing complexity." + "expansionPrompt": "Divide the implementation drift handling into subtasks covering change detection, task rewriting based on new context, dependency chain updates, work preservation, and update suggestion analysis. Each subtask should specify the algorithms, heuristics, and AI prompts needed to effectively manage implementation changes.", + "reasoning": "This task involves the complex challenge of updating future tasks based on changes in implementation. It requires sophisticated analysis of completed work, understanding how it affects pending tasks, and intelligently updating those tasks while preserving dependencies. This represents high complexity due to the need for context-aware AI reasoning." }, { "taskId": 9, "taskTitle": "Integrate Perplexity API", - "complexityScore": 7, - "recommendedSubtasks": 5, - "expansionPrompt": "Break down the API integration into subtasks including authentication, prompt templates, response handling, fallback logic, quality comparison, and model selection.", - "reasoning": "Integrating another external API involves similar complexities as the Claude API integration, including authentication and response handling." + "complexityScore": 5, + "recommendedSubtasks": 3, + "expansionPrompt": "Break down the Perplexity API integration into subtasks covering authentication setup, research-oriented prompt creation, response handling, and fallback mechanisms. Each subtask should detail the implementation approach, integration with existing systems, and quality comparison metrics.", + "reasoning": "Similar to the Claude integration but slightly less complex, this task focuses on connecting to the Perplexity API for research capabilities. The complexity is moderate, involving API authentication, prompt templates, and response handling with fallback mechanisms to Claude." }, { "taskId": 10, "taskTitle": "Create Research-Backed Subtask Generation", - "complexityScore": 8, - "recommendedSubtasks": 6, - "expansionPrompt": "Divide the research-backed generation into subtasks including prompt creation, context enrichment, domain knowledge incorporation, detailed generation, and reference inclusion.", - "reasoning": "Enhancing subtask generation with research requires handling domain-specific knowledge and context enrichment, adding complexity." + "complexityScore": 7, + "recommendedSubtasks": 4, + "expansionPrompt": "Divide the research-backed subtask generation into subtasks covering domain-specific prompt creation, context enrichment from research, knowledge incorporation, and detailed subtask generation. Each subtask should specify the approach for leveraging research data and integrating it into the generation process.", + "reasoning": "This task builds on previous work to enhance subtask generation with research capabilities. The complexity comes from effectively incorporating research results into the generation process and creating domain-specific prompts that produce high-quality, detailed subtasks with best practices." }, { "taskId": 11, "taskTitle": "Implement Batch Operations", - "complexityScore": 7, - "recommendedSubtasks": 5, - "expansionPrompt": "Break down the batch operations into subtasks including status updates, subtask generation, task filtering, dependency management, prioritization, and command creation.", - "reasoning": "Implementing batch operations involves handling multiple tasks simultaneously, each with its own set of operations." + "complexityScore": 6, + "recommendedSubtasks": 4, + "expansionPrompt": "Break down the batch operations functionality into subtasks covering multi-task status updates, bulk subtask generation, task filtering/querying, and batch prioritization. Each subtask should detail the command interface, implementation approach, and performance considerations for handling multiple tasks.", + "reasoning": "Implementing batch operations requires extending existing functionality to work with multiple tasks simultaneously. The complexity is moderate, focusing on efficient processing of task sets, filtering capabilities, and maintaining data consistency across bulk operations." }, { "taskId": 12, "taskTitle": "Develop Project Initialization System", - "complexityScore": 8, - "recommendedSubtasks": 6, - "expansionPrompt": "Divide the project initialization into subtasks including templating, setup wizard, environment configuration, directory structure, example tasks, and default configuration.", - "reasoning": "Creating a project initialization system involves setting up multiple components and configurations, increasing complexity." + "complexityScore": 6, + "recommendedSubtasks": 4, + "expansionPrompt": "Divide the project initialization system into subtasks covering project templating, interactive setup wizard, environment configuration, directory structure creation, and example generation. Each subtask should specify the user interaction flow, template design, and integration with existing components.", + "reasoning": "Creating a project initialization system involves setting up templates, an interactive wizard, and generating initial files and directories. The complexity is moderate, focusing on providing a smooth setup experience for new projects with appropriate defaults and configuration." }, { "taskId": 13, "taskTitle": "Create Cursor Rules Implementation", - "complexityScore": 7, - "recommendedSubtasks": 5, - "expansionPrompt": "Break down the Cursor rules implementation into subtasks including documentation creation, rule implementation, directory setup, and integration documentation.", - "reasoning": "Implementing Cursor rules involves creating documentation and setting up directory structures, adding to the complexity." + "complexityScore": 5, + "recommendedSubtasks": 3, + "expansionPrompt": "Break down the Cursor rules implementation into subtasks covering documentation creation (dev_workflow.mdc, cursor_rules.mdc, self_improve.mdc), directory structure setup, and integration documentation. Each subtask should detail the specific content to include and how it enables effective AI interaction.", + "reasoning": "This task focuses on creating documentation and rules for Cursor AI integration. The complexity is moderate, involving the creation of structured documentation files that define how AI should interact with the system and setting up the appropriate directory structure." }, { "taskId": 14, "taskTitle": "Develop Agent Workflow Guidelines", - "complexityScore": 7, - "recommendedSubtasks": 5, - "expansionPrompt": "Divide the agent workflow guidelines into subtasks including task discovery, selection, implementation, verification, prioritization, and dependency handling.", - "reasoning": "Creating guidelines for AI agents involves defining workflows and handling dependencies, increasing complexity." + "complexityScore": 5, + "recommendedSubtasks": 3, + "expansionPrompt": "Divide the agent workflow guidelines into subtasks covering task discovery documentation, selection guidelines, implementation guidance, verification procedures, and prioritization rules. Each subtask should specify the specific guidance to provide and how it enables effective agent workflows.", + "reasoning": "Creating comprehensive guidelines for AI agents involves documenting workflows, selection criteria, and implementation guidance. The complexity is moderate, focusing on clear documentation that helps agents interact effectively with the task system." }, { "taskId": 15, - "taskTitle": "Implement Agent Command Integration", - "complexityScore": 8, - "recommendedSubtasks": 6, - "expansionPrompt": "Break down the agent command integration into subtasks including command syntax, example interactions, response patterns, context management, special flags, and output interpretation.", - "reasoning": "Integrating commands for AI agents involves handling syntax, responses, and context, adding to the complexity." + "taskTitle": "Optimize Agent Integration with Cursor and dev.js Commands", + "complexityScore": 6, + "recommendedSubtasks": 4, + "expansionPrompt": "Break down the agent integration optimization into subtasks covering existing pattern documentation, Cursor-dev.js command integration enhancement, workflow documentation improvement, and feature additions. Each subtask should specify the specific improvements to make and how they enhance agent interaction.", + "reasoning": "This task involves enhancing and documenting existing agent interaction patterns with Cursor and dev.js commands. The complexity is moderate, focusing on improving integration between different components and ensuring agents can effectively utilize the system's capabilities." }, { "taskId": 16, "taskTitle": "Create Configuration Management System", - "complexityScore": 8, - "recommendedSubtasks": 6, - "expansionPrompt": "Divide the configuration management into subtasks including environment handling, .env support, validation, defaults, template creation, documentation, and API key security.", - "reasoning": "Implementing a robust configuration system involves handling environment variables, validation, and security, increasing complexity." + "complexityScore": 6, + "recommendedSubtasks": 4, + "expansionPrompt": "Divide the configuration management system into subtasks covering environment variable handling, .env file support, configuration validation, defaults with overrides, and secure API key handling. Each subtask should specify the implementation approach, security considerations, and user experience for configuration.", + "reasoning": "Implementing robust configuration management involves handling environment variables, .env files, validation, and secure storage of sensitive information. The complexity is moderate, focusing on creating a flexible system that works across different environments with appropriate security measures." }, { "taskId": 17, "taskTitle": "Implement Comprehensive Logging System", - "complexityScore": 7, - "recommendedSubtasks": 5, - "expansionPrompt": "Break down the logging system into subtasks including log levels, output destinations, command logging, API logging, error tracking, metrics, and file rotation.", - "reasoning": "Creating a logging system involves implementing multiple log levels and destinations, adding to the complexity." + "complexityScore": 5, + "recommendedSubtasks": 3, + "expansionPrompt": "Break down the logging system implementation into subtasks covering log level configuration, output destination management, specialized logging (commands, APIs, errors), and performance metrics. Each subtask should detail the implementation approach, configuration options, and integration with existing components.", + "reasoning": "Creating a comprehensive logging system involves implementing multiple log levels, configurable destinations, and specialized logging for different components. The complexity is moderate, focusing on providing useful information for debugging and monitoring while maintaining performance." }, { "taskId": 18, "taskTitle": "Create Comprehensive User Documentation", - "complexityScore": 8, - "recommendedSubtasks": 6, - "expansionPrompt": "Divide the user documentation into subtasks including README creation, command reference, configuration guide, examples, troubleshooting, API documentation, and best practices.", - "reasoning": "Developing comprehensive documentation involves covering multiple aspects of the system, increasing complexity." + "complexityScore": 7, + "recommendedSubtasks": 5, + "expansionPrompt": "Divide the user documentation creation into subtasks covering README with installation instructions, command reference, configuration guide, example workflows, troubleshooting guides, and advanced usage. Each subtask should specify the content to include, format, and organization to ensure comprehensive coverage.", + "reasoning": "Creating comprehensive documentation requires covering installation, usage, configuration, examples, and troubleshooting across multiple components. The complexity is moderate to high due to the breadth of functionality to document and the need to make it accessible to different user levels." }, { "taskId": 19, "taskTitle": "Implement Error Handling and Recovery", "complexityScore": 8, - "recommendedSubtasks": 6, - "expansionPrompt": "Break down the implementation of error handling and recovery into 6 subtasks, focusing on different aspects like message formatting, API handling, file system recovery, data validation, command errors, and system state recovery. For each subtask, specify the key components to implement and any specific techniques or best practices to consider.", - "reasoning": "High complexity due to system-wide implementation, multiple error types, and recovery mechanisms. Requires careful design and integration across various system components." + "recommendedSubtasks": 5, + "expansionPrompt": "Break down the error handling implementation into subtasks covering consistent error formatting, helpful error messages, API error handling with retries, file system error recovery, validation errors, and system state recovery. Each subtask should detail the specific error types to handle, recovery strategies, and user communication approach.", + "reasoning": "Implementing robust error handling across the entire system represents high complexity due to the variety of error types, the need for meaningful messages, and the implementation of recovery mechanisms. This task is critical for system reliability and user experience." }, { "taskId": 20, "taskTitle": "Create Token Usage Tracking and Cost Management", "complexityScore": 7, + "recommendedSubtasks": 4, + "expansionPrompt": "Divide the token tracking and cost management into subtasks covering usage tracking implementation, configurable limits, reporting features, cost estimation, caching for optimization, and usage alerts. Each subtask should specify the implementation approach, data storage, and user interface for monitoring and managing usage.", + "reasoning": "Implementing token usage tracking involves monitoring API calls, calculating costs, implementing limits, and optimizing usage through caching. The complexity is moderate to high, focusing on providing users with visibility into their API consumption and tools to manage costs." + }, + { + "taskId": 21, + "taskTitle": "Refactor dev.js into Modular Components", + "complexityScore": 8, "recommendedSubtasks": 5, - "expansionPrompt": "Divide the token usage tracking and cost management system into 5 subtasks, covering usage tracking implementation, limit configuration, reporting and cost estimation, caching and optimization, and alert system development. For each subtask, outline the main features to implement and any key considerations for effective integration with the existing system.", - "reasoning": "Moderate to high complexity due to the need for accurate tracking, optimization strategies, and integration with existing API systems. Involves both data processing and user-facing features." + "expansionPrompt": "Break down the refactoring of dev.js into subtasks covering module design (commands.js, ai-services.js, task-manager.js, ui.js, utils.js), entry point restructuring, dependency management, error handling standardization, and documentation. Each subtask should detail the specific code to extract, interfaces to define, and integration points between modules.", + "reasoning": "Refactoring a monolithic file into modular components represents high complexity due to the need to identify appropriate boundaries, manage dependencies between modules, and ensure all functionality is preserved. This requires deep understanding of the existing codebase and careful restructuring." + }, + { + "taskId": 22, + "taskTitle": "Create Comprehensive Test Suite for Task Master CLI", + "complexityScore": 9, + "recommendedSubtasks": 5, + "expansionPrompt": "Divide the test suite creation into subtasks covering unit test implementation, integration test development, end-to-end test creation, mocking setup, and CI integration. Each subtask should specify the testing approach, coverage goals, test data preparation, and specific functionality to test.", + "reasoning": "Developing a comprehensive test suite represents high complexity due to the need to cover unit, integration, and end-to-end tests across all functionality, implement appropriate mocking, and ensure good test coverage. This requires significant test engineering and understanding of the entire system." + }, + { + "taskId": 23, + "taskTitle": "Implement MCP (Model Context Protocol) Server Functionality for Task Master", + "complexityScore": 9, + "recommendedSubtasks": 5, + "expansionPrompt": "Break down the MCP server implementation into subtasks covering core server module creation, endpoint implementation (/context, /models, /execute), context management system, authentication mechanisms, and performance optimization. Each subtask should detail the API design, data structures, and integration with existing Task Master functionality.", + "reasoning": "Implementing an MCP server represents high complexity due to the need to create a RESTful API with multiple endpoints, manage context data efficiently, handle authentication, and ensure compatibility with the MCP specification. This requires significant API design and server-side development work." + }, + { + "taskId": 24, + "taskTitle": "Implement AI-Powered Test Generation Command", + "complexityScore": 7, + "recommendedSubtasks": 4, + "expansionPrompt": "Divide the test generation command implementation into subtasks covering command structure and parameter handling, task analysis logic, AI prompt construction, and test file generation. Each subtask should specify the implementation approach, AI interaction pattern, and output formatting requirements.", + "reasoning": "Creating an AI-powered test generation command involves analyzing tasks, constructing effective prompts, and generating well-formatted test files. The complexity is moderate to high, focusing on leveraging AI to produce useful tests based on task descriptions and subtasks." } ] } \ No newline at end of file diff --git a/tests/unit/task-manager.test.js b/tests/unit/task-manager.test.js index ccb3cdf8..72478f6f 100644 --- a/tests/unit/task-manager.test.js +++ b/tests/unit/task-manager.test.js @@ -150,4 +150,36 @@ describe('Task Manager Module', () => { expect(nextTask).toBeNull(); }); }); + + // Skipped tests for analyzeTaskComplexity + describe.skip('analyzeTaskComplexity function', () => { + // These tests are skipped because they require complex mocking + // but document what should be tested + + test('should handle valid JSON response from LLM', async () => { + // This test would verify that: + // 1. The function properly calls the AI model + // 2. It correctly parses a valid JSON response + // 3. It generates a properly formatted complexity report + // 4. The report includes all analyzed tasks with their complexity scores + expect(true).toBe(true); + }); + + test('should handle and fix malformed JSON with unterminated strings', async () => { + // This test would verify that: + // 1. The function can handle JSON with unterminated strings + // 2. It applies regex fixes to repair the malformed JSON + // 3. It still produces a valid report despite receiving bad JSON + expect(true).toBe(true); + }); + + test('should handle missing tasks in the response', async () => { + // This test would verify that: + // 1. When the AI response is missing some tasks + // 2. The function detects the missing tasks + // 3. It attempts to analyze just those missing tasks + // 4. The final report includes all tasks that could be analyzed + expect(true).toBe(true); + }); + }); }); \ No newline at end of file From 71d460ffc6e09a912497e6c213805a89f9bbd67d Mon Sep 17 00:00:00 2001 From: Eyal Toledano Date: Mon, 24 Mar 2025 16:50:16 -0400 Subject: [PATCH 3/3] fix: Ensures prompt is properly included in the expand command suggestion in the complexity-report. Makes the table fill the width of the terminal as well. --- scripts/modules/ui.js | 69 ++++++++++++++++++++++++------------------- 1 file changed, 39 insertions(+), 30 deletions(-) diff --git a/scripts/modules/ui.js b/scripts/modules/ui.js index a98651fe..de65da9b 100644 --- a/scripts/modules/ui.js +++ b/scripts/modules/ui.js @@ -950,37 +950,46 @@ async function displayComplexityReport(reportPath) { { padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 1 } } )); - // Create table for tasks that need expansion - if (tasksNeedingExpansion.length > 0) { - console.log(boxen( - chalk.yellow.bold(`Tasks Recommended for Expansion (${tasksNeedingExpansion.length})`), - { padding: { left: 2, right: 2, top: 0, bottom: 0 }, margin: { top: 1, bottom: 0 }, borderColor: 'yellow', borderStyle: 'round' } - )); + // Get terminal width + const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect + + // Calculate dynamic column widths + const idWidth = 5; + const titleWidth = Math.floor(terminalWidth * 0.25); // 25% of width + const scoreWidth = 8; + const subtasksWidth = 8; + // Command column gets the remaining space (minus some buffer for borders) + const commandWidth = terminalWidth - idWidth - titleWidth - scoreWidth - subtasksWidth - 10; + + // Create table with new column widths and word wrapping + const complexTable = new Table({ + head: [ + chalk.yellow.bold('ID'), + chalk.yellow.bold('Title'), + chalk.yellow.bold('Score'), + chalk.yellow.bold('Subtasks'), + chalk.yellow.bold('Expansion Command') + ], + colWidths: [idWidth, titleWidth, scoreWidth, subtasksWidth, commandWidth], + style: { head: [], border: [] }, + wordWrap: true, + wrapOnWordBoundary: true + }); + + // When adding rows, don't truncate the expansion command + tasksNeedingExpansion.forEach(task => { + const expansionCommand = `task-master expand --id=${task.taskId} --num=${task.recommendedSubtasks}${task.expansionPrompt ? ` --prompt="${task.expansionPrompt}"` : ''}`; - const complexTable = new Table({ - head: [ - chalk.yellow.bold('ID'), - chalk.yellow.bold('Title'), - chalk.yellow.bold('Score'), - chalk.yellow.bold('Subtasks'), - chalk.yellow.bold('Expansion Command') - ], - colWidths: [5, 40, 8, 10, 45], - style: { head: [], border: [] } - }); - - tasksNeedingExpansion.forEach(task => { - complexTable.push([ - task.taskId, - truncate(task.taskTitle, 37), - getComplexityWithColor(task.complexityScore), - task.recommendedSubtasks, - chalk.cyan(`task-master expand --id=${task.taskId} --num=${task.recommendedSubtasks}`) - ]); - }); - - console.log(complexTable.toString()); - } + complexTable.push([ + task.taskId, + truncate(task.taskTitle, titleWidth - 3), // Still truncate title for readability + getComplexityWithColor(task.complexityScore), + task.recommendedSubtasks, + chalk.cyan(expansionCommand) // Don't truncate - allow wrapping + ]); + }); + + console.log(complexTable.toString()); // Create table for simple tasks if (simpleTasks.length > 0) {