Merge branch 'next' of github.com:eyaltoledano/claude-task-master into v017-adds

This commit is contained in:
Eyal Toledano
2025-06-13 23:26:08 -04:00
18 changed files with 6599 additions and 35 deletions

View File

@@ -42,6 +42,8 @@ import {
taskExists,
moveTask,
migrateProject
moveTask,
migrateProject
} from './task-manager.js';
import {
@@ -111,6 +113,8 @@ import {
import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
import { syncTasksToReadme } from './sync-readme.js';
import { syncTasksToReadme } from './sync-readme.js';
/**
* Runs the interactive setup process for model configuration.
* @param {string|null} projectRoot - The resolved project root directory.
@@ -327,6 +331,7 @@ async function runInteractiveSetup(projectRoot) {
commonPrefix.push(customOllamaOption);
commonPrefix.push(customBedrockOption);
const prefixLength = commonPrefix.length; // Initial prefix length
const prefixLength = commonPrefix.length; // Initial prefix length
if (allowNone) {
@@ -521,6 +526,7 @@ async function runInteractiveSetup(projectRoot) {
console.error(
chalk.red(
'Error: AWS_ACCESS_KEY_ID and/or AWS_SECRET_ACCESS_KEY environment variables are missing. Please set them before using custom Bedrock models.'
'Error: AWS_ACCESS_KEY_ID and/or AWS_SECRET_ACCESS_KEY environment variables are missing. Please set them before using custom Bedrock models.'
)
);
setupSuccess = false;
@@ -679,6 +685,7 @@ function registerCommands(programInstance) {
'Path to the PRD file (alternative to positional argument)'
)
.option('-o, --output <file>', 'Output file path', TASKMASTER_TASKS_FILE)
.option('-o, --output <file>', 'Output file path', TASKMASTER_TASKS_FILE)
.option('-n, --num-tasks <number>', 'Number of tasks to generate', '10')
.option('-f, --force', 'Skip confirmation when overwriting existing tasks')
.option(
@@ -694,6 +701,7 @@ function registerCommands(programInstance) {
// Use input option if file argument not provided
const inputFile = file || options.input;
const defaultPrdPath = PRD_FILE;
const defaultPrdPath = PRD_FILE;
const numTasks = parseInt(options.numTasks, 10);
const outputPath = options.output;
const force = options.force || false;
@@ -778,10 +786,12 @@ function registerCommands(programInstance) {
console.log(
chalk.yellow(
`No PRD file specified and default PRD file not found at ${PRD_FILE}.`
`No PRD file specified and default PRD file not found at ${PRD_FILE}.`
)
);
console.log(
boxen(
`${chalk.white.bold('Parse PRD Help')}\n\n${chalk.cyan('Usage:')}\n task-master parse-prd <prd-file.txt> [options]\n\n${chalk.cyan('Options:')}\n -i, --input <file> Path to the PRD file (alternative to positional argument)\n -o, --output <file> Output file path (default: "${TASKMASTER_TASKS_FILE}")\n -n, --num-tasks <number> Number of tasks to generate (default: 10)\n -f, --force Skip confirmation when overwriting existing tasks\n --append Append new tasks to existing tasks.json instead of overwriting\n -r, --research Use Perplexity AI for research-backed task generation\n\n${chalk.cyan('Example:')}\n task-master parse-prd requirements.txt --num-tasks 15\n task-master parse-prd --input=requirements.txt\n task-master parse-prd --force\n task-master parse-prd requirements_v2.txt --append\n task-master parse-prd requirements.txt --research\n\n${chalk.yellow('Note: This command will:')}\n 1. Look for a PRD file at ${PRD_FILE} by default\n 2. Use the file specified by --input or positional argument if provided\n 3. Generate tasks from the PRD and either:\n - Overwrite any existing tasks.json file (default)\n - Append to existing tasks.json if --append is used`,
`${chalk.white.bold('Parse PRD Help')}\n\n${chalk.cyan('Usage:')}\n task-master parse-prd <prd-file.txt> [options]\n\n${chalk.cyan('Options:')}\n -i, --input <file> Path to the PRD file (alternative to positional argument)\n -o, --output <file> Output file path (default: "${TASKMASTER_TASKS_FILE}")\n -n, --num-tasks <number> Number of tasks to generate (default: 10)\n -f, --force Skip confirmation when overwriting existing tasks\n --append Append new tasks to existing tasks.json instead of overwriting\n -r, --research Use Perplexity AI for research-backed task generation\n\n${chalk.cyan('Example:')}\n task-master parse-prd requirements.txt --num-tasks 15\n task-master parse-prd --input=requirements.txt\n task-master parse-prd --force\n task-master parse-prd requirements_v2.txt --append\n task-master parse-prd requirements.txt --research\n\n${chalk.yellow('Note: This command will:')}\n 1. Look for a PRD file at ${PRD_FILE} by default\n 2. Use the file specified by --input or positional argument if provided\n 3. Generate tasks from the PRD and either:\n - Overwrite any existing tasks.json file (default)\n - Append to existing tasks.json if --append is used`,
{ padding: 1, borderColor: 'blue', borderStyle: 'round' }
)
@@ -841,6 +851,11 @@ function registerCommands(programInstance) {
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'--from <id>',
'Task ID to start updating from (tasks with ID >= this value will be updated)',
@@ -856,6 +871,7 @@ function registerCommands(programInstance) {
)
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const fromId = parseInt(options.from, 10); // Validation happens here
const prompt = options.prompt;
@@ -939,6 +955,11 @@ function registerCommands(programInstance) {
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option('-i, --id <id>', 'Task ID to update (required)')
.option(
'-p, --prompt <text>',
@@ -982,6 +1003,7 @@ function registerCommands(programInstance) {
// Parse the task ID and validate it's a number
const taskId = parseInt(options.id, 10);
if (Number.isNaN(taskId) || taskId <= 0) {
if (Number.isNaN(taskId) || taskId <= 0) {
console.error(
chalk.red(
@@ -1018,6 +1040,7 @@ function registerCommands(programInstance) {
console.error(
chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
);
if (tasksPath === TASKMASTER_TASKS_FILE) {
if (tasksPath === TASKMASTER_TASKS_FILE) {
console.log(
chalk.yellow(
@@ -1116,6 +1139,11 @@ function registerCommands(programInstance) {
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-i, --id <id>',
'Subtask ID to update in format "parentId.subtaskId" (required)'
@@ -1191,6 +1219,7 @@ function registerCommands(programInstance) {
console.error(
chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
);
if (tasksPath === TASKMASTER_TASKS_FILE) {
if (tasksPath === TASKMASTER_TASKS_FILE) {
console.log(
chalk.yellow(
@@ -1295,6 +1324,7 @@ function registerCommands(programInstance) {
)
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const outputDir = options.output;
const tag = options.tag;
@@ -1332,6 +1362,7 @@ function registerCommands(programInstance) {
)
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const taskId = options.id;
const status = options.status;
@@ -1379,10 +1410,16 @@ function registerCommands(programInstance) {
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-r, --report <report>',
'Path to the complexity report file',
COMPLEXITY_REPORT_FILE
COMPLEXITY_REPORT_FILE
)
.option('-s, --status <status>', 'Filter by status')
.option('--with-subtasks', 'Show subtasks for each task')
@@ -1446,6 +1483,7 @@ function registerCommands(programInstance) {
'--file <file>',
'Path to the tasks file (relative to project root)',
TASKMASTER_TASKS_FILE // Allow file override
TASKMASTER_TASKS_FILE // Allow file override
) // Allow file override
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
@@ -1526,6 +1564,7 @@ function registerCommands(programInstance) {
'-o, --output <file>',
'Output file path for the report',
COMPLEXITY_REPORT_FILE
COMPLEXITY_REPORT_FILE
)
.option(
'-m, --model <model>',
@@ -1541,6 +1580,11 @@ function registerCommands(programInstance) {
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-r, --research',
'Use Perplexity AI for research-backed complexity analysis'
@@ -1969,6 +2013,11 @@ ${result.result}
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-i, --id <ids>',
'Task IDs (comma-separated) to clear subtasks from'
@@ -1976,6 +2025,7 @@ ${result.result}
.option('--all', 'Clear subtasks from all tasks')
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const taskIds = options.id;
const all = options.all;
@@ -2022,6 +2072,11 @@ ${result.result}
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-p, --prompt <prompt>',
'Description of the task to add (required if not using manual fields)'
@@ -2064,6 +2119,14 @@ ${result.result}
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
if (!fs.existsSync(tasksPath)) {
console.error(
`❌ No tasks.json file found. Please run "task-master init" or create a tasks.json file at ${TASKMASTER_TASKS_FILE}`
);
process.exit(1);
}
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
if (!fs.existsSync(tasksPath)) {
console.error(
`❌ No tasks.json file found. Please run "task-master init" or create a tasks.json file at ${TASKMASTER_TASKS_FILE}`
@@ -2156,13 +2219,20 @@ ${result.result}
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-r, --report <report>',
'Path to the complexity report file',
COMPLEXITY_REPORT_FILE
COMPLEXITY_REPORT_FILE
)
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const reportPath = options.report;
const tag = options.tag;
@@ -2200,6 +2270,7 @@ ${result.result}
'-r, --report <report>',
'Path to the complexity report file',
COMPLEXITY_REPORT_FILE
COMPLEXITY_REPORT_FILE
)
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (taskId, options) => {
@@ -2221,6 +2292,7 @@ ${result.result}
process.exit(1);
}
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const reportPath = options.report;
@@ -2265,6 +2337,7 @@ ${result.result}
)
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const taskId = options.id;
const dependencyId = options.dependsOn;
@@ -2316,6 +2389,7 @@ ${result.result}
)
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const taskId = options.id;
const dependencyId = options.dependsOn;
@@ -2425,6 +2499,7 @@ ${result.result}
'-f, --file <file>',
'Path to the report file',
COMPLEXITY_REPORT_FILE
COMPLEXITY_REPORT_FILE
)
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
@@ -2458,6 +2533,11 @@ ${result.result}
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option('-p, --parent <id>', 'Parent task ID (required)')
.option('-i, --task-id <id>', 'Existing task ID to convert to subtask')
.option(
@@ -2633,6 +2713,7 @@ ${result.result}
function showAddSubtaskHelp() {
console.log(
boxen(
`${chalk.white.bold('Add Subtask Command Help')}\n\n${chalk.cyan('Usage:')}\n task-master add-subtask --parent=<id> [options]\n\n${chalk.cyan('Options:')}\n -p, --parent <id> Parent task ID (required)\n -i, --task-id <id> Existing task ID to convert to subtask\n -t, --title <title> Title for the new subtask\n -d, --description <text> Description for the new subtask\n --details <text> Implementation details for the new subtask\n --dependencies <ids> Comma-separated list of dependency IDs\n -s, --status <status> Status for the new subtask (default: "pending")\n -f, --file <file> Path to the tasks file (default: "${TASKMASTER_TASKS_FILE}")\n --skip-generate Skip regenerating task files\n\n${chalk.cyan('Examples:')}\n task-master add-subtask --parent=5 --task-id=8\n task-master add-subtask -p 5 -t "Implement login UI" -d "Create the login form"`,
`${chalk.white.bold('Add Subtask Command Help')}\n\n${chalk.cyan('Usage:')}\n task-master add-subtask --parent=<id> [options]\n\n${chalk.cyan('Options:')}\n -p, --parent <id> Parent task ID (required)\n -i, --task-id <id> Existing task ID to convert to subtask\n -t, --title <title> Title for the new subtask\n -d, --description <text> Description for the new subtask\n --details <text> Implementation details for the new subtask\n --dependencies <ids> Comma-separated list of dependency IDs\n -s, --status <status> Status for the new subtask (default: "pending")\n -f, --file <file> Path to the tasks file (default: "${TASKMASTER_TASKS_FILE}")\n --skip-generate Skip regenerating task files\n\n${chalk.cyan('Examples:')}\n task-master add-subtask --parent=5 --task-id=8\n task-master add-subtask -p 5 -t "Implement login UI" -d "Create the login form"`,
{ padding: 1, borderColor: 'blue', borderStyle: 'round' }
)
@@ -2648,6 +2729,11 @@ ${result.result}
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-i, --id <id>',
'Subtask ID(s) to remove in format "parentId.subtaskId" (can be comma-separated for multiple subtasks)'
@@ -2659,6 +2745,7 @@ ${result.result}
.option('--skip-generate', 'Skip regenerating task files')
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const subtaskIds = options.id;
const convertToTask = options.convert || false;
@@ -2790,6 +2877,9 @@ ${result.result}
' -f, --file <file> Path to the tasks file (default: "' +
TASKMASTER_TASKS_FILE +
'")\n' +
' -f, --file <file> Path to the tasks file (default: "' +
TASKMASTER_TASKS_FILE +
'")\n' +
' --skip-generate Skip regenerating task files\n\n' +
chalk.cyan('Examples:') +
'\n' +
@@ -2959,9 +3049,15 @@ ${result.result}
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option('-y, --yes', 'Skip confirmation prompt', false)
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const taskIdsString = options.id;
@@ -3479,6 +3575,11 @@ Examples:
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'--from <id>',
'ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated to move multiple tasks (e.g., "5,6,7")'
@@ -3489,6 +3590,7 @@ Examples:
)
.option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => {
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const sourceId = options.from;
const destinationId = options.to;
@@ -4289,6 +4391,7 @@ async function runCLI(argv = process.argv) {
// Setup and parse
// NOTE: getConfig() might be called during setupCLI->registerCommands if commands need config
// This means the ConfigurationError might be thrown here if configuration file is missing.
// This means the ConfigurationError might be thrown here if configuration file is missing.
const programInstance = setupCLI();
await programInstance.parseAsync(argv);
@@ -4348,7 +4451,10 @@ async function runCLI(argv = process.argv) {
'\n\n' +
chalk.white('Taskmaster now uses a ') +
chalk.yellow.bold('configuration file') +
chalk.white('Taskmaster now uses a ') +
chalk.yellow.bold('configuration file') +
chalk.white(
' in your project for AI model choices and settings.\n\n' +
' in your project for AI model choices and settings.\n\n' +
'This file appears to be '
) +
@@ -4362,6 +4468,7 @@ async function runCLI(argv = process.argv) {
'\n' +
chalk.white('* ') +
chalk.yellow.bold('Configuration file') +
chalk.yellow.bold('Configuration file') +
chalk.white(
': Stores your AI model settings (do not manually edit)\n'
) +

View File

@@ -46,7 +46,7 @@
{
"id": "o3",
"swe_score": 0.5,
"cost_per_1m_tokens": { "input": 10.0, "output": 40.0 },
"cost_per_1m_tokens": { "input": 2.0, "output": 8.0 },
"allowed_roles": ["main", "fallback"]
},
{

View File

@@ -213,6 +213,78 @@ function listTasks(
// Find next task to work on, passing the complexity report
const nextItem = findNextTask(data.tasks, complexityReport);
// Calculate dependency statistics (moved up to be available for all output formats)
const completedTaskIds = new Set(
data.tasks
.filter((t) => t.status === 'done' || t.status === 'completed')
.map((t) => t.id)
);
const tasksWithNoDeps = data.tasks.filter(
(t) =>
t.status !== 'done' &&
t.status !== 'completed' &&
(!t.dependencies || t.dependencies.length === 0)
).length;
const tasksWithAllDepsSatisfied = data.tasks.filter(
(t) =>
t.status !== 'done' &&
t.status !== 'completed' &&
t.dependencies &&
t.dependencies.length > 0 &&
t.dependencies.every((depId) => completedTaskIds.has(depId))
).length;
const tasksWithUnsatisfiedDeps = data.tasks.filter(
(t) =>
t.status !== 'done' &&
t.status !== 'completed' &&
t.dependencies &&
t.dependencies.length > 0 &&
!t.dependencies.every((depId) => completedTaskIds.has(depId))
).length;
// Calculate total tasks ready to work on (no deps + satisfied deps)
const tasksReadyToWork = tasksWithNoDeps + tasksWithAllDepsSatisfied;
// Calculate most depended-on tasks
const dependencyCount = {};
data.tasks.forEach((task) => {
if (task.dependencies && task.dependencies.length > 0) {
task.dependencies.forEach((depId) => {
dependencyCount[depId] = (dependencyCount[depId] || 0) + 1;
});
}
});
// Find the most depended-on task
let mostDependedOnTaskId = null;
let maxDependents = 0;
for (const [taskId, count] of Object.entries(dependencyCount)) {
if (count > maxDependents) {
maxDependents = count;
mostDependedOnTaskId = parseInt(taskId);
}
}
// Get the most depended-on task
const mostDependedOnTask =
mostDependedOnTaskId !== null
? data.tasks.find((t) => t.id === mostDependedOnTaskId)
: null;
// Calculate average dependencies per task
const totalDependencies = data.tasks.reduce(
(sum, task) => sum + (task.dependencies ? task.dependencies.length : 0),
0
);
const avgDependenciesPerTask = totalDependencies / data.tasks.length;
// Find next task to work on, passing the complexity report
const nextItem = findNextTask(data.tasks, complexityReport);
// For JSON output, return structured data
if (outputFormat === 'json') {
// *** Modification: Remove 'details' field for JSON output ***
@@ -1045,4 +1117,232 @@ function generateMarkdownOutput(data, filteredTasks, stats) {
return markdown;
}
/**
* Generate markdown-formatted output for README files
* @param {Object} data - Full tasks data
* @param {Array} filteredTasks - Filtered tasks array
* @param {Object} stats - Statistics object
* @returns {string} - Formatted markdown string
*/
function generateMarkdownOutput(data, filteredTasks, stats) {
const {
totalTasks,
completedTasks,
completionPercentage,
doneCount,
inProgressCount,
pendingCount,
blockedCount,
deferredCount,
cancelledCount,
totalSubtasks,
completedSubtasks,
subtaskCompletionPercentage,
inProgressSubtasks,
pendingSubtasks,
blockedSubtasks,
deferredSubtasks,
cancelledSubtasks,
tasksWithNoDeps,
tasksReadyToWork,
tasksWithUnsatisfiedDeps,
mostDependedOnTask,
mostDependedOnTaskId,
maxDependents,
avgDependenciesPerTask,
complexityReport,
withSubtasks,
nextItem
} = stats;
let markdown = '';
// Create progress bars for markdown (using Unicode block characters)
const createMarkdownProgressBar = (percentage, width = 20) => {
const filled = Math.round((percentage / 100) * width);
const empty = width - filled;
return '█'.repeat(filled) + '░'.repeat(empty);
};
// Dashboard section
markdown += '```\n';
markdown +=
'╭─────────────────────────────────────────────────────────╮╭─────────────────────────────────────────────────────────╮\n';
markdown +=
'│ ││ │\n';
markdown +=
'│ Project Dashboard ││ Dependency Status & Next Task │\n';
markdown += `│ Tasks Progress: ${createMarkdownProgressBar(completionPercentage, 20)} ${Math.round(completionPercentage)}% ││ Dependency Metrics: │\n`;
markdown += `${Math.round(completionPercentage)}% ││ • Tasks with no dependencies: ${tasksWithNoDeps}\n`;
markdown += `│ Done: ${doneCount} In Progress: ${inProgressCount} Pending: ${pendingCount} Blocked: ${blockedCount} ││ • Tasks ready to work on: ${tasksReadyToWork}\n`;
markdown += `│ Deferred: ${deferredCount} Cancelled: ${cancelledCount} ││ • Tasks blocked by dependencies: ${tasksWithUnsatisfiedDeps}\n`;
markdown += `│ ││ • Most depended-on task: #${mostDependedOnTaskId} (${maxDependents} dependents) │\n`;
markdown += `│ Subtasks Progress: ${createMarkdownProgressBar(subtaskCompletionPercentage, 20)} ││ • Avg dependencies per task: ${avgDependenciesPerTask.toFixed(1)}\n`;
markdown += `${Math.round(subtaskCompletionPercentage)}% ${Math.round(subtaskCompletionPercentage)}% ││ │\n`;
markdown += `│ Completed: ${completedSubtasks}/${totalSubtasks} In Progress: ${inProgressSubtasks} Pending: ${pendingSubtasks} ││ Next Task to Work On: │\n`;
const nextTaskTitle = nextItem
? nextItem.title.length > 40
? nextItem.title.substring(0, 37) + '...'
: nextItem.title
: 'No task available';
markdown += `│ Blocked: ${blockedSubtasks} Deferred: ${deferredSubtasks} Cancelled: ${cancelledSubtasks} ││ ID: ${nextItem ? nextItem.id : 'N/A'} - ${nextTaskTitle}\n`;
markdown += `│ ││ Priority: ${nextItem ? nextItem.priority || 'medium' : ''} Dependencies: ${nextItem && nextItem.dependencies && nextItem.dependencies.length > 0 ? 'Some' : 'None'}\n`;
markdown += `│ Priority Breakdown: ││ Complexity: ${nextItem && nextItem.complexityScore ? '● ' + nextItem.complexityScore : 'N/A'}\n`;
markdown += `│ • High priority: ${data.tasks.filter((t) => t.priority === 'high').length} │╰─────────────────────────────────────────────────────────╯\n`;
markdown += `│ • Medium priority: ${data.tasks.filter((t) => t.priority === 'medium').length}\n`;
markdown += `│ • Low priority: ${data.tasks.filter((t) => t.priority === 'low').length}\n`;
markdown += '│ │\n';
markdown += '╰─────────────────────────────────────────────────────────╯\n';
// Tasks table
markdown +=
'┌───────────┬──────────────────────────────────────┬─────────────────┬──────────────┬───────────────────────┬───────────┐\n';
markdown +=
'│ ID │ Title │ Status │ Priority │ Dependencies │ Complexi… │\n';
markdown +=
'├───────────┼──────────────────────────────────────┼─────────────────┼──────────────┼───────────────────────┼───────────┤\n';
// Helper function to format status with symbols
const getStatusSymbol = (status) => {
switch (status) {
case 'done':
case 'completed':
return '✓ done';
case 'in-progress':
return '► in-progress';
case 'pending':
return '○ pending';
case 'blocked':
return '⭕ blocked';
case 'deferred':
return 'x deferred';
case 'cancelled':
return 'x cancelled';
case 'review':
return '? review';
default:
return status || 'pending';
}
};
// Helper function to format dependencies without color codes
const formatDependenciesForMarkdown = (deps, allTasks) => {
if (!deps || deps.length === 0) return 'None';
return deps
.map((depId) => {
const depTask = allTasks.find((t) => t.id === depId);
return depTask ? depId.toString() : depId.toString();
})
.join(', ');
};
// Process all tasks
filteredTasks.forEach((task) => {
const taskTitle = task.title; // No truncation for README
const statusSymbol = getStatusSymbol(task.status);
const priority = task.priority || 'medium';
const deps = formatDependenciesForMarkdown(task.dependencies, data.tasks);
const complexity = task.complexityScore
? `${task.complexityScore}`
: 'N/A';
markdown += `${task.id.toString().padEnd(9)}${taskTitle.substring(0, 36).padEnd(36)}${statusSymbol.padEnd(15)}${priority.padEnd(12)}${deps.substring(0, 21).padEnd(21)}${complexity.padEnd(9)}\n`;
// Add subtasks if requested
if (withSubtasks && task.subtasks && task.subtasks.length > 0) {
task.subtasks.forEach((subtask) => {
const subtaskTitle = `└─ ${subtask.title}`; // No truncation
const subtaskStatus = getStatusSymbol(subtask.status);
const subtaskDeps = formatDependenciesForMarkdown(
subtask.dependencies,
data.tasks
);
const subtaskComplexity = subtask.complexityScore
? subtask.complexityScore.toString()
: 'N/A';
markdown +=
'├───────────┼──────────────────────────────────────┼─────────────────┼──────────────┼───────────────────────┼───────────┤\n';
markdown += `${task.id}.${subtask.id}${' '.padEnd(6)}${subtaskTitle.substring(0, 36).padEnd(36)}${subtaskStatus.padEnd(15)} │ - │ ${subtaskDeps.substring(0, 21).padEnd(21)}${subtaskComplexity.padEnd(9)}\n`;
});
}
markdown +=
'├───────────┼──────────────────────────────────────┼─────────────────┼──────────────┼───────────────────────┼───────────┤\n';
});
// Close the table
markdown = markdown.slice(
0,
-1 *
'├───────────┼──────────────────────────────────────┼─────────────────┼──────────────┼───────────────────────┼───────────┤\n'
.length
);
markdown +=
'└───────────┴──────────────────────────────────────┴─────────────────┴──────────────┴───────────────────────┴───────────┘\n';
markdown += '```\n\n';
// Next task recommendation
if (nextItem) {
markdown +=
'╭────────────────────────────────────────────── ⚡ RECOMMENDED NEXT TASK ⚡ ──────────────────────────────────────────────╮\n';
markdown +=
'│ │\n';
markdown += `│ 🔥 Next Task to Work On: #${nextItem.id} - ${nextItem.title}\n`;
markdown +=
'│ │\n';
markdown += `│ Priority: ${nextItem.priority || 'medium'} Status: ${getStatusSymbol(nextItem.status)}\n`;
markdown += `│ Dependencies: ${nextItem.dependencies && nextItem.dependencies.length > 0 ? formatDependenciesForMarkdown(nextItem.dependencies, data.tasks) : 'None'}\n`;
markdown +=
'│ │\n';
markdown += `│ Description: ${getWorkItemDescription(nextItem, data.tasks)}\n`;
markdown +=
'│ │\n';
// Add subtasks if they exist
const parentTask = data.tasks.find((t) => t.id === nextItem.id);
if (parentTask && parentTask.subtasks && parentTask.subtasks.length > 0) {
markdown +=
'│ Subtasks: │\n';
parentTask.subtasks.forEach((subtask) => {
markdown += `${nextItem.id}.${subtask.id} [${subtask.status || 'pending'}] ${subtask.title}\n`;
});
markdown +=
'│ │\n';
}
markdown += `│ Start working: task-master set-status --id=${nextItem.id} --status=in-progress │\n`;
markdown += `│ View details: task-master show ${nextItem.id}\n`;
markdown +=
'│ │\n';
markdown +=
'╰─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯\n\n';
}
// Suggested next steps
markdown += '\n';
markdown +=
'╭──────────────────────────────────────────────────────────────────────────────────────╮\n';
markdown +=
'│ │\n';
markdown +=
'│ Suggested Next Steps: │\n';
markdown +=
'│ │\n';
markdown +=
'│ 1. Run task-master next to see what to work on next │\n';
markdown +=
'│ 2. Run task-master expand --id=<id> to break down a task into subtasks │\n';
markdown +=
'│ 3. Run task-master set-status --id=<id> --status=done to mark a task as complete │\n';
markdown +=
'│ │\n';
markdown +=
'╰──────────────────────────────────────────────────────────────────────────────────────╯\n';
return markdown;
}
export default listTasks;

View File

@@ -28,6 +28,10 @@ import {
TASKMASTER_CONFIG_FILE,
TASKMASTER_TASKS_FILE
} from '../../src/constants/paths.js';
import {
TASKMASTER_CONFIG_FILE,
TASKMASTER_TASKS_FILE
} from '../../src/constants/paths.js';
import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
// Create a color gradient for the banner