feat: Add .taskmaster directory (#619)

This commit is contained in:
Ralph Khreish
2025-05-31 16:21:03 +02:00
committed by GitHub
parent 669b744ced
commit 3f64202c9f
151 changed files with 3432 additions and 7584 deletions

View File

@@ -32,7 +32,8 @@ import {
removeTask,
findTaskById,
taskExists,
moveTask
moveTask,
migrateProject
} from './task-manager.js';
import {
@@ -53,6 +54,12 @@ import {
getBaseUrlForRole
} from './config-manager.js';
import {
COMPLEXITY_REPORT_FILE,
PRD_FILE,
TASKMASTER_TASKS_FILE
} from '../../src/constants/paths.js';
import {
displayBanner,
displayHelp,
@@ -65,8 +72,7 @@ import {
stopLoadingIndicator,
displayModelConfiguration,
displayAvailableModels,
displayApiKeyStatus,
displayAiUsageSummary
displayApiKeyStatus
} from './ui.js';
import { initializeProject } from '../init.js';
@@ -81,6 +87,7 @@ import {
TASK_STATUS_OPTIONS
} from '../../src/constants/task-status.js';
import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
/**
* Runs the interactive setup process for model configuration.
* @param {string|null} projectRoot - The resolved project root directory.
@@ -297,7 +304,7 @@ async function runInteractiveSetup(projectRoot) {
commonPrefix.push(customOllamaOption);
commonPrefix.push(customBedrockOption);
let prefixLength = commonPrefix.length; // Initial prefix length
const prefixLength = commonPrefix.length; // Initial prefix length
if (allowNone) {
choices = [
@@ -490,7 +497,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;
@@ -648,7 +655,7 @@ function registerCommands(programInstance) {
'-i, --input <file>',
'Path to the PRD file (alternative to positional argument)'
)
.option('-o, --output <file>', 'Output file path', 'tasks/tasks.json')
.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(
@@ -662,14 +669,14 @@ function registerCommands(programInstance) {
.action(async (file, options) => {
// Use input option if file argument not provided
const inputFile = file || options.input;
const defaultPrdPath = 'scripts/prd.txt';
const defaultPrdPath = PRD_FILE;
const numTasks = parseInt(options.numTasks, 10);
const outputPath = options.output;
const force = options.force || false;
const append = options.append || false;
const research = options.research || false;
let useForce = force;
let useAppend = append;
const useAppend = append;
// Helper function to check if tasks.json exists and confirm overwrite
async function confirmOverwriteIfNeeded() {
@@ -709,38 +716,12 @@ function registerCommands(programInstance) {
console.log(
chalk.yellow(
'No PRD file specified and default PRD file not found at scripts/prd.txt.'
`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: "tasks/tasks.json")\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 scripts/prd.txt 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' }
)
);
@@ -792,7 +773,11 @@ function registerCommands(programInstance) {
.description(
'Update multiple tasks with ID >= "from" based on new information or implementation changes'
)
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.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)',
@@ -807,7 +792,7 @@ function registerCommands(programInstance) {
'Use Perplexity AI for research-backed task updates'
)
.action(async (options) => {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const fromId = parseInt(options.from, 10); // Validation happens here
const prompt = options.prompt;
const useResearch = options.research || false;
@@ -873,7 +858,11 @@ function registerCommands(programInstance) {
.description(
'Update a single specific task by ID with new information (use --id parameter)'
)
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.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>',
@@ -885,7 +874,7 @@ function registerCommands(programInstance) {
)
.action(async (options) => {
try {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
// Validate required parameters
if (!options.id) {
@@ -900,7 +889,7 @@ function registerCommands(programInstance) {
// Parse the task ID and validate it's a number
const taskId = parseInt(options.id, 10);
if (isNaN(taskId) || taskId <= 0) {
if (Number.isNaN(taskId) || taskId <= 0) {
console.error(
chalk.red(
`Error: Invalid task ID: ${options.id}. Task ID must be a positive integer.`
@@ -936,7 +925,7 @@ function registerCommands(programInstance) {
console.error(
chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
);
if (tasksPath === 'tasks/tasks.json') {
if (tasksPath === TASKMASTER_TASKS_FILE) {
console.log(
chalk.yellow(
'Hint: Run task-master init or task-master parse-prd to create tasks.json first'
@@ -1026,7 +1015,11 @@ function registerCommands(programInstance) {
.description(
'Update a subtask by appending additional timestamped information'
)
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.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)'
@@ -1038,7 +1031,7 @@ function registerCommands(programInstance) {
.option('-r, --research', 'Use Perplexity AI for research-backed updates')
.action(async (options) => {
try {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
// Validate required parameters
if (!options.id) {
@@ -1089,7 +1082,7 @@ function registerCommands(programInstance) {
console.error(
chalk.red(`Error: Tasks file not found at path: ${tasksPath}`)
);
if (tasksPath === 'tasks/tasks.json') {
if (tasksPath === TASKMASTER_TASKS_FILE) {
console.log(
chalk.yellow(
'Hint: Run task-master init or task-master parse-prd to create tasks.json first'
@@ -1180,10 +1173,14 @@ function registerCommands(programInstance) {
programInstance
.command('generate')
.description('Generate task files from tasks.json')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option('-o, --output <dir>', 'Output directory', 'tasks')
.action(async (options) => {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const outputDir = options.output;
console.log(chalk.blue(`Generating task files from: ${tasksPath}`));
@@ -1206,9 +1203,13 @@ function registerCommands(programInstance) {
'-s, --status <status>',
`New status (one of: ${TASK_STATUS_OPTIONS.join(', ')})`
)
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.action(async (options) => {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const taskId = options.id;
const status = options.status;
@@ -1238,16 +1239,20 @@ function registerCommands(programInstance) {
programInstance
.command('list')
.description('List all tasks')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-r, --report <report>',
'Path to the complexity report file',
'scripts/task-complexity-report.json'
COMPLEXITY_REPORT_FILE
)
.option('-s, --status <status>', 'Filter by status')
.option('--with-subtasks', 'Show subtasks for each task')
.action(async (options) => {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const reportPath = options.report;
const statusFilter = options.status;
const withSubtasks = options.withSubtasks || false;
@@ -1286,7 +1291,7 @@ function registerCommands(programInstance) {
.option(
'--file <file>',
'Path to the tasks file (relative to project root)',
'tasks/tasks.json'
TASKMASTER_TASKS_FILE // Allow file override
) // Allow file override
.action(async (options) => {
const projectRoot = findProjectRoot();
@@ -1361,7 +1366,7 @@ function registerCommands(programInstance) {
.option(
'-o, --output <file>',
'Output file path for the report',
'scripts/task-complexity-report.json'
COMPLEXITY_REPORT_FILE
)
.option(
'-m, --model <model>',
@@ -1372,7 +1377,11 @@ function registerCommands(programInstance) {
'Minimum complexity score to recommend expansion (1-10)',
'5'
)
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-r, --research',
'Use Perplexity AI for research-backed complexity analysis'
@@ -1384,7 +1393,7 @@ function registerCommands(programInstance) {
.option('--from <id>', 'Starting task ID in a range to analyze')
.option('--to <id>', 'Ending task ID in a range to analyze')
.action(async (options) => {
const tasksPath = options.file || 'tasks/tasks.json';
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const outputPath = options.output;
const modelOverride = options.model;
const thresholdScore = parseFloat(options.threshold);
@@ -1418,14 +1427,18 @@ function registerCommands(programInstance) {
programInstance
.command('clear-subtasks')
.description('Clear subtasks from specified tasks')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-i, --id <ids>',
'Task IDs (comma-separated) to clear subtasks from'
)
.option('--all', 'Clear subtasks from all tasks')
.action(async (options) => {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const taskIds = options.id;
const all = options.all;
@@ -1456,7 +1469,11 @@ function registerCommands(programInstance) {
programInstance
.command('add-task')
.description('Add a new task using AI, optionally providing manual details')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.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)'
@@ -1496,10 +1513,14 @@ function registerCommands(programInstance) {
process.exit(1);
}
const tasksPath =
options.file ||
path.join(findProjectRoot() || '.', 'tasks', 'tasks.json') || // Ensure tasksPath is also relative to a found root or current dir
'tasks/tasks.json';
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);
}
// Correctly determine projectRoot
const projectRoot = findProjectRoot();
@@ -1571,15 +1592,20 @@ function registerCommands(programInstance) {
.description(
`Show the next task to work on based on dependencies and status${chalk.reset('')}`
)
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-r, --report <report>',
'Path to the complexity report file',
'scripts/task-complexity-report.json'
COMPLEXITY_REPORT_FILE
)
.action(async (options) => {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const reportPath = options.report;
await displayNextTask(tasksPath, reportPath);
});
@@ -1592,11 +1618,15 @@ function registerCommands(programInstance) {
.argument('[id]', 'Task ID to show')
.option('-i, --id <id>', 'Task ID to show')
.option('-s, --status <status>', 'Filter subtasks by status') // ADDED status option
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option(
'-r, --report <report>',
'Path to the complexity report file',
'scripts/task-complexity-report.json'
COMPLEXITY_REPORT_FILE
)
.action(async (taskId, options) => {
const idArg = taskId || options.id;
@@ -1607,7 +1637,7 @@ function registerCommands(programInstance) {
process.exit(1);
}
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const reportPath = options.report;
// PASS statusFilter to the display function
await displayTaskById(tasksPath, idArg, reportPath, statusFilter);
@@ -1619,9 +1649,13 @@ function registerCommands(programInstance) {
.description('Add a dependency to a task')
.option('-i, --id <id>', 'Task ID to add dependency to')
.option('-d, --depends-on <id>', 'Task ID that will become a dependency')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.action(async (options) => {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const taskId = options.id;
const dependencyId = options.dependsOn;
@@ -1650,9 +1684,13 @@ function registerCommands(programInstance) {
.description('Remove a dependency from a task')
.option('-i, --id <id>', 'Task ID to remove dependency from')
.option('-d, --depends-on <id>', 'Task ID to remove as a dependency')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.action(async (options) => {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const taskId = options.id;
const dependencyId = options.dependsOn;
@@ -1681,18 +1719,26 @@ function registerCommands(programInstance) {
.description(
`Identify invalid dependencies without fixing them${chalk.reset('')}`
)
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.action(async (options) => {
await validateDependenciesCommand(options.file);
await validateDependenciesCommand(options.file || TASKMASTER_TASKS_FILE);
});
// fix-dependencies command
programInstance
.command('fix-dependencies')
.description(`Fix invalid dependencies automatically${chalk.reset('')}`)
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.action(async (options) => {
await fixDependenciesCommand(options.file);
await fixDependenciesCommand(options.file || TASKMASTER_TASKS_FILE);
});
// complexity-report command
@@ -1702,17 +1748,21 @@ function registerCommands(programInstance) {
.option(
'-f, --file <file>',
'Path to the report file',
'scripts/task-complexity-report.json'
COMPLEXITY_REPORT_FILE
)
.action(async (options) => {
await displayComplexityReport(options.file);
await displayComplexityReport(options.file || COMPLEXITY_REPORT_FILE);
});
// add-subtask command
programInstance
.command('add-subtask')
.description('Add a subtask to an existing task')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.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(
@@ -1728,7 +1778,7 @@ function registerCommands(programInstance) {
.option('-s, --status <status>', 'Status for the new subtask', 'pending')
.option('--skip-generate', 'Skip regenerating task files')
.action(async (options) => {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const parentId = options.parent;
const existingTaskId = options.taskId;
const generateFiles = !options.skipGenerate;
@@ -1873,26 +1923,7 @@ function registerCommands(programInstance) {
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: "tasks/tasks.json")\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' }
)
);
@@ -1902,7 +1933,11 @@ function registerCommands(programInstance) {
programInstance
.command('remove-subtask')
.description('Remove a subtask from its parent task')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.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)'
@@ -1913,7 +1948,7 @@ function registerCommands(programInstance) {
)
.option('--skip-generate', 'Skip regenerating task files')
.action(async (options) => {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const subtaskIds = options.id;
const convertToTask = options.convert || false;
const generateFiles = !options.skipGenerate;
@@ -2033,7 +2068,9 @@ function registerCommands(programInstance) {
'\n' +
' -i, --id <id> Subtask ID(s) to remove in format "parentId.subtaskId" (can be comma-separated, required)\n' +
' -c, --convert Convert the subtask to a standalone task instead of deleting it\n' +
' -f, --file <file> Path to the tasks file (default: "tasks/tasks.json")\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' +
@@ -2054,10 +2091,14 @@ function registerCommands(programInstance) {
'-i, --id <ids>',
'ID(s) of the task(s) or subtask(s) to remove (e.g., "5", "5.2", or "5,6.1,7")'
)
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option(
'-f, --file <file>',
'Path to the tasks file',
TASKMASTER_TASKS_FILE
)
.option('-y, --yes', 'Skip confirmation prompt', false)
.action(async (options) => {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const taskIdsString = options.id;
if (!taskIdsString) {
@@ -2554,7 +2595,11 @@ Examples:
programInstance
.command('move')
.description('Move a task or subtask to a new position')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.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")'
@@ -2564,7 +2609,7 @@ Examples:
'ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated'
)
.action(async (options) => {
const tasksPath = options.file;
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
const sourceId = options.from;
const destinationId = options.to;
@@ -2679,6 +2724,39 @@ Examples:
}
});
programInstance
.command('migrate')
.description(
'Migrate existing project to use the new .taskmaster directory structure'
)
.option(
'-f, --force',
'Force migration even if .taskmaster directory already exists'
)
.option(
'--backup',
'Create backup of old files before migration (default: false)',
false
)
.option(
'--cleanup',
'Remove old files after successful migration (default: true)',
true
)
.option('-y, --yes', 'Skip confirmation prompts')
.option(
'--dry-run',
'Show what would be migrated without actually moving files'
)
.action(async (options) => {
try {
await migrateProject(options);
} catch (error) {
console.error(chalk.red('Error during migration:'), error.message);
process.exit(1);
}
});
return programInstance;
}
@@ -2863,7 +2941,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 .taskmasterconfig is missing.
// This means the ConfigurationError might be thrown here if configuration file is missing.
const programInstance = setupCLI();
await programInstance.parseAsync(argv);
@@ -2882,10 +2960,10 @@ async function runCLI(argv = process.argv) {
boxen(
chalk.red.bold('Configuration Update Required!') +
'\n\n' +
chalk.white('Taskmaster now uses the ') +
chalk.yellow.bold('.taskmasterconfig') +
chalk.white('Taskmaster now uses a ') +
chalk.yellow.bold('configuration file') +
chalk.white(
' file in your project root for AI model choices and settings.\n\n' +
' in your project for AI model choices and settings.\n\n' +
'This file appears to be '
) +
chalk.red.bold('missing') +
@@ -2897,7 +2975,7 @@ async function runCLI(argv = process.argv) {
chalk.white.bold('Key Points:') +
'\n' +
chalk.white('* ') +
chalk.yellow.bold('.taskmasterconfig') +
chalk.yellow.bold('Configuration file') +
chalk.white(
': Stores your AI model settings (do not manually edit)\n'
) +

View File

@@ -3,6 +3,8 @@ import path from 'path';
import chalk from 'chalk';
import { fileURLToPath } from 'url';
import { log, findProjectRoot, resolveEnvVariable } from './utils.js';
import { LEGACY_CONFIG_FILE } from '../../src/constants/paths.js';
import { findConfigPath } from '../../src/utils/path-utils.js';
// Calculate __dirname in ESM
const __filename = fileURLToPath(import.meta.url);
@@ -27,12 +29,10 @@ try {
process.exit(1); // Exit if models can't be loaded
}
const CONFIG_FILE_NAME = '.taskmasterconfig';
// Define valid providers dynamically from the loaded MODEL_MAP
const VALID_PROVIDERS = Object.keys(MODEL_MAP || {});
// Default configuration values (used if .taskmasterconfig is missing or incomplete)
// Default configuration values (used if config file is missing or incomplete)
const DEFAULTS = {
models: {
main: {
@@ -96,13 +96,15 @@ function _loadAndValidateConfig(explicitRoot = null) {
}
// ---> End find project root logic <---
// --- Proceed with loading from the determined rootToUse ---
const configPath = path.join(rootToUse, CONFIG_FILE_NAME);
// --- Find configuration file using centralized path utility ---
const configPath = findConfigPath(null, { projectRoot: rootToUse });
let config = { ...defaults }; // Start with a deep copy of defaults
let configExists = false;
if (fs.existsSync(configPath)) {
if (configPath) {
configExists = true;
const isLegacy = configPath.endsWith(LEGACY_CONFIG_FILE);
try {
const rawData = fs.readFileSync(configPath, 'utf-8');
const parsedConfig = JSON.parse(rawData);
@@ -125,6 +127,15 @@ function _loadAndValidateConfig(explicitRoot = null) {
};
configSource = `file (${configPath})`; // Update source info
// Issue deprecation warning if using legacy config file
if (isLegacy) {
console.warn(
chalk.yellow(
`⚠️ DEPRECATION WARNING: Found configuration in legacy location '${configPath}'. Please migrate to .taskmaster/config.json. Run 'task-master migrate' to automatically migrate your project.`
)
);
}
// --- Validation (Warn if file content is invalid) ---
// Use log.warn for consistency
if (!validateProvider(config.models.main.provider)) {
@@ -171,19 +182,19 @@ function _loadAndValidateConfig(explicitRoot = null) {
// Only warn if an explicit root was *expected*.
console.warn(
chalk.yellow(
`Warning: ${CONFIG_FILE_NAME} not found at provided project root (${explicitRoot}). Using default configuration. Run 'task-master models --setup' to configure.`
`Warning: Configuration file not found at provided project root (${explicitRoot}). Using default configuration. Run 'task-master models --setup' to configure.`
)
);
} else {
console.warn(
chalk.yellow(
`Warning: ${CONFIG_FILE_NAME} not found at derived root (${rootToUse}). Using defaults.`
`Warning: Configuration file not found at derived root (${rootToUse}). Using defaults.`
)
);
}
// Keep config as defaults
config = { ...defaults };
configSource = `defaults (file not found at ${configPath})`;
configSource = `defaults (no config file found at ${rootToUse})`;
}
return config;
@@ -342,13 +353,13 @@ function getDefaultSubtasks(explicitRoot = null) {
// Directly return value from config, ensure integer
const val = getGlobalConfig(explicitRoot).defaultSubtasks;
const parsedVal = parseInt(val, 10);
return isNaN(parsedVal) ? DEFAULTS.global.defaultSubtasks : parsedVal;
return Number.isNaN(parsedVal) ? DEFAULTS.global.defaultSubtasks : parsedVal;
}
function getDefaultNumTasks(explicitRoot = null) {
const val = getGlobalConfig(explicitRoot).defaultNumTasks;
const parsedVal = parseInt(val, 10);
return isNaN(parsedVal) ? DEFAULTS.global.defaultNumTasks : parsedVal;
return Number.isNaN(parsedVal) ? DEFAULTS.global.defaultNumTasks : parsedVal;
}
function getDefaultPriority(explicitRoot = null) {
@@ -658,12 +669,16 @@ function writeConfig(config, explicitRoot = null) {
}
// ---> End determine root path logic <---
const configPath =
path.basename(rootPath) === CONFIG_FILE_NAME
? rootPath
: path.join(rootPath, CONFIG_FILE_NAME);
// Use new config location: .taskmaster/config.json
const taskmasterDir = path.join(rootPath, '.taskmaster');
const configPath = path.join(taskmasterDir, 'config.json');
try {
// Ensure .taskmaster directory exists
if (!fs.existsSync(taskmasterDir)) {
fs.mkdirSync(taskmasterDir, { recursive: true });
}
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
loadedConfig = config; // Update the cache after successful write
return true;
@@ -678,25 +693,12 @@ function writeConfig(config, explicitRoot = null) {
}
/**
* Checks if the .taskmasterconfig file exists at the project root
* Checks if a configuration file exists at the project root (new or legacy location)
* @param {string|null} explicitRoot - Optional explicit path to the project root
* @returns {boolean} True if the file exists, false otherwise
*/
function isConfigFilePresent(explicitRoot = null) {
// ---> Determine root path reliably <---
let rootPath = explicitRoot;
if (explicitRoot === null || explicitRoot === undefined) {
// Logic matching _loadAndValidateConfig
const foundRoot = findProjectRoot(); // *** Explicitly call findProjectRoot ***
if (!foundRoot) {
return false; // Cannot check if root doesn't exist
}
rootPath = foundRoot;
}
// ---> End determine root path logic <---
const configPath = path.join(rootPath, CONFIG_FILE_NAME);
return fs.existsSync(configPath);
return findConfigPath(null, { projectRoot: explicitRoot }) !== null;
}
/**

View File

@@ -24,6 +24,7 @@ import removeTask from './task-manager/remove-task.js';
import taskExists from './task-manager/task-exists.js';
import isTaskDependentOn from './task-manager/is-task-dependent.js';
import moveTask from './task-manager/move-task.js';
import { migrateProject } from './task-manager/migrate.js';
import { readComplexityReport } from './utils.js';
// Export task manager functions
export {
@@ -48,5 +49,6 @@ export {
taskExists,
isTaskDependentOn,
moveTask,
readComplexityReport
readComplexityReport,
migrateProject
};

View File

@@ -14,6 +14,10 @@ import {
import { generateTextService } from '../ai-services-unified.js';
import { getDebugFlag, getProjectName } from '../config-manager.js';
import {
COMPLEXITY_REPORT_FILE,
LEGACY_TASKS_FILE
} from '../../../src/constants/paths.js';
/**
* Generates the prompt for complexity analysis.
@@ -64,8 +68,8 @@ Do not include any explanatory text, markdown formatting, or code block markers
*/
async function analyzeTaskComplexity(options, context = {}) {
const { session, mcpLog } = context;
const tasksPath = options.file || 'tasks/tasks.json';
const outputPath = options.output || 'scripts/task-complexity-report.json';
const tasksPath = options.file || LEGACY_TASKS_FILE;
const outputPath = options.output || COMPLEXITY_REPORT_FILE;
const thresholdScore = parseFloat(options.threshold || '5');
const useResearch = options.research || false;
const projectRoot = options.projectRoot;
@@ -74,7 +78,7 @@ async function analyzeTaskComplexity(options, context = {}) {
? options.id
.split(',')
.map((id) => parseInt(id.trim(), 10))
.filter((id) => !isNaN(id))
.filter((id) => !Number.isNaN(id))
: null;
const fromId = options.from !== undefined ? parseInt(options.from, 10) : null;
const toId = options.to !== undefined ? parseInt(options.to, 10) : null;
@@ -92,7 +96,7 @@ async function analyzeTaskComplexity(options, context = {}) {
if (outputFormat === 'text') {
console.log(
chalk.blue(
`Analyzing task complexity and generating expansion recommendations...`
'Analyzing task complexity and generating expansion recommendations...'
)
);
}

View File

@@ -14,6 +14,7 @@ import { generateTextService } from '../ai-services-unified.js';
import { getDefaultSubtasks, getDebugFlag } from '../config-manager.js';
import generateTaskFiles from './generate-task-files.js';
import { COMPLEXITY_REPORT_FILE } from '../../../src/constants/paths.js';
// --- Zod Schemas (Keep from previous step) ---
const subtaskSchema = z
@@ -463,10 +464,7 @@ async function expandTask(
let complexityReasoningContext = '';
let systemPrompt; // Declare systemPrompt here
const complexityReportPath = path.join(
projectRoot,
'scripts/task-complexity-report.json'
);
const complexityReportPath = path.join(projectRoot, COMPLEXITY_REPORT_FILE);
let taskAnalysis = null;
try {

View File

@@ -0,0 +1,283 @@
import fs from 'fs';
import path from 'path';
import chalk from 'chalk';
import { fileURLToPath } from 'url';
import { createLogWrapper } from '../../../mcp-server/src/tools/utils.js';
import { findProjectRoot } from '../utils.js';
import {
LEGACY_CONFIG_FILE,
TASKMASTER_CONFIG_FILE
} from '../../../src/constants/paths.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Create a simple log wrapper for CLI use
const log = createLogWrapper({
info: (msg) => console.log(chalk.blue(''), msg),
warn: (msg) => console.log(chalk.yellow('⚠'), msg),
error: (msg) => console.error(chalk.red('✗'), msg),
success: (msg) => console.log(chalk.green('✓'), msg)
});
/**
* Main migration function
* @param {Object} options - Migration options
*/
export async function migrateProject(options = {}) {
const projectRoot = findProjectRoot() || process.cwd();
log.info(`Starting migration in: ${projectRoot}`);
// Check if .taskmaster directory already exists
const taskmasterDir = path.join(projectRoot, '.taskmaster');
if (fs.existsSync(taskmasterDir) && !options.force) {
log.warn(
'.taskmaster directory already exists. Use --force to overwrite or skip migration.'
);
return;
}
// Analyze what needs to be migrated
const migrationPlan = analyzeMigrationNeeds(projectRoot);
if (migrationPlan.length === 0) {
log.info(
'No files to migrate. Project may already be using the new structure.'
);
return;
}
// Show migration plan
log.info('Migration plan:');
for (const item of migrationPlan) {
const action = options.dryRun ? 'Would move' : 'Will move';
log.info(` ${action}: ${item.from}${item.to}`);
}
if (options.dryRun) {
log.info(
'Dry run complete. Use --dry-run=false to perform actual migration.'
);
return;
}
// Confirm migration
if (!options.yes) {
const readline = await import('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
const answer = await new Promise((resolve) => {
rl.question('Proceed with migration? (y/N): ', resolve);
});
rl.close();
if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
log.info('Migration cancelled.');
return;
}
}
// Perform migration
try {
await performMigration(projectRoot, migrationPlan, options);
log.success('Migration completed successfully!');
log.info('You can now use the new .taskmaster directory structure.');
if (!options.cleanup) {
log.info(
'Old files were preserved. Use --cleanup to remove them after verification.'
);
}
} catch (error) {
log.error(`Migration failed: ${error.message}`);
throw error;
}
}
/**
* Analyze what files need to be migrated
* @param {string} projectRoot - Project root directory
* @returns {Array} Migration plan items
*/
function analyzeMigrationNeeds(projectRoot) {
const migrationPlan = [];
// Check for tasks directory
const tasksDir = path.join(projectRoot, 'tasks');
if (fs.existsSync(tasksDir)) {
const tasksFiles = fs.readdirSync(tasksDir);
for (const file of tasksFiles) {
migrationPlan.push({
from: path.join('tasks', file),
to: path.join('.taskmaster', 'tasks', file),
type: 'task'
});
}
}
// Check for scripts directory files
const scriptsDir = path.join(projectRoot, 'scripts');
if (fs.existsSync(scriptsDir)) {
const scriptsFiles = fs.readdirSync(scriptsDir);
for (const file of scriptsFiles) {
const filePath = path.join(scriptsDir, file);
if (fs.statSync(filePath).isFile()) {
// Categorize files more intelligently
let destination;
const lowerFile = file.toLowerCase();
if (
lowerFile.includes('example') ||
lowerFile.includes('template') ||
lowerFile.includes('boilerplate') ||
lowerFile.includes('sample')
) {
// Template/example files go to templates (including example_prd.txt)
destination = path.join('.taskmaster', 'templates', file);
} else if (
lowerFile.includes('complexity') &&
lowerFile.includes('report') &&
lowerFile.endsWith('.json')
) {
// Only actual complexity reports go to reports
destination = path.join('.taskmaster', 'reports', file);
} else if (
lowerFile.includes('prd') ||
lowerFile.endsWith('.md') ||
lowerFile.endsWith('.txt')
) {
// Documentation files go to docs (but not examples or reports)
destination = path.join('.taskmaster', 'docs', file);
} else {
// Other files stay in scripts or get skipped - don't force everything into templates
log.warn(
`Skipping migration of '${file}' - uncertain categorization. You may need to move this manually.`
);
continue;
}
migrationPlan.push({
from: path.join('scripts', file),
to: destination,
type: 'script'
});
}
}
}
// Check for .taskmasterconfig
const oldConfig = path.join(projectRoot, LEGACY_CONFIG_FILE);
if (fs.existsSync(oldConfig)) {
migrationPlan.push({
from: LEGACY_CONFIG_FILE,
to: TASKMASTER_CONFIG_FILE,
type: 'config'
});
}
return migrationPlan;
}
/**
* Perform the actual migration
* @param {string} projectRoot - Project root directory
* @param {Array} migrationPlan - List of files to migrate
* @param {Object} options - Migration options
*/
async function performMigration(projectRoot, migrationPlan, options) {
// Create .taskmaster directory
const taskmasterDir = path.join(projectRoot, '.taskmaster');
if (!fs.existsSync(taskmasterDir)) {
fs.mkdirSync(taskmasterDir, { recursive: true });
}
// Group migration items by destination directory to create only needed subdirs
const neededDirs = new Set();
for (const item of migrationPlan) {
const destDir = path.dirname(item.to);
neededDirs.add(destDir);
}
// Create only the directories we actually need
for (const dir of neededDirs) {
const fullDirPath = path.join(projectRoot, dir);
if (!fs.existsSync(fullDirPath)) {
fs.mkdirSync(fullDirPath, { recursive: true });
log.info(`Created directory: ${dir}`);
}
}
// Create backup if requested
if (options.backup) {
const backupDir = path.join(projectRoot, '.taskmaster-migration-backup');
log.info(`Creating backup in: ${backupDir}`);
if (fs.existsSync(backupDir)) {
fs.rmSync(backupDir, { recursive: true, force: true });
}
fs.mkdirSync(backupDir, { recursive: true });
}
// Migrate files
for (const item of migrationPlan) {
const fromPath = path.join(projectRoot, item.from);
const toPath = path.join(projectRoot, item.to);
if (!fs.existsSync(fromPath)) {
log.warn(`Source file not found: ${item.from}`);
continue;
}
// Create backup if requested
if (options.backup) {
const backupPath = path.join(
projectRoot,
'.taskmaster-migration-backup',
item.from
);
const backupDir = path.dirname(backupPath);
if (!fs.existsSync(backupDir)) {
fs.mkdirSync(backupDir, { recursive: true });
}
fs.copyFileSync(fromPath, backupPath);
}
// Ensure destination directory exists
const toDir = path.dirname(toPath);
if (!fs.existsSync(toDir)) {
fs.mkdirSync(toDir, { recursive: true });
}
// Copy file
fs.copyFileSync(fromPath, toPath);
log.info(`Migrated: ${item.from}${item.to}`);
// Remove original if cleanup is requested
if (options.cleanup) {
fs.unlinkSync(fromPath);
}
}
// Clean up empty directories if cleanup is requested
if (options.cleanup) {
const dirsToCheck = ['tasks', 'scripts'];
for (const dir of dirsToCheck) {
const dirPath = path.join(projectRoot, dir);
if (fs.existsSync(dirPath)) {
try {
const files = fs.readdirSync(dirPath);
if (files.length === 0) {
fs.rmdirSync(dirPath);
log.info(`Removed empty directory: ${dir}`);
}
} catch (error) {
// Directory not empty or other error, skip
}
}
}
}
}
export default { migrateProject };

View File

@@ -3,8 +3,6 @@
* Core functionality for managing AI model configurations
*/
import path from 'path';
import fs from 'fs';
import https from 'https';
import http from 'http';
import {
@@ -23,6 +21,8 @@ import {
getAllProviders,
getBaseUrlForRole
} from '../config-manager.js';
import { findConfigPath } from '../../../src/utils/path-utils.js';
import { log } from '../utils.js';
/**
* Fetches the list of models from OpenRouter API.
@@ -149,34 +149,27 @@ async function getModelConfiguration(options = {}) {
}
};
// Check if configuration file exists using provided project root
let configPath;
let configExists = false;
if (projectRoot) {
configPath = path.join(projectRoot, '.taskmasterconfig');
configExists = fs.existsSync(configPath);
report(
'info',
`Checking for .taskmasterconfig at: ${configPath}, exists: ${configExists}`
);
} else {
configExists = isConfigFilePresent();
report(
'info',
`Checking for .taskmasterconfig using isConfigFilePresent(), exists: ${configExists}`
);
if (!projectRoot) {
throw new Error('Project root is required but not found.');
}
// Use centralized config path finding instead of hardcoded path
const configPath = findConfigPath(null, { projectRoot });
const configExists = isConfigFilePresent(projectRoot);
log(
'debug',
`Checking for config file using findConfigPath, found: ${configPath}`
);
log(
'debug',
`Checking config file using isConfigFilePresent(), exists: ${configExists}`
);
if (!configExists) {
return {
success: false,
error: {
code: 'CONFIG_MISSING',
message:
'The .taskmasterconfig file is missing. Run "task-master models --setup" to create it.'
}
};
throw new Error(
'The configuration file is missing. Run "task-master models --setup" to create it.'
);
}
try {
@@ -286,34 +279,27 @@ async function getAvailableModelsList(options = {}) {
}
};
// Check if configuration file exists using provided project root
let configPath;
let configExists = false;
if (projectRoot) {
configPath = path.join(projectRoot, '.taskmasterconfig');
configExists = fs.existsSync(configPath);
report(
'info',
`Checking for .taskmasterconfig at: ${configPath}, exists: ${configExists}`
);
} else {
configExists = isConfigFilePresent();
report(
'info',
`Checking for .taskmasterconfig using isConfigFilePresent(), exists: ${configExists}`
);
if (!projectRoot) {
throw new Error('Project root is required but not found.');
}
// Use centralized config path finding instead of hardcoded path
const configPath = findConfigPath(null, { projectRoot });
const configExists = isConfigFilePresent(projectRoot);
log(
'debug',
`Checking for config file using findConfigPath, found: ${configPath}`
);
log(
'debug',
`Checking config file using isConfigFilePresent(), exists: ${configExists}`
);
if (!configExists) {
return {
success: false,
error: {
code: 'CONFIG_MISSING',
message:
'The .taskmasterconfig file is missing. Run "task-master models --setup" to create it.'
}
};
throw new Error(
'The configuration file is missing. Run "task-master models --setup" to create it.'
);
}
try {
@@ -386,34 +372,27 @@ async function setModel(role, modelId, options = {}) {
}
};
// Check if configuration file exists using provided project root
let configPath;
let configExists = false;
if (projectRoot) {
configPath = path.join(projectRoot, '.taskmasterconfig');
configExists = fs.existsSync(configPath);
report(
'info',
`Checking for .taskmasterconfig at: ${configPath}, exists: ${configExists}`
);
} else {
configExists = isConfigFilePresent();
report(
'info',
`Checking for .taskmasterconfig using isConfigFilePresent(), exists: ${configExists}`
);
if (!projectRoot) {
throw new Error('Project root is required but not found.');
}
// Use centralized config path finding instead of hardcoded path
const configPath = findConfigPath(null, { projectRoot });
const configExists = isConfigFilePresent(projectRoot);
log(
'debug',
`Checking for config file using findConfigPath, found: ${configPath}`
);
log(
'debug',
`Checking config file using isConfigFilePresent(), exists: ${configExists}`
);
if (!configExists) {
return {
success: false,
error: {
code: 'CONFIG_MISSING',
message:
'The .taskmasterconfig file is missing. Run "task-master models --setup" to create it.'
}
};
throw new Error(
'The configuration file is missing. Run "task-master models --setup" to create it.'
);
}
// Validate role
@@ -445,7 +424,7 @@ async function setModel(role, modelId, options = {}) {
let warningMessage = null;
// Find the model data in internal list initially to see if it exists at all
let modelData = availableModels.find((m) => m.id === modelId);
const modelData = availableModels.find((m) => m.id === modelId);
// --- Revised Logic: Prioritize providerHint --- //
@@ -556,8 +535,8 @@ async function setModel(role, modelId, options = {}) {
return {
success: false,
error: {
code: 'WRITE_ERROR',
message: 'Error writing updated configuration to .taskmasterconfig'
code: 'CONFIG_WRITE_ERROR',
message: 'Error writing updated configuration to configuration file'
}
};
}

View File

@@ -24,6 +24,7 @@ import {
} from './task-manager.js';
import { getProjectName, getDefaultSubtasks } from './config-manager.js';
import { TASK_STATUS_OPTIONS } from '../../src/constants/task-status.js';
import { TASKMASTER_TASKS_FILE } from '../../src/constants/paths.js';
import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
// Create a color gradient for the banner
@@ -1524,10 +1525,18 @@ async function displayComplexityReport(reportPath) {
if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') {
// Call the analyze-complexity command
console.log(chalk.blue('Generating complexity report...'));
const tasksPath = 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.'
);
return null;
}
await analyzeTaskComplexity({
output: reportPath,
research: false, // Default to no research for speed
file: 'tasks/tasks.json'
file: tasksPath
});
// Read the newly generated report
return displayComplexityReport(reportPath);

View File

@@ -9,6 +9,11 @@ import chalk from 'chalk';
import dotenv from 'dotenv';
// Import specific config getters needed here
import { getLogLevel, getDebugFlag } from './config-manager.js';
import {
COMPLEXITY_REPORT_FILE,
LEGACY_COMPLEXITY_REPORT_FILE,
LEGACY_CONFIG_FILE
} from '../../src/constants/paths.js';
// Global silent mode flag
let silentMode = false;
@@ -60,16 +65,16 @@ function resolveEnvVariable(key, session = null, projectRoot = null) {
// --- Project Root Finding Utility ---
/**
* Finds the project root directory by searching for marker files/directories.
* @param {string} [startPath=process.cwd()] - The directory to start searching from.
* @param {string[]} [markers=['package.json', '.git', '.taskmasterconfig']] - Marker files/dirs to look for.
* @returns {string|null} The path to the project root directory, or null if not found.
* Recursively searches upwards for project root starting from a given directory.
* @param {string} [startDir=process.cwd()] - The directory to start searching from.
* @param {string[]} [markers=['package.json', '.git', LEGACY_CONFIG_FILE]] - Marker files/dirs to look for.
* @returns {string|null} The path to the project root, or null if not found.
*/
function findProjectRoot(
startPath = process.cwd(),
markers = ['package.json', '.git', '.taskmasterconfig']
startDir = process.cwd(),
markers = ['package.json', '.git', LEGACY_CONFIG_FILE]
) {
let currentPath = path.resolve(startPath);
let currentPath = path.resolve(startDir);
const rootPath = path.parse(currentPath).root;
while (currentPath !== rootPath) {
@@ -236,7 +241,7 @@ function sanitizePrompt(prompt) {
}
/**
* Reads and parses the complexity report if it exists
* Reads the complexity report from file
* @param {string} customPath - Optional custom path to the report
* @returns {Object|null} The parsed complexity report or null if not found
*/
@@ -244,21 +249,35 @@ function readComplexityReport(customPath = null) {
// Get debug flag dynamically from config-manager
const isDebug = getDebugFlag();
try {
const reportPath =
customPath ||
path.join(process.cwd(), 'scripts', 'task-complexity-report.json');
let reportPath;
if (customPath) {
reportPath = customPath;
} else {
// Try new location first, then fall back to legacy
const newPath = path.join(process.cwd(), COMPLEXITY_REPORT_FILE);
const legacyPath = path.join(
process.cwd(),
LEGACY_COMPLEXITY_REPORT_FILE
);
reportPath = fs.existsSync(newPath) ? newPath : legacyPath;
}
if (!fs.existsSync(reportPath)) {
if (isDebug) {
log('debug', `Complexity report not found at ${reportPath}`);
}
return null;
}
const reportData = fs.readFileSync(reportPath, 'utf8');
return JSON.parse(reportData);
} catch (error) {
log('warn', `Could not read complexity report: ${error.message}`);
// Optionally log full error in debug mode
const reportData = readJSON(reportPath);
if (isDebug) {
// Use dynamic debug flag
log('error', 'Full error details:', error);
log('debug', `Successfully read complexity report from ${reportPath}`);
}
return reportData;
} catch (error) {
if (isDebug) {
log('error', `Error reading complexity report: ${error.message}`);
}
return null;
}
@@ -395,7 +414,7 @@ function findTaskById(
}
let taskResult = null;
let originalSubtaskCount = null;
const originalSubtaskCount = null;
// Find the main task
const id = parseInt(taskId, 10);
@@ -410,7 +429,6 @@ function findTaskById(
// If task found and statusFilter provided, filter its subtasks
if (statusFilter && task.subtasks && Array.isArray(task.subtasks)) {
const originalSubtaskCount = task.subtasks.length;
// Clone the task to avoid modifying the original array
const filteredTask = { ...task };
filteredTask.subtasks = task.subtasks.filter(
@@ -420,7 +438,6 @@ function findTaskById(
);
taskResult = filteredTask;
originalSubtaskCount = originalSubtaskCount;
}
// If task found and complexityReport provided, add complexity data
@@ -443,7 +460,7 @@ function truncate(text, maxLength) {
return text;
}
return text.slice(0, maxLength - 3) + '...';
return `${text.slice(0, maxLength - 3)}...`;
}
/**