Merge branch 'next' of github.com:eyaltoledano/claude-task-master into v017-adds
This commit is contained in:
@@ -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'
|
||||
) +
|
||||
|
||||
@@ -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"]
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user