mirror of
https://github.com/eyaltoledano/claude-task-master.git
synced 2025-06-27 00:29:58 +00:00

This commit introduces a comprehensive set of skipped tests to both and . These skipped tests serve as a blueprint for future test implementation, outlining the necessary test cases for currently untested functionalities. - Ensures sync with bin/ folder by adding -r/--research to the command - Fixes an issue that improperly parsed command line args - Ensures confirmation card on dependency add/remove - Properly formats some sub-task dependencies **Potentially addressed issues:** While primarily focused on adding test coverage, this commit also implicitly addresses potential issues by: - **Improving error handling coverage:** The addition of skipped tests for error scenarios in functions like , , , and highlights areas where error handling needs to be robustly tested and potentially improved in the codebase. - **Enhancing dependency validation:** Skipped tests for include validation of dependencies, prompting a review of the dependency validation logic and ensuring its correctness. - **Standardizing test coverage:** By creating a clear roadmap for testing all functions, this commit contributes to a more standardized and complete test suite, reducing the likelihood of undiscovered bugs in the future. **task-manager.test.js:** - Added skipped test blocks for the following functions: - : Includes tests for handling valid JSON responses, malformed JSON, missing tasks in responses, Perplexity AI research integration, Claude fallback, and parallel task processing. - : Covers tests for updating tasks based on context, handling Claude streaming, Perplexity AI integration, scenarios with no tasks to update, and error handling during updates. - : Includes tests for generating task files from , formatting dependencies with status indicators, handling tasks without subtasks, empty task arrays, and dependency validation before file generation. - : Covers tests for updating task status, subtask status using dot notation, updating multiple tasks, automatic subtask status updates, parent task update suggestions, and handling non-existent task IDs. - : Includes tests for updating regular and subtask statuses, handling parent tasks without subtasks, and non-existent subtask IDs. - : Covers tests for displaying all tasks, filtering by status, displaying subtasks, showing completion statistics, identifying the next task, and handling empty task arrays. - : Includes tests for generating subtasks, using complexity reports for subtask counts, Perplexity AI integration, appending subtasks, skipping completed tasks, and error handling during subtask generation. - : Covers tests for expanding all pending tasks, sorting by complexity, skipping tasks with existing subtasks (unless forced), using task-specific parameters from complexity reports, handling empty task arrays, and error handling for individual tasks. - : Includes tests for clearing subtasks from specific and multiple tasks, handling tasks without subtasks, non-existent task IDs, and regenerating task files after clearing subtasks. - : Covers tests for adding new tasks using AI, handling Claude streaming, validating dependencies, handling malformed AI responses, and using existing task context for generation. **utils.test.js:** - Added skipped test blocks for the following functions: - : Tests for logging messages according to log levels and filtering messages below configured levels. - : Tests for reading and parsing valid JSON files, handling file not found errors, and invalid JSON formats. - : Tests for writing JSON data to files and handling file write errors. - : Tests for escaping double quotes in prompts and handling prompts without special characters. - : Tests for reading and parsing complexity reports, handling missing report files, and custom report paths. - : Tests for finding tasks in reports by ID, handling non-existent task IDs, and invalid report structures. - : Tests for verifying existing task and subtask IDs, handling non-existent IDs, and invalid inputs. - : Tests for formatting numeric and string task IDs and preserving dot notation for subtasks. - : Tests for detecting simple and complex cycles in dependency graphs, handling acyclic graphs, and empty dependency maps. These skipped tests provide a clear roadmap for future test development, ensuring comprehensive coverage for core functionalities in both modules. They document the intended behavior of each function and outline various scenarios, including happy paths, edge cases, and error conditions, thereby improving the overall test strategy and maintainability of the Task Master CLI.
1038 lines
37 KiB
JavaScript
1038 lines
37 KiB
JavaScript
/**
|
|
* ui.js
|
|
* User interface functions for the Task Master CLI
|
|
*/
|
|
|
|
import chalk from 'chalk';
|
|
import figlet from 'figlet';
|
|
import boxen from 'boxen';
|
|
import ora from 'ora';
|
|
import Table from 'cli-table3';
|
|
import gradient from 'gradient-string';
|
|
import { CONFIG, log, findTaskById, readJSON, readComplexityReport, truncate } from './utils.js';
|
|
import path from 'path';
|
|
import fs from 'fs';
|
|
import { findNextTask, analyzeTaskComplexity } from './task-manager.js';
|
|
|
|
// Create a color gradient for the banner
|
|
const coolGradient = gradient(['#00b4d8', '#0077b6', '#03045e']);
|
|
const warmGradient = gradient(['#fb8b24', '#e36414', '#9a031e']);
|
|
|
|
/**
|
|
* Display a fancy banner for the CLI
|
|
*/
|
|
function displayBanner() {
|
|
console.clear();
|
|
const bannerText = figlet.textSync('Task Master', {
|
|
font: 'Standard',
|
|
horizontalLayout: 'default',
|
|
verticalLayout: 'default'
|
|
});
|
|
|
|
console.log(coolGradient(bannerText));
|
|
|
|
// Add creator credit line below the banner
|
|
console.log(chalk.dim('by ') + chalk.cyan.underline('https://x.com/eyaltoledano'));
|
|
|
|
// Read version directly from package.json
|
|
let version = CONFIG.projectVersion; // Default fallback
|
|
try {
|
|
const packageJsonPath = path.join(process.cwd(), 'package.json');
|
|
if (fs.existsSync(packageJsonPath)) {
|
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
version = packageJson.version;
|
|
}
|
|
} catch (error) {
|
|
// Silently fall back to default version
|
|
}
|
|
|
|
console.log(boxen(chalk.white(`${chalk.bold('Version:')} ${version} ${chalk.bold('Project:')} ${CONFIG.projectName}`), {
|
|
padding: 1,
|
|
margin: { top: 0, bottom: 1 },
|
|
borderStyle: 'round',
|
|
borderColor: 'cyan'
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Start a loading indicator with an animated spinner
|
|
* @param {string} message - Message to display next to the spinner
|
|
* @returns {Object} Spinner object
|
|
*/
|
|
function startLoadingIndicator(message) {
|
|
const spinner = ora({
|
|
text: message,
|
|
color: 'cyan'
|
|
}).start();
|
|
|
|
return spinner;
|
|
}
|
|
|
|
/**
|
|
* Stop a loading indicator
|
|
* @param {Object} spinner - Spinner object to stop
|
|
*/
|
|
function stopLoadingIndicator(spinner) {
|
|
if (spinner && spinner.stop) {
|
|
spinner.stop();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a progress bar using ASCII characters
|
|
* @param {number} percent - Progress percentage (0-100)
|
|
* @param {number} length - Length of the progress bar in characters
|
|
* @returns {string} Formatted progress bar
|
|
*/
|
|
function createProgressBar(percent, length = 30) {
|
|
const filled = Math.round(percent * length / 100);
|
|
const empty = length - filled;
|
|
|
|
const filledBar = '█'.repeat(filled);
|
|
const emptyBar = '░'.repeat(empty);
|
|
|
|
return `${filledBar}${emptyBar} ${percent.toFixed(0)}%`;
|
|
}
|
|
|
|
/**
|
|
* 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, forTable = false) {
|
|
if (!status) {
|
|
return chalk.gray('❓ unknown');
|
|
}
|
|
|
|
const statusConfig = {
|
|
'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: '❌', 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}`);
|
|
}
|
|
|
|
/**
|
|
* Format dependencies list with status indicators
|
|
* @param {Array} dependencies - Array of dependency IDs
|
|
* @param {Array} allTasks - Array of all tasks
|
|
* @param {boolean} forConsole - Whether the output is for console display
|
|
* @returns {string} Formatted dependencies string
|
|
*/
|
|
function formatDependenciesWithStatus(dependencies, allTasks, forConsole = false) {
|
|
if (!dependencies || !Array.isArray(dependencies) || dependencies.length === 0) {
|
|
return forConsole ? chalk.gray('None') : 'None';
|
|
}
|
|
|
|
const formattedDeps = dependencies.map(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);
|
|
}
|
|
}
|
|
|
|
// For plain text output (task files), return just the ID without any formatting or emoji
|
|
return depIdStr;
|
|
}
|
|
|
|
// 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(`${depIdStr} (Not found)`) :
|
|
`${depIdStr} (Not found)`;
|
|
}
|
|
|
|
// Format with status
|
|
const status = depTask.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.yellow.bold(depIdStr);
|
|
} else {
|
|
return chalk.red.bold(depIdStr);
|
|
}
|
|
}
|
|
|
|
// For plain text output (task files), return just the ID without any formatting or emoji
|
|
return depIdStr;
|
|
});
|
|
|
|
return formattedDeps.join(', ');
|
|
}
|
|
|
|
/**
|
|
* Display a comprehensive help guide
|
|
*/
|
|
function displayHelp() {
|
|
displayBanner();
|
|
|
|
console.log(boxen(
|
|
chalk.white.bold('Task Master CLI'),
|
|
{ padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } }
|
|
));
|
|
|
|
// Command categories
|
|
const commandCategories = [
|
|
{
|
|
title: 'Task Generation',
|
|
color: 'cyan',
|
|
commands: [
|
|
{ name: 'parse-prd', args: '--input=<file.txt> [--tasks=10]',
|
|
desc: 'Generate tasks from a PRD document' },
|
|
{ name: 'generate', args: '',
|
|
desc: 'Create individual task files from tasks.json' }
|
|
]
|
|
},
|
|
{
|
|
title: 'Task Management',
|
|
color: 'green',
|
|
commands: [
|
|
{ name: 'list', args: '[--status=<status>] [--with-subtasks]',
|
|
desc: 'List all tasks with their status' },
|
|
{ name: 'set-status', args: '--id=<id> --status=<status>',
|
|
desc: 'Update task status (done, pending, etc.)' },
|
|
{ name: 'update', args: '--from=<id> --prompt="<context>"',
|
|
desc: 'Update tasks based on new requirements' },
|
|
{ name: 'add-task', args: '--prompt="<text>" [--dependencies=<ids>] [--priority=<priority>]',
|
|
desc: 'Add a new task using AI' },
|
|
{ name: 'add-dependency', args: '--id=<id> --depends-on=<id>',
|
|
desc: 'Add a dependency to a task' },
|
|
{ name: 'remove-dependency', args: '--id=<id> --depends-on=<id>',
|
|
desc: 'Remove a dependency from a task' }
|
|
]
|
|
},
|
|
{
|
|
title: 'Task Analysis & Detail',
|
|
color: 'yellow',
|
|
commands: [
|
|
{ name: 'analyze-complexity', args: '[--research] [--threshold=5]',
|
|
desc: 'Analyze tasks and generate expansion recommendations' },
|
|
{ name: 'complexity-report', args: '[--file=<path>]',
|
|
desc: 'Display the complexity analysis report' },
|
|
{ name: 'expand', args: '--id=<id> [--num=5] [--research] [--prompt="<context>"]',
|
|
desc: 'Break down tasks into detailed subtasks' },
|
|
{ name: 'expand --all', args: '[--force] [--research]',
|
|
desc: 'Expand all pending tasks with subtasks' },
|
|
{ name: 'clear-subtasks', args: '--id=<id>',
|
|
desc: 'Remove subtasks from specified tasks' }
|
|
]
|
|
},
|
|
{
|
|
title: 'Task Navigation & Viewing',
|
|
color: 'magenta',
|
|
commands: [
|
|
{ name: 'next', args: '',
|
|
desc: 'Show the next task to work on based on dependencies' },
|
|
{ name: 'show', args: '<id>',
|
|
desc: 'Display detailed information about a specific task' }
|
|
]
|
|
},
|
|
{
|
|
title: 'Dependency Management',
|
|
color: 'blue',
|
|
commands: [
|
|
{ name: 'validate-dependencies', args: '',
|
|
desc: 'Identify invalid dependencies without fixing them' },
|
|
{ name: 'fix-dependencies', args: '',
|
|
desc: 'Fix invalid dependencies automatically' }
|
|
]
|
|
}
|
|
];
|
|
|
|
// Display each category
|
|
commandCategories.forEach(category => {
|
|
console.log(boxen(
|
|
chalk[category.color].bold(category.title),
|
|
{
|
|
padding: { left: 2, right: 2, top: 0, bottom: 0 },
|
|
margin: { top: 1, bottom: 0 },
|
|
borderColor: category.color,
|
|
borderStyle: 'round'
|
|
}
|
|
));
|
|
|
|
const commandTable = new Table({
|
|
colWidths: [25, 40, 45],
|
|
chars: {
|
|
'top': '', 'top-mid': '', 'top-left': '', 'top-right': '',
|
|
'bottom': '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '',
|
|
'left': '', 'left-mid': '', 'mid': '', 'mid-mid': '',
|
|
'right': '', 'right-mid': '', 'middle': ' '
|
|
},
|
|
style: { border: [], 'padding-left': 4 }
|
|
});
|
|
|
|
category.commands.forEach((cmd, index) => {
|
|
commandTable.push([
|
|
`${chalk.yellow.bold(cmd.name)}${chalk.reset('')}`,
|
|
`${chalk.white(cmd.args)}${chalk.reset('')}`,
|
|
`${chalk.dim(cmd.desc)}${chalk.reset('')}`
|
|
]);
|
|
});
|
|
|
|
console.log(commandTable.toString());
|
|
console.log('');
|
|
});
|
|
|
|
// Display environment variables section
|
|
console.log(boxen(
|
|
chalk.cyan.bold('Environment Variables'),
|
|
{
|
|
padding: { left: 2, right: 2, top: 0, bottom: 0 },
|
|
margin: { top: 1, bottom: 0 },
|
|
borderColor: 'cyan',
|
|
borderStyle: 'round'
|
|
}
|
|
));
|
|
|
|
const envTable = new Table({
|
|
colWidths: [30, 50, 30],
|
|
chars: {
|
|
'top': '', 'top-mid': '', 'top-left': '', 'top-right': '',
|
|
'bottom': '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '',
|
|
'left': '', 'left-mid': '', 'mid': '', 'mid-mid': '',
|
|
'right': '', 'right-mid': '', 'middle': ' '
|
|
},
|
|
style: { border: [], 'padding-left': 4 }
|
|
});
|
|
|
|
envTable.push(
|
|
[`${chalk.yellow('ANTHROPIC_API_KEY')}${chalk.reset('')}`,
|
|
`${chalk.white('Your Anthropic API key')}${chalk.reset('')}`,
|
|
`${chalk.dim('Required')}${chalk.reset('')}`],
|
|
[`${chalk.yellow('MODEL')}${chalk.reset('')}`,
|
|
`${chalk.white('Claude model to use')}${chalk.reset('')}`,
|
|
`${chalk.dim(`Default: ${CONFIG.model}`)}${chalk.reset('')}`],
|
|
[`${chalk.yellow('MAX_TOKENS')}${chalk.reset('')}`,
|
|
`${chalk.white('Maximum tokens for responses')}${chalk.reset('')}`,
|
|
`${chalk.dim(`Default: ${CONFIG.maxTokens}`)}${chalk.reset('')}`],
|
|
[`${chalk.yellow('TEMPERATURE')}${chalk.reset('')}`,
|
|
`${chalk.white('Temperature for model responses')}${chalk.reset('')}`,
|
|
`${chalk.dim(`Default: ${CONFIG.temperature}`)}${chalk.reset('')}`],
|
|
[`${chalk.yellow('PERPLEXITY_API_KEY')}${chalk.reset('')}`,
|
|
`${chalk.white('Perplexity API key for research')}${chalk.reset('')}`,
|
|
`${chalk.dim('Optional')}${chalk.reset('')}`],
|
|
[`${chalk.yellow('PERPLEXITY_MODEL')}${chalk.reset('')}`,
|
|
`${chalk.white('Perplexity model to use')}${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('')}`],
|
|
[`${chalk.yellow('LOG_LEVEL')}${chalk.reset('')}`,
|
|
`${chalk.white('Console output level (debug,info,warn,error)')}${chalk.reset('')}`,
|
|
`${chalk.dim(`Default: ${CONFIG.logLevel}`)}${chalk.reset('')}`],
|
|
[`${chalk.yellow('DEFAULT_SUBTASKS')}${chalk.reset('')}`,
|
|
`${chalk.white('Default number of subtasks to generate')}${chalk.reset('')}`,
|
|
`${chalk.dim(`Default: ${CONFIG.defaultSubtasks}`)}${chalk.reset('')}`],
|
|
[`${chalk.yellow('DEFAULT_PRIORITY')}${chalk.reset('')}`,
|
|
`${chalk.white('Default task priority')}${chalk.reset('')}`,
|
|
`${chalk.dim(`Default: ${CONFIG.defaultPriority}`)}${chalk.reset('')}`],
|
|
[`${chalk.yellow('PROJECT_NAME')}${chalk.reset('')}`,
|
|
`${chalk.white('Project name displayed in UI')}${chalk.reset('')}`,
|
|
`${chalk.dim(`Default: ${CONFIG.projectName}`)}${chalk.reset('')}`]
|
|
);
|
|
|
|
console.log(envTable.toString());
|
|
console.log('');
|
|
}
|
|
|
|
/**
|
|
* Get colored complexity score
|
|
* @param {number} score - Complexity score (1-10)
|
|
* @returns {string} Colored complexity score
|
|
*/
|
|
function getComplexityWithColor(score) {
|
|
if (score <= 3) return chalk.green(`🟢 ${score}`);
|
|
if (score <= 6) return chalk.yellow(`🟡 ${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
|
|
*/
|
|
async function displayNextTask(tasksPath) {
|
|
displayBanner();
|
|
|
|
// Read the tasks file
|
|
const data = readJSON(tasksPath);
|
|
if (!data || !data.tasks) {
|
|
log('error', "No valid tasks found.");
|
|
process.exit(1);
|
|
}
|
|
|
|
// Find the next task
|
|
const nextTask = findNextTask(data.tasks);
|
|
|
|
if (!nextTask) {
|
|
console.log(boxen(
|
|
chalk.yellow('No eligible tasks found!\n\n') +
|
|
'All pending tasks have unsatisfied dependencies, or all tasks are completed.',
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } }
|
|
));
|
|
return;
|
|
}
|
|
|
|
// Display the task in a nice format
|
|
console.log(boxen(
|
|
chalk.white.bold(`Next Task: #${nextTask.id} - ${nextTask.title}`),
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 0 } }
|
|
));
|
|
|
|
// Create a table with task details
|
|
const taskTable = new Table({
|
|
style: {
|
|
head: [],
|
|
border: [],
|
|
'padding-top': 0,
|
|
'padding-bottom': 0,
|
|
compact: true
|
|
},
|
|
chars: {
|
|
'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': ''
|
|
},
|
|
colWidths: [15, Math.min(75, (process.stdout.columns - 20) || 60)],
|
|
wordWrap: true
|
|
});
|
|
|
|
// Priority with color
|
|
const priorityColors = {
|
|
'high': chalk.red.bold,
|
|
'medium': chalk.yellow,
|
|
'low': chalk.gray
|
|
};
|
|
const priorityColor = priorityColors[nextTask.priority || 'medium'] || chalk.white;
|
|
|
|
// Add task details to table
|
|
taskTable.push(
|
|
[chalk.cyan.bold('ID:'), nextTask.id.toString()],
|
|
[chalk.cyan.bold('Title:'), nextTask.title],
|
|
[chalk.cyan.bold('Priority:'), priorityColor(nextTask.priority || 'medium')],
|
|
[chalk.cyan.bold('Dependencies:'), formatDependenciesWithStatus(nextTask.dependencies, data.tasks, true)],
|
|
[chalk.cyan.bold('Description:'), nextTask.description]
|
|
);
|
|
|
|
console.log(taskTable.toString());
|
|
|
|
// If task has details, show them in a separate box
|
|
if (nextTask.details && nextTask.details.trim().length > 0) {
|
|
console.log(boxen(
|
|
chalk.white.bold('Implementation Details:') + '\n\n' +
|
|
nextTask.details,
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 0 } }
|
|
));
|
|
}
|
|
|
|
// Show subtasks if they exist
|
|
if (nextTask.subtasks && nextTask.subtasks.length > 0) {
|
|
console.log(boxen(
|
|
chalk.white.bold('Subtasks'),
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, margin: { top: 1, bottom: 0 }, borderColor: 'magenta', borderStyle: 'round' }
|
|
));
|
|
|
|
// 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('Deps')
|
|
],
|
|
colWidths: [6, 12, Math.min(50, process.stdout.columns - 65 || 30), 30],
|
|
style: {
|
|
head: [],
|
|
border: [],
|
|
'padding-top': 0,
|
|
'padding-bottom': 0,
|
|
compact: true
|
|
},
|
|
chars: {
|
|
'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': ''
|
|
},
|
|
wordWrap: true
|
|
});
|
|
|
|
// Add subtasks to table
|
|
nextTask.subtasks.forEach(st => {
|
|
const statusColor = {
|
|
'done': chalk.green,
|
|
'completed': chalk.green,
|
|
'pending': chalk.yellow,
|
|
'in-progress': chalk.blue
|
|
}[st.status || 'pending'] || chalk.white;
|
|
|
|
// Format subtask dependencies
|
|
let subtaskDeps = 'None';
|
|
if (st.dependencies && st.dependencies.length > 0) {
|
|
// Format dependencies with correct notation
|
|
const formattedDeps = st.dependencies.map(depId => {
|
|
if (typeof depId === 'number' && depId < 100) {
|
|
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;
|
|
});
|
|
|
|
// Join the formatted dependencies directly instead of passing to formatDependenciesWithStatus again
|
|
subtaskDeps = formattedDeps.length === 1
|
|
? formattedDeps[0]
|
|
: formattedDeps.join(chalk.white(', '));
|
|
}
|
|
|
|
subtaskTable.push([
|
|
`${nextTask.id}.${st.id}`,
|
|
statusColor(st.status || 'pending'),
|
|
st.title,
|
|
subtaskDeps
|
|
]);
|
|
});
|
|
|
|
console.log(subtaskTable.toString());
|
|
} else {
|
|
// Suggest expanding if no subtasks
|
|
console.log(boxen(
|
|
chalk.yellow('No subtasks found. Consider breaking down this task:') + '\n' +
|
|
chalk.white(`Run: ${chalk.cyan(`task-master expand --id=${nextTask.id}`)}`),
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1, bottom: 0 } }
|
|
));
|
|
}
|
|
|
|
// Show action suggestions
|
|
console.log(boxen(
|
|
chalk.white.bold('Suggested Actions:') + '\n' +
|
|
`${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` +
|
|
`${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` +
|
|
(nextTask.subtasks && nextTask.subtasks.length > 0
|
|
? `${chalk.cyan('3.')} Update subtask status: ${chalk.yellow(`task-master set-status --id=${nextTask.id}.1 --status=done`)}`
|
|
: `${chalk.cyan('3.')} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${nextTask.id}`)}`),
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } }
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Display a specific task by ID
|
|
* @param {string} tasksPath - Path to the tasks.json file
|
|
* @param {string|number} taskId - The ID of the task to display
|
|
*/
|
|
async function displayTaskById(tasksPath, taskId) {
|
|
displayBanner();
|
|
|
|
// Read the tasks file
|
|
const data = readJSON(tasksPath);
|
|
if (!data || !data.tasks) {
|
|
log('error', "No valid tasks found.");
|
|
process.exit(1);
|
|
}
|
|
|
|
// Find the task by ID
|
|
const task = findTaskById(data.tasks, taskId);
|
|
|
|
if (!task) {
|
|
console.log(boxen(
|
|
chalk.yellow(`Task with ID ${taskId} not found!`),
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } }
|
|
));
|
|
return;
|
|
}
|
|
|
|
// Handle subtask display specially
|
|
if (task.isSubtask || task.parentTask) {
|
|
console.log(boxen(
|
|
chalk.white.bold(`Subtask: #${task.parentTask.id}.${task.id} - ${task.title}`),
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'magenta', borderStyle: 'round', margin: { top: 1, bottom: 0 } }
|
|
));
|
|
|
|
// Create a table with subtask details
|
|
const taskTable = new Table({
|
|
style: {
|
|
head: [],
|
|
border: [],
|
|
'padding-top': 0,
|
|
'padding-bottom': 0,
|
|
compact: true
|
|
},
|
|
chars: {
|
|
'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': ''
|
|
},
|
|
colWidths: [15, Math.min(75, (process.stdout.columns - 20) || 60)],
|
|
wordWrap: true
|
|
});
|
|
|
|
// Add subtask details to table
|
|
taskTable.push(
|
|
[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', true)],
|
|
[chalk.cyan.bold('Description:'), task.description || 'No description provided.']
|
|
);
|
|
|
|
console.log(taskTable.toString());
|
|
|
|
// Show action suggestions for subtask
|
|
console.log(boxen(
|
|
chalk.white.bold('Suggested Actions:') + '\n' +
|
|
`${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=in-progress`)}\n` +
|
|
`${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=done`)}\n` +
|
|
`${chalk.cyan('3.')} View parent task: ${chalk.yellow(`task-master show --id=${task.parentTask.id}`)}`,
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } }
|
|
));
|
|
|
|
return;
|
|
}
|
|
|
|
// Display a regular task
|
|
console.log(boxen(
|
|
chalk.white.bold(`Task: #${task.id} - ${task.title}`),
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 0 } }
|
|
));
|
|
|
|
// Create a table with task details with improved handling
|
|
const taskTable = new Table({
|
|
style: {
|
|
head: [],
|
|
border: [],
|
|
'padding-top': 0,
|
|
'padding-bottom': 0,
|
|
compact: true
|
|
},
|
|
chars: {
|
|
'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': ''
|
|
},
|
|
colWidths: [15, Math.min(75, (process.stdout.columns - 20) || 60)],
|
|
wordWrap: true
|
|
});
|
|
|
|
// Priority with color
|
|
const priorityColors = {
|
|
'high': chalk.red.bold,
|
|
'medium': chalk.yellow,
|
|
'low': chalk.gray
|
|
};
|
|
const priorityColor = priorityColors[task.priority || 'medium'] || chalk.white;
|
|
|
|
// Add task details to table
|
|
taskTable.push(
|
|
[chalk.cyan.bold('ID:'), task.id.toString()],
|
|
[chalk.cyan.bold('Title:'), task.title],
|
|
[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]
|
|
);
|
|
|
|
console.log(taskTable.toString());
|
|
|
|
// If task has details, show them in a separate box
|
|
if (task.details && task.details.trim().length > 0) {
|
|
console.log(boxen(
|
|
chalk.white.bold('Implementation Details:') + '\n\n' +
|
|
task.details,
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 0 } }
|
|
));
|
|
}
|
|
|
|
// Show test strategy if available
|
|
if (task.testStrategy && task.testStrategy.trim().length > 0) {
|
|
console.log(boxen(
|
|
chalk.white.bold('Test Strategy:') + '\n\n' +
|
|
task.testStrategy,
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 0 } }
|
|
));
|
|
}
|
|
|
|
// Show subtasks if they exist
|
|
if (task.subtasks && task.subtasks.length > 0) {
|
|
console.log(boxen(
|
|
chalk.white.bold('Subtasks'),
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, margin: { top: 1, bottom: 0 }, borderColor: 'magenta', borderStyle: 'round' }
|
|
));
|
|
|
|
// 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('Deps')
|
|
],
|
|
colWidths: [6, 12, Math.min(50, process.stdout.columns - 65 || 30), 30],
|
|
style: {
|
|
head: [],
|
|
border: [],
|
|
'padding-top': 0,
|
|
'padding-bottom': 0,
|
|
compact: true
|
|
},
|
|
chars: {
|
|
'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': ''
|
|
},
|
|
wordWrap: true
|
|
});
|
|
|
|
// Add subtasks to table
|
|
task.subtasks.forEach(st => {
|
|
const statusColor = {
|
|
'done': chalk.green,
|
|
'completed': chalk.green,
|
|
'pending': chalk.yellow,
|
|
'in-progress': chalk.blue
|
|
}[st.status || 'pending'] || chalk.white;
|
|
|
|
// Format subtask dependencies
|
|
let subtaskDeps = 'None';
|
|
if (st.dependencies && st.dependencies.length > 0) {
|
|
// Format dependencies with correct notation
|
|
const formattedDeps = st.dependencies.map(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}`);
|
|
}
|
|
}
|
|
return chalk.red(`${task.id}.${depId} (Not found)`);
|
|
}
|
|
return depId;
|
|
});
|
|
|
|
// Join the formatted dependencies directly instead of passing to formatDependenciesWithStatus again
|
|
subtaskDeps = formattedDeps.length === 1
|
|
? formattedDeps[0]
|
|
: formattedDeps.join(chalk.white(', '));
|
|
}
|
|
|
|
subtaskTable.push([
|
|
`${task.id}.${st.id}`,
|
|
statusColor(st.status || 'pending'),
|
|
st.title,
|
|
subtaskDeps
|
|
]);
|
|
});
|
|
|
|
console.log(subtaskTable.toString());
|
|
} else {
|
|
// Suggest expanding if no subtasks
|
|
console.log(boxen(
|
|
chalk.yellow('No subtasks found. Consider breaking down this task:') + '\n' +
|
|
chalk.white(`Run: ${chalk.cyan(`task-master expand --id=${task.id}`)}`),
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1, bottom: 0 } }
|
|
));
|
|
}
|
|
|
|
// Show action suggestions
|
|
console.log(boxen(
|
|
chalk.white.bold('Suggested Actions:') + '\n' +
|
|
`${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}\n` +
|
|
`${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.id} --status=done`)}\n` +
|
|
(task.subtasks && task.subtasks.length > 0
|
|
? `${chalk.cyan('3.')} Update subtask status: ${chalk.yellow(`task-master set-status --id=${task.id}.1 --status=done`)}`
|
|
: `${chalk.cyan('3.')} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${task.id}`)}`),
|
|
{ padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } }
|
|
));
|
|
}
|
|
|
|
/**
|
|
* Display the complexity analysis report in a nice format
|
|
* @param {string} reportPath - Path to the complexity report file
|
|
*/
|
|
async function displayComplexityReport(reportPath) {
|
|
displayBanner();
|
|
|
|
// Check if the report exists
|
|
if (!fs.existsSync(reportPath)) {
|
|
console.log(boxen(
|
|
chalk.yellow(`No complexity report found at ${reportPath}\n\n`) +
|
|
'Would you like to generate one now?',
|
|
{ padding: 1, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } }
|
|
));
|
|
|
|
const readline = require('readline').createInterface({
|
|
input: process.stdin,
|
|
output: process.stdout
|
|
});
|
|
|
|
const answer = await new Promise(resolve => {
|
|
readline.question(chalk.cyan('Generate complexity report? (y/n): '), resolve);
|
|
});
|
|
readline.close();
|
|
|
|
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
|
|
// Call the analyze-complexity command
|
|
console.log(chalk.blue('Generating complexity report...'));
|
|
await analyzeTaskComplexity({
|
|
output: reportPath,
|
|
research: false, // Default to no research for speed
|
|
file: 'tasks/tasks.json'
|
|
});
|
|
// Read the newly generated report
|
|
return displayComplexityReport(reportPath);
|
|
} else {
|
|
console.log(chalk.yellow('Report generation cancelled.'));
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Read the report
|
|
let report;
|
|
try {
|
|
report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
|
|
} catch (error) {
|
|
log('error', `Error reading complexity report: ${error.message}`);
|
|
return;
|
|
}
|
|
|
|
// Display report header
|
|
console.log(boxen(
|
|
chalk.white.bold('Task Complexity Analysis Report'),
|
|
{ padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } }
|
|
));
|
|
|
|
// Display metadata
|
|
const metaTable = new Table({
|
|
style: {
|
|
head: [],
|
|
border: [],
|
|
'padding-top': 0,
|
|
'padding-bottom': 0,
|
|
compact: true
|
|
},
|
|
chars: {
|
|
'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': ''
|
|
},
|
|
colWidths: [20, 50]
|
|
});
|
|
|
|
metaTable.push(
|
|
[chalk.cyan.bold('Generated:'), new Date(report.meta.generatedAt).toLocaleString()],
|
|
[chalk.cyan.bold('Tasks Analyzed:'), report.meta.tasksAnalyzed],
|
|
[chalk.cyan.bold('Threshold Score:'), report.meta.thresholdScore],
|
|
[chalk.cyan.bold('Project:'), report.meta.projectName],
|
|
[chalk.cyan.bold('Research-backed:'), report.meta.usedResearch ? 'Yes' : 'No']
|
|
);
|
|
|
|
console.log(metaTable.toString());
|
|
|
|
// Sort tasks by complexity score (highest first)
|
|
const sortedTasks = [...report.complexityAnalysis].sort((a, b) => b.complexityScore - a.complexityScore);
|
|
|
|
// Determine which tasks need expansion based on threshold
|
|
const tasksNeedingExpansion = sortedTasks.filter(task => task.complexityScore >= report.meta.thresholdScore);
|
|
const simpleTasks = sortedTasks.filter(task => task.complexityScore < report.meta.thresholdScore);
|
|
|
|
// Create progress bar to show complexity distribution
|
|
const complexityDistribution = [0, 0, 0]; // Low (0-4), Medium (5-7), High (8-10)
|
|
sortedTasks.forEach(task => {
|
|
if (task.complexityScore < 5) complexityDistribution[0]++;
|
|
else if (task.complexityScore < 8) complexityDistribution[1]++;
|
|
else complexityDistribution[2]++;
|
|
});
|
|
|
|
const percentLow = Math.round((complexityDistribution[0] / sortedTasks.length) * 100);
|
|
const percentMedium = Math.round((complexityDistribution[1] / sortedTasks.length) * 100);
|
|
const percentHigh = Math.round((complexityDistribution[2] / sortedTasks.length) * 100);
|
|
|
|
console.log(boxen(
|
|
chalk.white.bold('Complexity Distribution\n\n') +
|
|
`${chalk.green.bold('Low (1-4):')} ${complexityDistribution[0]} tasks (${percentLow}%)\n` +
|
|
`${chalk.yellow.bold('Medium (5-7):')} ${complexityDistribution[1]} tasks (${percentMedium}%)\n` +
|
|
`${chalk.red.bold('High (8-10):')} ${complexityDistribution[2]} tasks (${percentHigh}%)`,
|
|
{ padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 1 } }
|
|
));
|
|
|
|
// 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}"` : ''}`;
|
|
|
|
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) {
|
|
console.log(boxen(
|
|
chalk.green.bold(`Simple Tasks (${simpleTasks.length})`),
|
|
{ padding: { left: 2, right: 2, top: 0, bottom: 0 }, margin: { top: 1, bottom: 0 }, borderColor: 'green', borderStyle: 'round' }
|
|
));
|
|
|
|
const simpleTable = new Table({
|
|
head: [
|
|
chalk.green.bold('ID'),
|
|
chalk.green.bold('Title'),
|
|
chalk.green.bold('Score'),
|
|
chalk.green.bold('Reasoning')
|
|
],
|
|
colWidths: [5, 40, 8, 50],
|
|
style: { head: [], border: [] }
|
|
});
|
|
|
|
simpleTasks.forEach(task => {
|
|
simpleTable.push([
|
|
task.taskId,
|
|
truncate(task.taskTitle, 37),
|
|
getComplexityWithColor(task.complexityScore),
|
|
truncate(task.reasoning, 47)
|
|
]);
|
|
});
|
|
|
|
console.log(simpleTable.toString());
|
|
}
|
|
|
|
// Show action suggestions
|
|
console.log(boxen(
|
|
chalk.white.bold('Suggested Actions:') + '\n\n' +
|
|
`${chalk.cyan('1.')} Expand all complex tasks: ${chalk.yellow(`task-master expand --all`)}\n` +
|
|
`${chalk.cyan('2.')} Expand a specific task: ${chalk.yellow(`task-master expand --id=<id>`)}\n` +
|
|
`${chalk.cyan('3.')} Regenerate with research: ${chalk.yellow(`task-master analyze-complexity --research`)}`,
|
|
{ padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1 } }
|
|
));
|
|
}
|
|
|
|
// Export UI functions
|
|
export {
|
|
displayBanner,
|
|
startLoadingIndicator,
|
|
stopLoadingIndicator,
|
|
createProgressBar,
|
|
getStatusWithColor,
|
|
formatDependenciesWithStatus,
|
|
displayHelp,
|
|
getComplexityWithColor,
|
|
displayNextTask,
|
|
displayTaskById,
|
|
displayComplexityReport,
|
|
};
|