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
This commit is contained in:
@@ -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=<your-prd-file.txt>') + '\n' +
|
||||
chalk.white(' └─ ') + chalk.dim('You can also run ') + chalk.cyan('task-master parse-prd <your-prd-file.txt>') + '\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' +
|
||||
|
||||
@@ -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)
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
@@ -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([
|
||||
|
||||
Reference in New Issue
Block a user