Merge origin/next into extension branch
Resolved conflicts: - Added kiro profile to all relevant files - Updated package.json version to 0.22.0-rc.0 - Integrated extension functionality with latest codebase - Resolved CHANGELOG.md and documentation conflicts - Fixed profile exports and test files This merge brings the VS Code extension up to date with the latest Task Master features including Kiro IDE integration.
This commit is contained in:
@@ -826,7 +826,8 @@ function registerCommands(programInstance) {
|
||||
let taskMaster;
|
||||
try {
|
||||
const initOptions = {
|
||||
prdPath: file || options.input || true
|
||||
prdPath: file || options.input || true,
|
||||
tag: options.tag
|
||||
};
|
||||
// Only include tasksPath if output is explicitly specified
|
||||
if (options.output) {
|
||||
@@ -852,8 +853,7 @@ function registerCommands(programInstance) {
|
||||
const useAppend = append;
|
||||
|
||||
// Resolve tag using standard pattern
|
||||
const tag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
@@ -966,7 +966,8 @@ function registerCommands(programInstance) {
|
||||
.action(async (options) => {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
|
||||
const fromId = parseInt(options.from, 10); // Validation happens here
|
||||
@@ -976,8 +977,7 @@ function registerCommands(programInstance) {
|
||||
const tasksPath = taskMaster.getTasksPath();
|
||||
|
||||
// Resolve tag using standard pattern
|
||||
const tag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
@@ -1066,13 +1066,13 @@ function registerCommands(programInstance) {
|
||||
try {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
const tasksPath = taskMaster.getTasksPath();
|
||||
|
||||
// Resolve tag using standard pattern
|
||||
const tag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
@@ -1238,13 +1238,13 @@ function registerCommands(programInstance) {
|
||||
try {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
const tasksPath = taskMaster.getTasksPath();
|
||||
|
||||
// Resolve tag using standard pattern
|
||||
const tag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
@@ -1404,11 +1404,12 @@ function registerCommands(programInstance) {
|
||||
.action(async (options) => {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
|
||||
const outputDir = options.output;
|
||||
const tag = options.tag;
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
console.log(
|
||||
chalk.blue(`Generating task files from: ${taskMaster.getTasksPath()}`)
|
||||
@@ -1444,12 +1445,12 @@ function registerCommands(programInstance) {
|
||||
.action(async (options) => {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
|
||||
const taskId = options.id;
|
||||
const status = options.status;
|
||||
const tag = options.tag;
|
||||
|
||||
if (!taskId || !status) {
|
||||
console.error(chalk.red('Error: Both --id and --status are required'));
|
||||
@@ -1465,11 +1466,9 @@ function registerCommands(programInstance) {
|
||||
|
||||
process.exit(1);
|
||||
}
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Resolve tag using standard pattern and show current tag context
|
||||
const resolvedTag =
|
||||
tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
displayCurrentTagIndicator(resolvedTag);
|
||||
displayCurrentTagIndicator(tag);
|
||||
|
||||
console.log(
|
||||
chalk.blue(`Setting status of task(s) ${taskId} to: ${status}`)
|
||||
@@ -1501,7 +1500,8 @@ function registerCommands(programInstance) {
|
||||
.action(async (options) => {
|
||||
// Initialize TaskMaster
|
||||
const initOptions = {
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
};
|
||||
|
||||
// Only pass complexityReportPath if user provided a custom path
|
||||
@@ -1513,9 +1513,7 @@ function registerCommands(programInstance) {
|
||||
|
||||
const statusFilter = options.status;
|
||||
const withSubtasks = options.withSubtasks || false;
|
||||
const tag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
|
||||
@@ -1535,8 +1533,7 @@ function registerCommands(programInstance) {
|
||||
taskMaster.getComplexityReportPath(),
|
||||
withSubtasks,
|
||||
'text',
|
||||
tag,
|
||||
{ projectRoot: taskMaster.getProjectRoot() }
|
||||
{ projectRoot: taskMaster.getProjectRoot(), tag }
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1565,18 +1562,29 @@ function registerCommands(programInstance) {
|
||||
'Path to the tasks file (relative to project root)',
|
||||
TASKMASTER_TASKS_FILE // Allow file override
|
||||
) // Allow file override
|
||||
.option(
|
||||
'-cr, --complexity-report <file>',
|
||||
'Path to the report file',
|
||||
COMPLEXITY_REPORT_FILE
|
||||
)
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
});
|
||||
const tag = options.tag;
|
||||
const initOptions = {
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
};
|
||||
|
||||
if (options.complexityReport) {
|
||||
initOptions.complexityReportPath = options.complexityReport;
|
||||
}
|
||||
|
||||
const taskMaster = initTaskMaster(initOptions);
|
||||
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(
|
||||
tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master'
|
||||
);
|
||||
displayCurrentTagIndicator(tag);
|
||||
|
||||
if (options.all) {
|
||||
// --- Handle expand --all ---
|
||||
@@ -1589,7 +1597,11 @@ function registerCommands(programInstance) {
|
||||
options.research, // Pass research flag
|
||||
options.prompt, // Pass additional context
|
||||
options.force, // Pass force flag
|
||||
{ projectRoot: taskMaster.getProjectRoot(), tag } // Pass context with projectRoot and tag
|
||||
{
|
||||
projectRoot: taskMaster.getProjectRoot(),
|
||||
tag,
|
||||
complexityReportPath: taskMaster.getComplexityReportPath()
|
||||
} // Pass context with projectRoot and tag
|
||||
// outputFormat defaults to 'text' in expandAllTasks for CLI
|
||||
);
|
||||
} catch (error) {
|
||||
@@ -1616,7 +1628,11 @@ function registerCommands(programInstance) {
|
||||
options.num,
|
||||
options.research,
|
||||
options.prompt,
|
||||
{ projectRoot: taskMaster.getProjectRoot(), tag }, // Pass context with projectRoot and tag
|
||||
{
|
||||
projectRoot: taskMaster.getProjectRoot(),
|
||||
tag,
|
||||
complexityReportPath: taskMaster.getComplexityReportPath()
|
||||
}, // Pass context with projectRoot and tag
|
||||
options.force // Pass the force flag down
|
||||
);
|
||||
// expandTask logs its own success/failure for single task
|
||||
@@ -1669,34 +1685,28 @@ function registerCommands(programInstance) {
|
||||
.action(async (options) => {
|
||||
// Initialize TaskMaster
|
||||
const initOptions = {
|
||||
tasksPath: options.file || true // Tasks file is required to analyze
|
||||
tasksPath: options.file || true, // Tasks file is required to analyze
|
||||
tag: options.tag
|
||||
};
|
||||
// Only include complexityReportPath if output is explicitly specified
|
||||
if (options.output) {
|
||||
initOptions.complexityReportPath = options.output;
|
||||
}
|
||||
|
||||
const taskMaster = initTaskMaster(initOptions);
|
||||
|
||||
const tag = options.tag;
|
||||
const modelOverride = options.model;
|
||||
const thresholdScore = parseFloat(options.threshold);
|
||||
const useResearch = options.research || false;
|
||||
|
||||
// Use the provided tag, or the current active tag, or default to 'master'
|
||||
const targetTag =
|
||||
tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
const targetTag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(targetTag);
|
||||
|
||||
// Tag-aware output file naming: master -> task-complexity-report.json, other tags -> task-complexity-report_tagname.json
|
||||
const baseOutputPath =
|
||||
taskMaster.getComplexityReportPath() ||
|
||||
path.join(taskMaster.getProjectRoot(), COMPLEXITY_REPORT_FILE);
|
||||
const outputPath =
|
||||
options.output === COMPLEXITY_REPORT_FILE && targetTag !== 'master'
|
||||
? baseOutputPath.replace('.json', `_${targetTag}.json`)
|
||||
: options.output || baseOutputPath;
|
||||
// Use user's explicit output path if provided, otherwise use tag-aware default
|
||||
const outputPath = taskMaster.getComplexityReportPath();
|
||||
|
||||
console.log(
|
||||
chalk.blue(
|
||||
@@ -1777,9 +1787,12 @@ function registerCommands(programInstance) {
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (prompt, options) => {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
});
|
||||
const initOptions = {
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
};
|
||||
|
||||
const taskMaster = initTaskMaster(initOptions);
|
||||
|
||||
// Parameter validation
|
||||
if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) {
|
||||
@@ -1879,8 +1892,7 @@ function registerCommands(programInstance) {
|
||||
}
|
||||
}
|
||||
|
||||
const tag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
@@ -2113,17 +2125,17 @@ ${result.result}
|
||||
.action(async (options) => {
|
||||
const taskIds = options.id;
|
||||
const all = options.all;
|
||||
const tag = options.tag;
|
||||
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(
|
||||
tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master'
|
||||
);
|
||||
displayCurrentTagIndicator(tag);
|
||||
|
||||
if (!taskIds && !all) {
|
||||
console.error(
|
||||
@@ -2219,15 +2231,16 @@ ${result.result}
|
||||
// Correctly determine projectRoot
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
|
||||
const projectRoot = taskMaster.getProjectRoot();
|
||||
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master'
|
||||
);
|
||||
displayCurrentTagIndicator(tag);
|
||||
|
||||
let manualTaskData = null;
|
||||
if (isManualCreation) {
|
||||
@@ -2263,7 +2276,7 @@ ${result.result}
|
||||
|
||||
const context = {
|
||||
projectRoot,
|
||||
tag: options.tag,
|
||||
tag,
|
||||
commandName: 'add-task',
|
||||
outputType: 'cli'
|
||||
};
|
||||
@@ -2309,22 +2322,36 @@ ${result.result}
|
||||
)
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
const tag = options.tag;
|
||||
const initOptions = {
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
};
|
||||
|
||||
if (options.report && options.report !== COMPLEXITY_REPORT_FILE) {
|
||||
initOptions.complexityReportPath = options.report;
|
||||
}
|
||||
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag,
|
||||
complexityReportPath: options.report || false
|
||||
});
|
||||
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
const context = {
|
||||
projectRoot: taskMaster.getProjectRoot(),
|
||||
tag
|
||||
};
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(
|
||||
tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master'
|
||||
);
|
||||
displayCurrentTagIndicator(tag);
|
||||
|
||||
await displayNextTask(
|
||||
taskMaster.getTasksPath(),
|
||||
taskMaster.getComplexityReportPath(),
|
||||
{ projectRoot: taskMaster.getProjectRoot(), tag }
|
||||
context
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2354,7 +2381,8 @@ ${result.result}
|
||||
.action(async (taskId, options) => {
|
||||
// Initialize TaskMaster
|
||||
const initOptions = {
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
};
|
||||
// Only pass complexityReportPath if user provided a custom path
|
||||
if (options.report && options.report !== COMPLEXITY_REPORT_FILE) {
|
||||
@@ -2364,12 +2392,10 @@ ${result.result}
|
||||
|
||||
const idArg = taskId || options.id;
|
||||
const statusFilter = options.status;
|
||||
const tag = options.tag;
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(
|
||||
tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master'
|
||||
);
|
||||
displayCurrentTagIndicator(tag);
|
||||
|
||||
if (!idArg) {
|
||||
console.error(chalk.red('Error: Please provide a task ID'));
|
||||
@@ -2398,8 +2424,7 @@ ${result.result}
|
||||
taskIds[0],
|
||||
taskMaster.getComplexityReportPath(),
|
||||
statusFilter,
|
||||
tag,
|
||||
{ projectRoot: taskMaster.getProjectRoot() }
|
||||
{ projectRoot: taskMaster.getProjectRoot(), tag }
|
||||
);
|
||||
}
|
||||
});
|
||||
@@ -2417,17 +2442,19 @@ ${result.result}
|
||||
)
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
const initOptions = {
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
};
|
||||
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
});
|
||||
const taskMaster = initTaskMaster(initOptions);
|
||||
|
||||
const taskId = options.id;
|
||||
const dependencyId = options.dependsOn;
|
||||
|
||||
// Resolve tag using standard pattern
|
||||
const tag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
@@ -2472,17 +2499,19 @@ ${result.result}
|
||||
)
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
const initOptions = {
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
};
|
||||
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
});
|
||||
const taskMaster = initTaskMaster(initOptions);
|
||||
|
||||
const taskId = options.id;
|
||||
const dependencyId = options.dependsOn;
|
||||
|
||||
// Resolve tag using standard pattern
|
||||
const tag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
@@ -2527,14 +2556,16 @@ ${result.result}
|
||||
)
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
const initOptions = {
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
};
|
||||
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
});
|
||||
const taskMaster = initTaskMaster(initOptions);
|
||||
|
||||
// Resolve tag using standard pattern
|
||||
const tag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
@@ -2555,14 +2586,16 @@ ${result.result}
|
||||
)
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
const initOptions = {
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
};
|
||||
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
});
|
||||
const taskMaster = initTaskMaster(initOptions);
|
||||
|
||||
// Resolve tag using standard pattern
|
||||
const tag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
@@ -2583,26 +2616,21 @@ ${result.result}
|
||||
)
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
complexityReportPath: options.file || true
|
||||
});
|
||||
const initOptions = {
|
||||
tag: options.tag
|
||||
};
|
||||
|
||||
// Use the provided tag, or the current active tag, or default to 'master'
|
||||
const targetTag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
if (options.file && options.file !== COMPLEXITY_REPORT_FILE) {
|
||||
initOptions.complexityReportPath = options.file;
|
||||
}
|
||||
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster(initOptions);
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(targetTag);
|
||||
displayCurrentTagIndicator(taskMaster.getCurrentTag());
|
||||
|
||||
// Tag-aware report file naming: master -> task-complexity-report.json, other tags -> task-complexity-report_tagname.json
|
||||
const baseReportPath = taskMaster.getComplexityReportPath();
|
||||
const reportPath =
|
||||
options.file === COMPLEXITY_REPORT_FILE && targetTag !== 'master'
|
||||
? baseReportPath.replace('.json', `_${targetTag}.json`)
|
||||
: baseReportPath;
|
||||
|
||||
await displayComplexityReport(reportPath);
|
||||
await displayComplexityReport(taskMaster.getComplexityReportPath());
|
||||
});
|
||||
|
||||
// add-subtask command
|
||||
@@ -2627,21 +2655,21 @@ ${result.result}
|
||||
'Comma-separated list of dependency IDs for the new subtask'
|
||||
)
|
||||
.option('-s, --status <status>', 'Status for the new subtask', 'pending')
|
||||
.option('--skip-generate', 'Skip regenerating task files')
|
||||
.option('--generate', 'Regenerate task files after adding subtask')
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
|
||||
const parentId = options.parent;
|
||||
const existingTaskId = options.taskId;
|
||||
const generateFiles = !options.skipGenerate;
|
||||
const generateFiles = options.generate || false;
|
||||
|
||||
// Resolve tag using standard pattern
|
||||
const tag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
@@ -2788,7 +2816,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 --generate Regenerate task files after adding subtask\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" --generate`,
|
||||
{ padding: 1, borderColor: 'blue', borderStyle: 'round' }
|
||||
)
|
||||
);
|
||||
@@ -2811,18 +2839,19 @@ ${result.result}
|
||||
'-c, --convert',
|
||||
'Convert the subtask to a standalone task instead of deleting it'
|
||||
)
|
||||
.option('--skip-generate', 'Skip regenerating task files')
|
||||
.option('--generate', 'Regenerate task files after removing subtask')
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
|
||||
const subtaskIds = options.id;
|
||||
const convertToTask = options.convert || false;
|
||||
const generateFiles = !options.skipGenerate;
|
||||
const tag = options.tag;
|
||||
const generateFiles = options.generate || false;
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
if (!subtaskIds) {
|
||||
console.error(
|
||||
@@ -3117,14 +3146,14 @@ ${result.result}
|
||||
.action(async (options) => {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
|
||||
const taskIdsString = options.id;
|
||||
|
||||
// Resolve tag using standard pattern
|
||||
const tag =
|
||||
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master';
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
// Show current tag context
|
||||
displayCurrentTagIndicator(tag);
|
||||
@@ -3699,10 +3728,7 @@ Examples:
|
||||
const taskMaster = initTaskMaster({});
|
||||
const projectRoot = taskMaster.getProjectRoot(); // Find project root for context
|
||||
const { response, setup } = options;
|
||||
console.log(
|
||||
chalk.blue('Response language set to:', JSON.stringify(options))
|
||||
);
|
||||
let responseLanguage = response || 'English';
|
||||
let responseLanguage = response !== undefined ? response : 'English';
|
||||
if (setup) {
|
||||
console.log(
|
||||
chalk.blue('Starting interactive response language setup...')
|
||||
@@ -3744,6 +3770,7 @@ Examples:
|
||||
`❌ Error setting response language: ${result.error.message}`
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -3768,12 +3795,13 @@ Examples:
|
||||
.action(async (options) => {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
|
||||
const sourceId = options.from;
|
||||
const destinationId = options.to;
|
||||
const tag = options.tag;
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
if (!sourceId || !destinationId) {
|
||||
console.error(
|
||||
@@ -4201,15 +4229,19 @@ Examples:
|
||||
'-s, --status <status>',
|
||||
'Show only tasks matching this status (e.g., pending, done)'
|
||||
)
|
||||
.option('-t, --tag <tag>', 'Tag to use for the task list (default: master)')
|
||||
.action(async (options) => {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
|
||||
const withSubtasks = options.withSubtasks || false;
|
||||
const status = options.status || null;
|
||||
|
||||
const tag = taskMaster.getCurrentTag();
|
||||
|
||||
console.log(
|
||||
chalk.blue(
|
||||
`📝 Syncing tasks to README.md${withSubtasks ? ' (with subtasks)' : ''}${status ? ` (status: ${status})` : ''}...`
|
||||
@@ -4219,7 +4251,8 @@ Examples:
|
||||
const success = await syncTasksToReadme(taskMaster.getProjectRoot(), {
|
||||
withSubtasks,
|
||||
status,
|
||||
tasksPath: taskMaster.getTasksPath()
|
||||
tasksPath: taskMaster.getTasksPath(),
|
||||
tag
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
@@ -4451,11 +4484,13 @@ Examples:
|
||||
TASKMASTER_TASKS_FILE
|
||||
)
|
||||
.option('--show-metadata', 'Show detailed metadata for each tag')
|
||||
.option('--tag <tag>', 'Specify tag context for task operations')
|
||||
.action(async (options) => {
|
||||
try {
|
||||
// Initialize TaskMaster
|
||||
const taskMaster = initTaskMaster({
|
||||
tasksPath: options.file || true
|
||||
tasksPath: options.file || true,
|
||||
tag: options.tag
|
||||
});
|
||||
const tasksPath = taskMaster.getTasksPath();
|
||||
|
||||
@@ -4941,6 +4976,33 @@ async function runCLI(argv = process.argv) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the final complexity-report path.
|
||||
* Rules:
|
||||
* 1. If caller passes --output, always respect it.
|
||||
* 2. If no explicit output AND tag === 'master' → default report file
|
||||
* 3. If no explicit output AND tag !== 'master' → append _<tag>.json
|
||||
*
|
||||
* @param {string|undefined} outputOpt --output value from CLI (may be undefined)
|
||||
* @param {string} targetTag resolved tag (defaults to 'master')
|
||||
* @param {string} projectRoot absolute project root
|
||||
* @returns {string} absolute path for the report
|
||||
*/
|
||||
export function resolveComplexityReportPath({
|
||||
projectRoot,
|
||||
tag = 'master',
|
||||
output // may be undefined
|
||||
}) {
|
||||
// 1. user knows best
|
||||
if (output) {
|
||||
return path.isAbsolute(output) ? output : path.join(projectRoot, output);
|
||||
}
|
||||
|
||||
// 2. default naming
|
||||
const base = path.join(projectRoot, COMPLEXITY_REPORT_FILE);
|
||||
return tag !== 'master' ? base.replace('.json', `_${tag}.json`) : base;
|
||||
}
|
||||
|
||||
export {
|
||||
registerCommands,
|
||||
setupCLI,
|
||||
|
||||
@@ -584,10 +584,21 @@ function getParametersForRole(role, explicitRoot = null) {
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log(
|
||||
'debug',
|
||||
`No model definitions found for provider ${providerName} in MODEL_MAP. Using role default maxTokens: ${roleMaxTokens}`
|
||||
);
|
||||
// Special handling for custom OpenRouter models
|
||||
if (providerName === CUSTOM_PROVIDERS.OPENROUTER) {
|
||||
// Use a conservative default for OpenRouter models not in our list
|
||||
const openrouterDefault = 32768;
|
||||
effectiveMaxTokens = Math.min(roleMaxTokens, openrouterDefault);
|
||||
log(
|
||||
'debug',
|
||||
`Custom OpenRouter model ${modelId} detected. Using conservative max_tokens: ${effectiveMaxTokens}`
|
||||
);
|
||||
} else {
|
||||
log(
|
||||
'debug',
|
||||
`No model definitions found for provider ${providerName} in MODEL_MAP. Using role default maxTokens: ${roleMaxTokens}`
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (lookupError) {
|
||||
log(
|
||||
@@ -772,36 +783,38 @@ function getAvailableModels() {
|
||||
const available = [];
|
||||
for (const [provider, models] of Object.entries(MODEL_MAP)) {
|
||||
if (models.length > 0) {
|
||||
models.forEach((modelObj) => {
|
||||
// Basic name generation - can be improved
|
||||
const modelId = modelObj.id;
|
||||
const sweScore = modelObj.swe_score;
|
||||
const cost = modelObj.cost_per_1m_tokens;
|
||||
const allowedRoles = modelObj.allowed_roles || ['main', 'fallback'];
|
||||
const nameParts = modelId
|
||||
.split('-')
|
||||
.map((p) => p.charAt(0).toUpperCase() + p.slice(1));
|
||||
// Handle specific known names better if needed
|
||||
let name = nameParts.join(' ');
|
||||
if (modelId === 'claude-3.5-sonnet-20240620')
|
||||
name = 'Claude 3.5 Sonnet';
|
||||
if (modelId === 'claude-3-7-sonnet-20250219')
|
||||
name = 'Claude 3.7 Sonnet';
|
||||
if (modelId === 'gpt-4o') name = 'GPT-4o';
|
||||
if (modelId === 'gpt-4-turbo') name = 'GPT-4 Turbo';
|
||||
if (modelId === 'sonar-pro') name = 'Perplexity Sonar Pro';
|
||||
if (modelId === 'sonar-mini') name = 'Perplexity Sonar Mini';
|
||||
models
|
||||
.filter((modelObj) => Boolean(modelObj.supported))
|
||||
.forEach((modelObj) => {
|
||||
// Basic name generation - can be improved
|
||||
const modelId = modelObj.id;
|
||||
const sweScore = modelObj.swe_score;
|
||||
const cost = modelObj.cost_per_1m_tokens;
|
||||
const allowedRoles = modelObj.allowed_roles || ['main', 'fallback'];
|
||||
const nameParts = modelId
|
||||
.split('-')
|
||||
.map((p) => p.charAt(0).toUpperCase() + p.slice(1));
|
||||
// Handle specific known names better if needed
|
||||
let name = nameParts.join(' ');
|
||||
if (modelId === 'claude-3.5-sonnet-20240620')
|
||||
name = 'Claude 3.5 Sonnet';
|
||||
if (modelId === 'claude-3-7-sonnet-20250219')
|
||||
name = 'Claude 3.7 Sonnet';
|
||||
if (modelId === 'gpt-4o') name = 'GPT-4o';
|
||||
if (modelId === 'gpt-4-turbo') name = 'GPT-4 Turbo';
|
||||
if (modelId === 'sonar-pro') name = 'Perplexity Sonar Pro';
|
||||
if (modelId === 'sonar-mini') name = 'Perplexity Sonar Mini';
|
||||
|
||||
available.push({
|
||||
id: modelId,
|
||||
name: name,
|
||||
provider: provider,
|
||||
swe_score: sweScore,
|
||||
cost_per_1m_tokens: cost,
|
||||
allowed_roles: allowedRoles,
|
||||
max_tokens: modelObj.max_tokens
|
||||
available.push({
|
||||
id: modelId,
|
||||
name: name,
|
||||
provider: provider,
|
||||
swe_score: sweScore,
|
||||
cost_per_1m_tokens: cost,
|
||||
allowed_roles: allowedRoles,
|
||||
max_tokens: modelObj.max_tokens
|
||||
});
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// For providers with empty lists (like ollama), maybe add a placeholder or skip
|
||||
available.push({
|
||||
|
||||
@@ -27,6 +27,8 @@ import { generateTaskFiles } from './task-manager.js';
|
||||
* @param {number|string} taskId - ID of the task to add dependency to
|
||||
* @param {number|string} dependencyId - ID of the task to add as dependency
|
||||
* @param {Object} context - Context object containing projectRoot and tag information
|
||||
* @param {string} [context.projectRoot] - Project root path
|
||||
* @param {string} [context.tag] - Tag for the task
|
||||
*/
|
||||
async function addDependency(tasksPath, taskId, dependencyId, context = {}) {
|
||||
log('info', `Adding dependency ${dependencyId} to task ${taskId}...`);
|
||||
@@ -214,6 +216,8 @@ async function addDependency(tasksPath, taskId, dependencyId, context = {}) {
|
||||
* @param {number|string} taskId - ID of the task to remove dependency from
|
||||
* @param {number|string} dependencyId - ID of the task to remove as dependency
|
||||
* @param {Object} context - Context object containing projectRoot and tag information
|
||||
* @param {string} [context.projectRoot] - Project root path
|
||||
* @param {string} [context.tag] - Tag for the task
|
||||
*/
|
||||
async function removeDependency(tasksPath, taskId, dependencyId, context = {}) {
|
||||
log('info', `Removing dependency ${dependencyId} from task ${taskId}...`);
|
||||
|
||||
@@ -8,7 +8,8 @@
|
||||
"output": 15.0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 64000
|
||||
"max_tokens": 64000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "claude-opus-4-20250514",
|
||||
@@ -18,7 +19,8 @@
|
||||
"output": 75.0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 32000
|
||||
"max_tokens": 32000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "claude-3-7-sonnet-20250219",
|
||||
@@ -28,7 +30,8 @@
|
||||
"output": 15.0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 120000
|
||||
"max_tokens": 120000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "claude-3-5-sonnet-20241022",
|
||||
@@ -38,7 +41,8 @@
|
||||
"output": 15.0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 8192
|
||||
"max_tokens": 8192,
|
||||
"supported": true
|
||||
}
|
||||
],
|
||||
"claude-code": [
|
||||
@@ -50,7 +54,8 @@
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 32000
|
||||
"max_tokens": 32000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "sonnet",
|
||||
@@ -60,7 +65,8 @@
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 64000
|
||||
"max_tokens": 64000,
|
||||
"supported": true
|
||||
}
|
||||
],
|
||||
"mcp": [
|
||||
@@ -72,7 +78,8 @@
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 100000
|
||||
"max_tokens": 100000,
|
||||
"supported": true
|
||||
}
|
||||
],
|
||||
"gemini-cli": [
|
||||
@@ -84,7 +91,8 @@
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 65536
|
||||
"max_tokens": 65536,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gemini-2.5-flash",
|
||||
@@ -94,7 +102,8 @@
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 65536
|
||||
"max_tokens": 65536,
|
||||
"supported": true
|
||||
}
|
||||
],
|
||||
"openai": [
|
||||
@@ -106,7 +115,8 @@
|
||||
"output": 10.0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 16384
|
||||
"max_tokens": 16384,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "o1",
|
||||
@@ -115,7 +125,8 @@
|
||||
"input": 15.0,
|
||||
"output": 60.0
|
||||
},
|
||||
"allowed_roles": ["main"]
|
||||
"allowed_roles": ["main"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "o3",
|
||||
@@ -125,7 +136,8 @@
|
||||
"output": 8.0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 100000
|
||||
"max_tokens": 100000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "o3-mini",
|
||||
@@ -135,7 +147,8 @@
|
||||
"output": 4.4
|
||||
},
|
||||
"allowed_roles": ["main"],
|
||||
"max_tokens": 100000
|
||||
"max_tokens": 100000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "o4-mini",
|
||||
@@ -144,7 +157,8 @@
|
||||
"input": 1.1,
|
||||
"output": 4.4
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"]
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "o1-mini",
|
||||
@@ -153,7 +167,8 @@
|
||||
"input": 1.1,
|
||||
"output": 4.4
|
||||
},
|
||||
"allowed_roles": ["main"]
|
||||
"allowed_roles": ["main"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "o1-pro",
|
||||
@@ -162,7 +177,8 @@
|
||||
"input": 150.0,
|
||||
"output": 600.0
|
||||
},
|
||||
"allowed_roles": ["main"]
|
||||
"allowed_roles": ["main"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gpt-4-5-preview",
|
||||
@@ -171,7 +187,8 @@
|
||||
"input": 75.0,
|
||||
"output": 150.0
|
||||
},
|
||||
"allowed_roles": ["main"]
|
||||
"allowed_roles": ["main"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gpt-4-1-mini",
|
||||
@@ -180,7 +197,8 @@
|
||||
"input": 0.4,
|
||||
"output": 1.6
|
||||
},
|
||||
"allowed_roles": ["main"]
|
||||
"allowed_roles": ["main"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gpt-4-1-nano",
|
||||
@@ -189,7 +207,8 @@
|
||||
"input": 0.1,
|
||||
"output": 0.4
|
||||
},
|
||||
"allowed_roles": ["main"]
|
||||
"allowed_roles": ["main"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gpt-4o-mini",
|
||||
@@ -198,7 +217,8 @@
|
||||
"input": 0.15,
|
||||
"output": 0.6
|
||||
},
|
||||
"allowed_roles": ["main"]
|
||||
"allowed_roles": ["main"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gpt-4o-search-preview",
|
||||
@@ -207,7 +227,8 @@
|
||||
"input": 2.5,
|
||||
"output": 10.0
|
||||
},
|
||||
"allowed_roles": ["research"]
|
||||
"allowed_roles": ["research"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gpt-4o-mini-search-preview",
|
||||
@@ -216,7 +237,8 @@
|
||||
"input": 0.15,
|
||||
"output": 0.6
|
||||
},
|
||||
"allowed_roles": ["research"]
|
||||
"allowed_roles": ["research"],
|
||||
"supported": true
|
||||
}
|
||||
],
|
||||
"google": [
|
||||
@@ -225,21 +247,24 @@
|
||||
"swe_score": 0.638,
|
||||
"cost_per_1m_tokens": null,
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1048000
|
||||
"max_tokens": 1048000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gemini-2.5-pro-preview-03-25",
|
||||
"swe_score": 0.638,
|
||||
"cost_per_1m_tokens": null,
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1048000
|
||||
"max_tokens": 1048000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gemini-2.5-flash-preview-04-17",
|
||||
"swe_score": 0.604,
|
||||
"cost_per_1m_tokens": null,
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1048000
|
||||
"max_tokens": 1048000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gemini-2.0-flash",
|
||||
@@ -249,14 +274,16 @@
|
||||
"output": 0.6
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1048000
|
||||
"max_tokens": 1048000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gemini-2.0-flash-lite",
|
||||
"swe_score": 0,
|
||||
"cost_per_1m_tokens": null,
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1048000
|
||||
"max_tokens": 1048000,
|
||||
"supported": true
|
||||
}
|
||||
],
|
||||
"xai": [
|
||||
@@ -269,7 +296,8 @@
|
||||
"output": 15
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 131072
|
||||
"max_tokens": 131072,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "grok-3-fast",
|
||||
@@ -280,7 +308,8 @@
|
||||
"output": 25
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 131072
|
||||
"max_tokens": 131072,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "grok-4",
|
||||
@@ -291,7 +320,8 @@
|
||||
"output": 15
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 131072
|
||||
"max_tokens": 131072,
|
||||
"supported": true
|
||||
}
|
||||
],
|
||||
"groq": [
|
||||
@@ -302,8 +332,9 @@
|
||||
"input": 1.0,
|
||||
"output": 3.0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 16384
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 16384,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "llama-3.3-70b-versatile",
|
||||
@@ -313,7 +344,8 @@
|
||||
"output": 0.79
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 32768
|
||||
"max_tokens": 32768,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "llama-3.1-8b-instant",
|
||||
@@ -323,7 +355,8 @@
|
||||
"output": 0.08
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 131072
|
||||
"max_tokens": 131072,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "llama-4-scout",
|
||||
@@ -333,7 +366,8 @@
|
||||
"output": 0.34
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 32768
|
||||
"max_tokens": 32768,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "llama-4-maverick",
|
||||
@@ -343,7 +377,8 @@
|
||||
"output": 0.77
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 32768
|
||||
"max_tokens": 32768,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "mixtral-8x7b-32768",
|
||||
@@ -353,7 +388,8 @@
|
||||
"output": 0.24
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 32768
|
||||
"max_tokens": 32768,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "qwen-qwq-32b-preview",
|
||||
@@ -363,7 +399,8 @@
|
||||
"output": 0.18
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 32768
|
||||
"max_tokens": 32768,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "deepseek-r1-distill-llama-70b",
|
||||
@@ -373,7 +410,8 @@
|
||||
"output": 0.99
|
||||
},
|
||||
"allowed_roles": ["main", "research"],
|
||||
"max_tokens": 8192
|
||||
"max_tokens": 8192,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gemma2-9b-it",
|
||||
@@ -383,7 +421,8 @@
|
||||
"output": 0.2
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 8192
|
||||
"max_tokens": 8192,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "whisper-large-v3",
|
||||
@@ -393,7 +432,8 @@
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main"],
|
||||
"max_tokens": 0
|
||||
"max_tokens": 0,
|
||||
"supported": true
|
||||
}
|
||||
],
|
||||
"perplexity": [
|
||||
@@ -405,7 +445,8 @@
|
||||
"output": 15
|
||||
},
|
||||
"allowed_roles": ["main", "research"],
|
||||
"max_tokens": 8700
|
||||
"max_tokens": 8700,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "sonar",
|
||||
@@ -415,7 +456,8 @@
|
||||
"output": 1
|
||||
},
|
||||
"allowed_roles": ["research"],
|
||||
"max_tokens": 8700
|
||||
"max_tokens": 8700,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "deep-research",
|
||||
@@ -425,7 +467,8 @@
|
||||
"output": 8
|
||||
},
|
||||
"allowed_roles": ["research"],
|
||||
"max_tokens": 8700
|
||||
"max_tokens": 8700,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "sonar-reasoning-pro",
|
||||
@@ -435,7 +478,8 @@
|
||||
"output": 8
|
||||
},
|
||||
"allowed_roles": ["main", "research", "fallback"],
|
||||
"max_tokens": 8700
|
||||
"max_tokens": 8700,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "sonar-reasoning",
|
||||
@@ -445,7 +489,8 @@
|
||||
"output": 5
|
||||
},
|
||||
"allowed_roles": ["main", "research", "fallback"],
|
||||
"max_tokens": 8700
|
||||
"max_tokens": 8700,
|
||||
"supported": true
|
||||
}
|
||||
],
|
||||
"openrouter": [
|
||||
@@ -457,7 +502,8 @@
|
||||
"output": 0.6
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1048576
|
||||
"max_tokens": 1048576,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "google/gemini-2.5-flash-preview-05-20:thinking",
|
||||
@@ -467,7 +513,8 @@
|
||||
"output": 3.5
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1048576
|
||||
"max_tokens": 1048576,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "google/gemini-2.5-pro-exp-03-25",
|
||||
@@ -477,7 +524,8 @@
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1000000
|
||||
"max_tokens": 1000000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "deepseek/deepseek-chat-v3-0324:free",
|
||||
@@ -487,7 +535,9 @@
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 163840
|
||||
"max_tokens": 163840,
|
||||
"supported": false,
|
||||
"reason": "Free OpenRouter models are not supported due to severe rate limits, lack of tool use support, and other reliability issues that make them impractical for production use."
|
||||
},
|
||||
{
|
||||
"id": "deepseek/deepseek-chat-v3-0324",
|
||||
@@ -497,7 +547,8 @@
|
||||
"output": 1.1
|
||||
},
|
||||
"allowed_roles": ["main"],
|
||||
"max_tokens": 64000
|
||||
"max_tokens": 64000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-4.1",
|
||||
@@ -507,7 +558,8 @@
|
||||
"output": 8
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1000000
|
||||
"max_tokens": 1000000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-4.1-mini",
|
||||
@@ -517,7 +569,8 @@
|
||||
"output": 1.6
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1000000
|
||||
"max_tokens": 1000000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-4.1-nano",
|
||||
@@ -527,7 +580,8 @@
|
||||
"output": 0.4
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1000000
|
||||
"max_tokens": 1000000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "openai/o3",
|
||||
@@ -537,7 +591,8 @@
|
||||
"output": 40
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 200000
|
||||
"max_tokens": 200000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "openai/codex-mini",
|
||||
@@ -547,7 +602,8 @@
|
||||
"output": 6
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 100000
|
||||
"max_tokens": 100000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "openai/gpt-4o-mini",
|
||||
@@ -557,7 +613,8 @@
|
||||
"output": 0.6
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 100000
|
||||
"max_tokens": 100000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "openai/o4-mini",
|
||||
@@ -567,7 +624,8 @@
|
||||
"output": 4.4
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 100000
|
||||
"max_tokens": 100000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "openai/o4-mini-high",
|
||||
@@ -577,7 +635,8 @@
|
||||
"output": 4.4
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 100000
|
||||
"max_tokens": 100000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "openai/o1-pro",
|
||||
@@ -587,7 +646,8 @@
|
||||
"output": 600
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 100000
|
||||
"max_tokens": 100000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "meta-llama/llama-3.3-70b-instruct",
|
||||
@@ -597,7 +657,8 @@
|
||||
"output": 600
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1048576
|
||||
"max_tokens": 1048576,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "meta-llama/llama-4-maverick",
|
||||
@@ -607,7 +668,8 @@
|
||||
"output": 0.6
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1000000
|
||||
"max_tokens": 1000000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "meta-llama/llama-4-scout",
|
||||
@@ -617,7 +679,8 @@
|
||||
"output": 0.3
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1000000
|
||||
"max_tokens": 1000000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "qwen/qwen-max",
|
||||
@@ -627,7 +690,8 @@
|
||||
"output": 6.4
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 32768
|
||||
"max_tokens": 32768,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "qwen/qwen-turbo",
|
||||
@@ -637,7 +701,8 @@
|
||||
"output": 0.2
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 1000000
|
||||
"max_tokens": 32768,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "qwen/qwen3-235b-a22b",
|
||||
@@ -647,7 +712,8 @@
|
||||
"output": 2
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 24000
|
||||
"max_tokens": 24000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "mistralai/mistral-small-3.1-24b-instruct:free",
|
||||
@@ -657,7 +723,9 @@
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 96000
|
||||
"max_tokens": 96000,
|
||||
"supported": false,
|
||||
"reason": "Free OpenRouter models are not supported due to severe rate limits, lack of tool use support, and other reliability issues that make them impractical for production use."
|
||||
},
|
||||
{
|
||||
"id": "mistralai/mistral-small-3.1-24b-instruct",
|
||||
@@ -667,7 +735,8 @@
|
||||
"output": 0.3
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 128000
|
||||
"max_tokens": 128000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "mistralai/devstral-small",
|
||||
@@ -677,7 +746,8 @@
|
||||
"output": 0.3
|
||||
},
|
||||
"allowed_roles": ["main"],
|
||||
"max_tokens": 110000
|
||||
"max_tokens": 110000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "mistralai/mistral-nemo",
|
||||
@@ -687,7 +757,8 @@
|
||||
"output": 0.07
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 100000
|
||||
"max_tokens": 100000,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "thudm/glm-4-32b:free",
|
||||
@@ -697,7 +768,9 @@
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 32768
|
||||
"max_tokens": 32768,
|
||||
"supported": false,
|
||||
"reason": "Free OpenRouter models are not supported due to severe rate limits, lack of tool use support, and other reliability issues that make them impractical for production use."
|
||||
}
|
||||
],
|
||||
"ollama": [
|
||||
@@ -708,7 +781,8 @@
|
||||
"input": 0,
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"]
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "qwen3:latest",
|
||||
@@ -717,7 +791,8 @@
|
||||
"input": 0,
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"]
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "qwen3:14b",
|
||||
@@ -726,7 +801,8 @@
|
||||
"input": 0,
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"]
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "qwen3:32b",
|
||||
@@ -735,7 +811,8 @@
|
||||
"input": 0,
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"]
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "mistral-small3.1:latest",
|
||||
@@ -744,7 +821,8 @@
|
||||
"input": 0,
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"]
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "llama3.3:latest",
|
||||
@@ -753,7 +831,8 @@
|
||||
"input": 0,
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"]
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "phi4:latest",
|
||||
@@ -762,7 +841,8 @@
|
||||
"input": 0,
|
||||
"output": 0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"]
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"supported": true
|
||||
}
|
||||
],
|
||||
"azure": [
|
||||
@@ -771,10 +851,11 @@
|
||||
"swe_score": 0.332,
|
||||
"cost_per_1m_tokens": {
|
||||
"input": 2.5,
|
||||
"output": 10.0
|
||||
"output": 10
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 16384
|
||||
"max_tokens": 16384,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gpt-4o-mini",
|
||||
@@ -784,7 +865,8 @@
|
||||
"output": 0.6
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 16384
|
||||
"max_tokens": 16384,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "gpt-4-1",
|
||||
@@ -794,7 +876,8 @@
|
||||
"output": 10.0
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"max_tokens": 16384
|
||||
"max_tokens": 16384,
|
||||
"supported": true
|
||||
}
|
||||
],
|
||||
"bedrock": [
|
||||
@@ -805,7 +888,8 @@
|
||||
"input": 0.25,
|
||||
"output": 1.25
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"]
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "us.anthropic.claude-3-opus-20240229-v1:0",
|
||||
@@ -814,7 +898,8 @@
|
||||
"input": 15,
|
||||
"output": 75
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"]
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "us.anthropic.claude-3-5-sonnet-20240620-v1:0",
|
||||
@@ -823,7 +908,8 @@
|
||||
"input": 3,
|
||||
"output": 15
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"]
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "us.anthropic.claude-3-5-sonnet-20241022-v2:0",
|
||||
@@ -832,7 +918,8 @@
|
||||
"input": 3,
|
||||
"output": 15
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"]
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "us.anthropic.claude-3-7-sonnet-20250219-v1:0",
|
||||
@@ -842,7 +929,8 @@
|
||||
"output": 15
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"max_tokens": 65536
|
||||
"max_tokens": 65536,
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "us.anthropic.claude-3-5-haiku-20241022-v1:0",
|
||||
@@ -851,7 +939,8 @@
|
||||
"input": 0.8,
|
||||
"output": 4
|
||||
},
|
||||
"allowed_roles": ["main", "fallback"]
|
||||
"allowed_roles": ["main", "fallback"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "us.anthropic.claude-opus-4-20250514-v1:0",
|
||||
@@ -860,7 +949,8 @@
|
||||
"input": 15,
|
||||
"output": 75
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"]
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "us.anthropic.claude-sonnet-4-20250514-v1:0",
|
||||
@@ -869,7 +959,8 @@
|
||||
"input": 3,
|
||||
"output": 15
|
||||
},
|
||||
"allowed_roles": ["main", "fallback", "research"]
|
||||
"allowed_roles": ["main", "fallback", "research"],
|
||||
"supported": true
|
||||
},
|
||||
{
|
||||
"id": "us.deepseek.r1-v1:0",
|
||||
@@ -879,7 +970,8 @@
|
||||
"output": 5.4
|
||||
},
|
||||
"allowed_roles": ["research"],
|
||||
"max_tokens": 65536
|
||||
"max_tokens": 65536,
|
||||
"supported": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -91,11 +91,12 @@ function createEndMarker() {
|
||||
* @param {string} options.status - Filter by status (e.g., 'pending', 'done')
|
||||
* @param {string} options.tasksPath - Custom path to tasks.json
|
||||
* @returns {boolean} - True if sync was successful, false otherwise
|
||||
* TODO: Add tag support - this is not currently supported how we want to handle this - Parthy
|
||||
*/
|
||||
export async function syncTasksToReadme(projectRoot = null, options = {}) {
|
||||
try {
|
||||
const actualProjectRoot = projectRoot || findProjectRoot() || '.';
|
||||
const { withSubtasks = false, status, tasksPath } = options;
|
||||
const { withSubtasks = false, status, tasksPath, tag } = options;
|
||||
|
||||
// Get current tasks using the list-tasks functionality with markdown-readme format
|
||||
const tasksOutput = await listTasks(
|
||||
@@ -104,7 +105,8 @@ export async function syncTasksToReadme(projectRoot = null, options = {}) {
|
||||
status,
|
||||
null,
|
||||
withSubtasks,
|
||||
'markdown-readme'
|
||||
'markdown-readme',
|
||||
{ projectRoot, tag }
|
||||
);
|
||||
|
||||
if (!tasksOutput) {
|
||||
|
||||
@@ -12,6 +12,8 @@ import generateTaskFiles from './generate-task-files.js';
|
||||
* @param {Object} newSubtaskData - Data for creating a new subtask (used if existingTaskId is null)
|
||||
* @param {boolean} generateFiles - Whether to regenerate task files after adding the subtask
|
||||
* @param {Object} context - Context object containing projectRoot and tag information
|
||||
* @param {string} context.projectRoot - Project root path
|
||||
* @param {string} context.tag - Tag for the task
|
||||
* @returns {Object} The newly created or converted subtask
|
||||
*/
|
||||
async function addSubtask(
|
||||
@@ -19,16 +21,15 @@ async function addSubtask(
|
||||
parentId,
|
||||
existingTaskId = null,
|
||||
newSubtaskData = null,
|
||||
generateFiles = true,
|
||||
generateFiles = false,
|
||||
context = {}
|
||||
) {
|
||||
const { projectRoot, tag } = context;
|
||||
try {
|
||||
log('info', `Adding subtask to parent task ${parentId}...`);
|
||||
|
||||
const currentTag =
|
||||
context.tag || getCurrentTag(context.projectRoot) || 'master';
|
||||
// Read the existing tasks with proper context
|
||||
const data = readJSON(tasksPath, context.projectRoot, currentTag);
|
||||
const data = readJSON(tasksPath, projectRoot, tag);
|
||||
if (!data || !data.tasks) {
|
||||
throw new Error(`Invalid or missing tasks file at ${tasksPath}`);
|
||||
}
|
||||
@@ -139,7 +140,7 @@ async function addSubtask(
|
||||
}
|
||||
|
||||
// Write the updated tasks back to the file with proper context
|
||||
writeJSON(tasksPath, data, context.projectRoot, currentTag);
|
||||
writeJSON(tasksPath, data, projectRoot, tag);
|
||||
|
||||
// Generate task files if requested
|
||||
if (generateFiles) {
|
||||
|
||||
@@ -22,8 +22,7 @@ import {
|
||||
truncate,
|
||||
ensureTagMetadata,
|
||||
performCompleteTagMigration,
|
||||
markMigrationForNotice,
|
||||
getCurrentTag
|
||||
markMigrationForNotice
|
||||
} from '../utils.js';
|
||||
import { generateObjectService } from '../ai-services-unified.js';
|
||||
import { getDefaultPriority } from '../config-manager.js';
|
||||
@@ -93,7 +92,7 @@ function getAllTasks(rawData) {
|
||||
* @param {string} [context.projectRoot] - Project root path (for MCP/env fallback)
|
||||
* @param {string} [context.commandName] - The name of the command being executed (for telemetry)
|
||||
* @param {string} [context.outputType] - The output type ('cli' or 'mcp', for telemetry)
|
||||
* @param {string} [tag] - Tag for the task (optional)
|
||||
* @param {string} [context.tag] - Tag for the task (optional)
|
||||
* @returns {Promise<object>} An object containing newTaskId and telemetryData
|
||||
*/
|
||||
async function addTask(
|
||||
@@ -104,10 +103,10 @@ async function addTask(
|
||||
context = {},
|
||||
outputFormat = 'text', // Default to text for CLI
|
||||
manualTaskData = null,
|
||||
useResearch = false,
|
||||
tag = null
|
||||
useResearch = false
|
||||
) {
|
||||
const { session, mcpLog, projectRoot, commandName, outputType } = context;
|
||||
const { session, mcpLog, projectRoot, commandName, outputType, tag } =
|
||||
context;
|
||||
const isMCP = !!mcpLog;
|
||||
|
||||
// Create a consistent logFn object regardless of context
|
||||
@@ -224,7 +223,7 @@ async function addTask(
|
||||
|
||||
try {
|
||||
// Read the existing tasks - IMPORTANT: Read the raw data without tag resolution
|
||||
let rawData = readJSON(tasksPath, projectRoot); // No tag parameter
|
||||
let rawData = readJSON(tasksPath, projectRoot, tag); // No tag parameter
|
||||
|
||||
// Handle the case where readJSON returns resolved data with _rawTaggedData
|
||||
if (rawData && rawData._rawTaggedData) {
|
||||
@@ -279,8 +278,7 @@ async function addTask(
|
||||
}
|
||||
|
||||
// Use the provided tag, or the current active tag, or default to 'master'
|
||||
const targetTag =
|
||||
tag || context.tag || getCurrentTag(projectRoot) || 'master';
|
||||
const targetTag = tag;
|
||||
|
||||
// Ensure the target tag exists
|
||||
if (!rawData[targetTag]) {
|
||||
@@ -389,7 +387,7 @@ async function addTask(
|
||||
report(`Generating task data with AI with prompt:\n${prompt}`, 'info');
|
||||
|
||||
// --- Use the new ContextGatherer ---
|
||||
const contextGatherer = new ContextGatherer(projectRoot);
|
||||
const contextGatherer = new ContextGatherer(projectRoot, tag);
|
||||
const gatherResult = await contextGatherer.gather({
|
||||
semanticQuery: prompt,
|
||||
dependencyTasks: numericDependencies,
|
||||
|
||||
@@ -19,6 +19,7 @@ import {
|
||||
COMPLEXITY_REPORT_FILE,
|
||||
LEGACY_TASKS_FILE
|
||||
} from '../../../src/constants/paths.js';
|
||||
import { resolveComplexityReportOutputPath } from '../../../src/utils/path-utils.js';
|
||||
import { ContextGatherer } from '../utils/contextGatherer.js';
|
||||
import { FuzzyTaskSearch } from '../utils/fuzzyTaskSearch.js';
|
||||
import { flattenTasksWithSubtasks } from '../utils.js';
|
||||
@@ -71,6 +72,7 @@ Do not include any explanatory text, markdown formatting, or code block markers
|
||||
* @param {string|number} [options.threshold] - Complexity threshold
|
||||
* @param {boolean} [options.research] - Use research role
|
||||
* @param {string} [options.projectRoot] - Project root path (for MCP/env fallback).
|
||||
* @param {string} [options.tag] - Tag for the task
|
||||
* @param {string} [options.id] - Comma-separated list of task IDs to analyze specifically
|
||||
* @param {number} [options.from] - Starting task ID in a range to analyze
|
||||
* @param {number} [options.to] - Ending task ID in a range to analyze
|
||||
@@ -84,7 +86,6 @@ 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 || 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;
|
||||
@@ -109,6 +110,13 @@ async function analyzeTaskComplexity(options, context = {}) {
|
||||
}
|
||||
};
|
||||
|
||||
// Resolve output path using tag-aware resolution
|
||||
const outputPath = resolveComplexityReportOutputPath(
|
||||
options.output,
|
||||
{ projectRoot, tag },
|
||||
reportLog
|
||||
);
|
||||
|
||||
if (outputFormat === 'text') {
|
||||
console.log(
|
||||
chalk.blue(
|
||||
@@ -220,7 +228,7 @@ async function analyzeTaskComplexity(options, context = {}) {
|
||||
let gatheredContext = '';
|
||||
if (originalData && originalData.tasks.length > 0) {
|
||||
try {
|
||||
const contextGatherer = new ContextGatherer(projectRoot);
|
||||
const contextGatherer = new ContextGatherer(projectRoot, tag);
|
||||
const allTasksFlat = flattenTasksWithSubtasks(originalData.tasks);
|
||||
const fuzzySearch = new FuzzyTaskSearch(
|
||||
allTasksFlat,
|
||||
@@ -535,7 +543,7 @@ async function analyzeTaskComplexity(options, context = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Merge with existing report
|
||||
// Merge with existing report - only keep entries from the current tag
|
||||
let finalComplexityAnalysis = [];
|
||||
|
||||
if (existingReport && Array.isArray(existingReport.complexityAnalysis)) {
|
||||
@@ -544,10 +552,14 @@ async function analyzeTaskComplexity(options, context = {}) {
|
||||
complexityAnalysis.map((item) => item.taskId)
|
||||
);
|
||||
|
||||
// Keep existing entries that weren't in this analysis run
|
||||
// Keep existing entries that weren't in this analysis run AND belong to the current tag
|
||||
// We determine tag membership by checking if the task ID exists in the current tag's tasks
|
||||
const currentTagTaskIds = new Set(tasksData.tasks.map((t) => t.id));
|
||||
const existingEntriesNotAnalyzed =
|
||||
existingReport.complexityAnalysis.filter(
|
||||
(item) => !analyzedTaskIds.has(item.taskId)
|
||||
(item) =>
|
||||
!analyzedTaskIds.has(item.taskId) &&
|
||||
currentTagTaskIds.has(item.taskId) // Only keep entries for tasks in current tag
|
||||
);
|
||||
|
||||
// Combine with new analysis
|
||||
@@ -557,7 +569,7 @@ async function analyzeTaskComplexity(options, context = {}) {
|
||||
];
|
||||
|
||||
reportLog(
|
||||
`Merged ${complexityAnalysis.length} new analyses with ${existingEntriesNotAnalyzed.length} existing entries`,
|
||||
`Merged ${complexityAnalysis.length} new analyses with ${existingEntriesNotAnalyzed.length} existing entries from current tag`,
|
||||
'info'
|
||||
);
|
||||
} else {
|
||||
|
||||
@@ -11,6 +11,8 @@ import { displayBanner } from '../ui.js';
|
||||
* @param {string} tasksPath - Path to the tasks.json file
|
||||
* @param {string} taskIds - Task IDs to clear subtasks from
|
||||
* @param {Object} context - Context object containing projectRoot and tag
|
||||
* @param {string} [context.projectRoot] - Project root path
|
||||
* @param {string} [context.tag] - Tag for the task
|
||||
*/
|
||||
function clearSubtasks(tasksPath, taskIds, context = {}) {
|
||||
const { projectRoot, tag } = context;
|
||||
|
||||
@@ -20,6 +20,9 @@ import boxen from 'boxen';
|
||||
* @param {Object} context - Context object containing session and mcpLog.
|
||||
* @param {Object} [context.session] - Session object from MCP.
|
||||
* @param {Object} [context.mcpLog] - MCP logger object.
|
||||
* @param {string} [context.projectRoot] - Project root path
|
||||
* @param {string} [context.tag] - Tag for the task
|
||||
* @param {string} [context.complexityReportPath] - Path to the complexity report file
|
||||
* @param {string} [outputFormat='text'] - Output format ('text' or 'json'). MCP calls should use 'json'.
|
||||
* @returns {Promise<{success: boolean, expandedCount: number, failedCount: number, skippedCount: number, tasksToExpand: number, telemetryData: Array<Object>}>} - Result summary.
|
||||
*/
|
||||
@@ -36,7 +39,8 @@ async function expandAllTasks(
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot: providedProjectRoot,
|
||||
tag: contextTag
|
||||
tag,
|
||||
complexityReportPath
|
||||
} = context;
|
||||
const isMCPCall = !!mcpLog; // Determine if called from MCP
|
||||
|
||||
@@ -79,7 +83,7 @@ async function expandAllTasks(
|
||||
|
||||
try {
|
||||
logger.info(`Reading tasks from ${tasksPath}`);
|
||||
const data = readJSON(tasksPath, projectRoot, contextTag);
|
||||
const data = readJSON(tasksPath, projectRoot, tag);
|
||||
if (!data || !data.tasks) {
|
||||
throw new Error(`Invalid tasks data in ${tasksPath}`);
|
||||
}
|
||||
@@ -129,7 +133,12 @@ async function expandAllTasks(
|
||||
numSubtasks,
|
||||
useResearch,
|
||||
additionalContext,
|
||||
{ ...context, projectRoot, tag: data.tag || contextTag }, // Pass the whole context object with projectRoot and resolved tag
|
||||
{
|
||||
...context,
|
||||
projectRoot,
|
||||
tag: data.tag || tag,
|
||||
complexityReportPath
|
||||
}, // Pass the whole context object with projectRoot and resolved tag
|
||||
force
|
||||
);
|
||||
expandedCount++;
|
||||
|
||||
@@ -40,8 +40,10 @@ const subtaskSchema = z
|
||||
.min(10)
|
||||
.describe('Detailed description of the subtask'),
|
||||
dependencies: z
|
||||
.array(z.number().int())
|
||||
.describe('IDs of prerequisite subtasks within this expansion'),
|
||||
.array(z.string())
|
||||
.describe(
|
||||
'Array of subtask dependencies within the same parent task. Use format ["parentTaskId.1", "parentTaskId.2"]. Subtasks can only depend on siblings, not external tasks.'
|
||||
),
|
||||
details: z.string().min(20).describe('Implementation details and guidance'),
|
||||
status: z
|
||||
.string()
|
||||
@@ -235,12 +237,10 @@ function parseSubtasksFromText(
|
||||
...rawSubtask,
|
||||
id: currentId,
|
||||
dependencies: Array.isArray(rawSubtask.dependencies)
|
||||
? rawSubtask.dependencies
|
||||
.map((dep) => (typeof dep === 'string' ? parseInt(dep, 10) : dep))
|
||||
.filter(
|
||||
(depId) =>
|
||||
!Number.isNaN(depId) && depId >= startId && depId < currentId
|
||||
)
|
||||
? rawSubtask.dependencies.filter(
|
||||
(dep) =>
|
||||
typeof dep === 'string' && dep.startsWith(`${parentTaskId}.`)
|
||||
)
|
||||
: [],
|
||||
status: 'pending'
|
||||
};
|
||||
@@ -290,6 +290,8 @@ function parseSubtasksFromText(
|
||||
* @param {Object} context - Context object containing session and mcpLog.
|
||||
* @param {Object} [context.session] - Session object from MCP.
|
||||
* @param {Object} [context.mcpLog] - MCP logger object.
|
||||
* @param {string} [context.projectRoot] - Project root path
|
||||
* @param {string} [context.tag] - Tag for the task
|
||||
* @param {boolean} [force=false] - If true, replace existing subtasks; otherwise, append.
|
||||
* @returns {Promise<Object>} The updated parent task object with new subtasks.
|
||||
* @throws {Error} If task not found, AI service fails, or parsing fails.
|
||||
@@ -303,7 +305,13 @@ async function expandTask(
|
||||
context = {},
|
||||
force = false
|
||||
) {
|
||||
const { session, mcpLog, projectRoot: contextProjectRoot, tag } = context;
|
||||
const {
|
||||
session,
|
||||
mcpLog,
|
||||
projectRoot: contextProjectRoot,
|
||||
tag,
|
||||
complexityReportPath
|
||||
} = context;
|
||||
const outputFormat = mcpLog ? 'json' : 'text';
|
||||
|
||||
// Determine projectRoot: Use from context if available, otherwise derive from tasksPath
|
||||
@@ -350,7 +358,7 @@ async function expandTask(
|
||||
// --- Context Gathering ---
|
||||
let gatheredContext = '';
|
||||
try {
|
||||
const contextGatherer = new ContextGatherer(projectRoot);
|
||||
const contextGatherer = new ContextGatherer(projectRoot, tag);
|
||||
const allTasksFlat = flattenTasksWithSubtasks(data.tasks);
|
||||
const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'expand-task');
|
||||
const searchQuery = `${task.title} ${task.description}`;
|
||||
@@ -379,17 +387,10 @@ async function expandTask(
|
||||
// --- Complexity Report Integration ---
|
||||
let finalSubtaskCount;
|
||||
let complexityReasoningContext = '';
|
||||
|
||||
// Use tag-aware complexity report path
|
||||
const complexityReportPath = getTagAwareFilePath(
|
||||
COMPLEXITY_REPORT_FILE,
|
||||
tag,
|
||||
projectRoot
|
||||
);
|
||||
let taskAnalysis = null;
|
||||
|
||||
logger.info(
|
||||
`Looking for complexity report at: ${complexityReportPath}${tag && tag !== 'master' ? ` (tag-specific for '${tag}')` : ''}`
|
||||
`Looking for complexity report at: ${complexityReportPath}${tag !== 'master' ? ` (tag-specific for '${tag}')` : ''}`
|
||||
);
|
||||
|
||||
try {
|
||||
|
||||
@@ -12,16 +12,20 @@ import { getDebugFlag } from '../config-manager.js';
|
||||
* @param {string} tasksPath - Path to the tasks.json file
|
||||
* @param {string} outputDir - Output directory for task files
|
||||
* @param {Object} options - Additional options (mcpLog for MCP mode, projectRoot, tag)
|
||||
* @param {string} [options.projectRoot] - Project root path
|
||||
* @param {string} [options.tag] - Tag for the task
|
||||
* @param {Object} [options.mcpLog] - MCP logger object
|
||||
* @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode
|
||||
*/
|
||||
function generateTaskFiles(tasksPath, outputDir, options = {}) {
|
||||
try {
|
||||
const isMcpMode = !!options?.mcpLog;
|
||||
const { projectRoot, tag } = options;
|
||||
|
||||
// 1. Read the raw data structure, ensuring we have all tags.
|
||||
// We call readJSON without a specific tag to get the resolved default view,
|
||||
// which correctly contains the full structure in `_rawTaggedData`.
|
||||
const resolvedData = readJSON(tasksPath, options.projectRoot);
|
||||
const resolvedData = readJSON(tasksPath, projectRoot, tag);
|
||||
if (!resolvedData) {
|
||||
throw new Error(`Could not read or parse tasks file: ${tasksPath}`);
|
||||
}
|
||||
@@ -29,13 +33,10 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
|
||||
const rawData = resolvedData._rawTaggedData || resolvedData;
|
||||
|
||||
// 2. Determine the target tag we need to generate files for.
|
||||
const targetTag = options.tag || resolvedData.tag || 'master';
|
||||
const tagData = rawData[targetTag];
|
||||
const tagData = rawData[tag];
|
||||
|
||||
if (!tagData || !tagData.tasks) {
|
||||
throw new Error(
|
||||
`Tag '${targetTag}' not found or has no tasks in the data.`
|
||||
);
|
||||
throw new Error(`Tag '${tag}' not found or has no tasks in the data.`);
|
||||
}
|
||||
const tasksForGeneration = tagData.tasks;
|
||||
|
||||
@@ -46,15 +47,15 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
|
||||
|
||||
log(
|
||||
'info',
|
||||
`Preparing to regenerate ${tasksForGeneration.length} task files for tag '${targetTag}'`
|
||||
`Preparing to regenerate ${tasksForGeneration.length} task files for tag '${tag}'`
|
||||
);
|
||||
|
||||
// 3. Validate dependencies using the FULL, raw data structure to prevent data loss.
|
||||
validateAndFixDependencies(
|
||||
rawData, // Pass the entire object with all tags
|
||||
tasksPath,
|
||||
options.projectRoot,
|
||||
targetTag // Provide the current tag context for the operation
|
||||
projectRoot,
|
||||
tag // Provide the current tag context for the operation
|
||||
);
|
||||
|
||||
const allTasksInTag = tagData.tasks;
|
||||
@@ -66,14 +67,14 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
|
||||
const files = fs.readdirSync(outputDir);
|
||||
// Tag-aware file patterns: master -> task_001.txt, other tags -> task_001_tagname.txt
|
||||
const masterFilePattern = /^task_(\d+)\.txt$/;
|
||||
const taggedFilePattern = new RegExp(`^task_(\\d+)_${targetTag}\\.txt$`);
|
||||
const taggedFilePattern = new RegExp(`^task_(\\d+)_${tag}\\.txt$`);
|
||||
|
||||
const orphanedFiles = files.filter((file) => {
|
||||
let match = null;
|
||||
let fileTaskId = null;
|
||||
|
||||
// Check if file belongs to current tag
|
||||
if (targetTag === 'master') {
|
||||
if (tag === 'master') {
|
||||
match = file.match(masterFilePattern);
|
||||
if (match) {
|
||||
fileTaskId = parseInt(match[1], 10);
|
||||
@@ -94,7 +95,7 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
|
||||
if (orphanedFiles.length > 0) {
|
||||
log(
|
||||
'info',
|
||||
`Found ${orphanedFiles.length} orphaned task files to remove for tag '${targetTag}'`
|
||||
`Found ${orphanedFiles.length} orphaned task files to remove for tag '${tag}'`
|
||||
);
|
||||
orphanedFiles.forEach((file) => {
|
||||
const filePath = path.join(outputDir, file);
|
||||
@@ -108,13 +109,13 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
|
||||
}
|
||||
|
||||
// Generate task files for the target tag
|
||||
log('info', `Generating individual task files for tag '${targetTag}'...`);
|
||||
log('info', `Generating individual task files for tag '${tag}'...`);
|
||||
tasksForGeneration.forEach((task) => {
|
||||
// Tag-aware file naming: master -> task_001.txt, other tags -> task_001_tagname.txt
|
||||
const taskFileName =
|
||||
targetTag === 'master'
|
||||
tag === 'master'
|
||||
? `task_${task.id.toString().padStart(3, '0')}.txt`
|
||||
: `task_${task.id.toString().padStart(3, '0')}_${targetTag}.txt`;
|
||||
: `task_${task.id.toString().padStart(3, '0')}_${tag}.txt`;
|
||||
|
||||
const taskPath = path.join(outputDir, taskFileName);
|
||||
|
||||
@@ -174,7 +175,7 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) {
|
||||
|
||||
log(
|
||||
'success',
|
||||
`All ${tasksForGeneration.length} tasks for tag '${targetTag}' have been generated into '${outputDir}'.`
|
||||
`All ${tasksForGeneration.length} tasks for tag '${tag}' have been generated into '${outputDir}'.`
|
||||
);
|
||||
|
||||
if (isMcpMode) {
|
||||
|
||||
@@ -26,8 +26,9 @@ import {
|
||||
* @param {string} reportPath - Path to the complexity report
|
||||
* @param {boolean} withSubtasks - Whether to show subtasks
|
||||
* @param {string} outputFormat - Output format (text or json)
|
||||
* @param {string} tag - Optional tag to override current tag resolution
|
||||
* @param {Object} context - Optional context object containing projectRoot and other options
|
||||
* @param {Object} context - Context object (required)
|
||||
* @param {string} context.projectRoot - Project root path
|
||||
* @param {string} context.tag - Tag for the task
|
||||
* @returns {Object} - Task list result for json format
|
||||
*/
|
||||
function listTasks(
|
||||
@@ -36,18 +37,18 @@ function listTasks(
|
||||
reportPath = null,
|
||||
withSubtasks = false,
|
||||
outputFormat = 'text',
|
||||
tag = null,
|
||||
context = {}
|
||||
) {
|
||||
const { projectRoot, tag } = context;
|
||||
try {
|
||||
// Extract projectRoot from context if provided
|
||||
const projectRoot = context.projectRoot || null;
|
||||
const data = readJSON(tasksPath, projectRoot, tag); // Pass projectRoot to readJSON
|
||||
if (!data || !data.tasks) {
|
||||
throw new Error(`No valid tasks found in ${tasksPath}`);
|
||||
}
|
||||
|
||||
// Add complexity scores to tasks if report exists
|
||||
// `reportPath` is already tag-aware (resolved at the CLI boundary).
|
||||
const complexityReport = readComplexityReport(reportPath);
|
||||
// Apply complexity scores to tasks
|
||||
if (complexityReport && complexityReport.complexityAnalysis) {
|
||||
|
||||
@@ -25,6 +25,10 @@ import { findConfigPath } from '../../../src/utils/path-utils.js';
|
||||
import { log } from '../utils.js';
|
||||
import { CUSTOM_PROVIDERS } from '../../../src/constants/providers.js';
|
||||
|
||||
// Constants
|
||||
const CONFIG_MISSING_ERROR =
|
||||
'The configuration file is missing. Run "task-master init" to create it.';
|
||||
|
||||
/**
|
||||
* Fetches the list of models from OpenRouter API.
|
||||
* @returns {Promise<Array|null>} A promise that resolves with the list of model IDs or null if fetch fails.
|
||||
@@ -168,9 +172,7 @@ async function getModelConfiguration(options = {}) {
|
||||
);
|
||||
|
||||
if (!configExists) {
|
||||
throw new Error(
|
||||
'The configuration file is missing. Run "task-master models --setup" to create it.'
|
||||
);
|
||||
throw new Error(CONFIG_MISSING_ERROR);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -298,9 +300,7 @@ async function getAvailableModelsList(options = {}) {
|
||||
);
|
||||
|
||||
if (!configExists) {
|
||||
throw new Error(
|
||||
'The configuration file is missing. Run "task-master models --setup" to create it.'
|
||||
);
|
||||
throw new Error(CONFIG_MISSING_ERROR);
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -391,9 +391,7 @@ async function setModel(role, modelId, options = {}) {
|
||||
);
|
||||
|
||||
if (!configExists) {
|
||||
throw new Error(
|
||||
'The configuration file is missing. Run "task-master models --setup" to create it.'
|
||||
);
|
||||
throw new Error(CONFIG_MISSING_ERROR);
|
||||
}
|
||||
|
||||
// Validate role
|
||||
|
||||
@@ -1,11 +1,5 @@
|
||||
import path from 'path';
|
||||
import {
|
||||
log,
|
||||
readJSON,
|
||||
writeJSON,
|
||||
getCurrentTag,
|
||||
setTasksForTag
|
||||
} from '../utils.js';
|
||||
import { log, readJSON, writeJSON, setTasksForTag } from '../utils.js';
|
||||
import { isTaskDependentOn } from '../task-manager.js';
|
||||
import generateTaskFiles from './generate-task-files.js';
|
||||
|
||||
@@ -27,6 +21,7 @@ async function moveTask(
|
||||
generateFiles = false,
|
||||
options = {}
|
||||
) {
|
||||
const { projectRoot, tag } = options;
|
||||
// Check if we have comma-separated IDs (batch move)
|
||||
const sourceIds = sourceId.split(',').map((id) => id.trim());
|
||||
const destinationIds = destinationId.split(',').map((id) => id.trim());
|
||||
@@ -53,7 +48,10 @@ async function moveTask(
|
||||
|
||||
// Generate files once at the end if requested
|
||||
if (generateFiles) {
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
|
||||
tag: tag,
|
||||
projectRoot: projectRoot
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
@@ -64,7 +62,7 @@ async function moveTask(
|
||||
|
||||
// Single move logic
|
||||
// Read the raw data without tag resolution to preserve tagged structure
|
||||
let rawData = readJSON(tasksPath, options.projectRoot); // No tag parameter
|
||||
let rawData = readJSON(tasksPath, projectRoot, tag);
|
||||
|
||||
// Handle the case where readJSON returns resolved data with _rawTaggedData
|
||||
if (rawData && rawData._rawTaggedData) {
|
||||
@@ -72,27 +70,19 @@ async function moveTask(
|
||||
rawData = rawData._rawTaggedData;
|
||||
}
|
||||
|
||||
// Determine the current tag
|
||||
const currentTag =
|
||||
options.tag || getCurrentTag(options.projectRoot) || 'master';
|
||||
|
||||
// Ensure the tag exists in the raw data
|
||||
if (
|
||||
!rawData ||
|
||||
!rawData[currentTag] ||
|
||||
!Array.isArray(rawData[currentTag].tasks)
|
||||
) {
|
||||
if (!rawData || !rawData[tag] || !Array.isArray(rawData[tag].tasks)) {
|
||||
throw new Error(
|
||||
`Invalid tasks file or tag "${currentTag}" not found at ${tasksPath}`
|
||||
`Invalid tasks file or tag "${tag}" not found at ${tasksPath}`
|
||||
);
|
||||
}
|
||||
|
||||
// Get the tasks for the current tag
|
||||
const tasks = rawData[currentTag].tasks;
|
||||
const tasks = rawData[tag].tasks;
|
||||
|
||||
log(
|
||||
'info',
|
||||
`Moving task/subtask ${sourceId} to ${destinationId} (tag: ${currentTag})`
|
||||
`Moving task/subtask ${sourceId} to ${destinationId} (tag: ${tag})`
|
||||
);
|
||||
|
||||
// Parse source and destination IDs
|
||||
@@ -116,14 +106,17 @@ async function moveTask(
|
||||
}
|
||||
|
||||
// Update the data structure with the modified tasks
|
||||
rawData[currentTag].tasks = tasks;
|
||||
rawData[tag].tasks = tasks;
|
||||
|
||||
// Always write the data object, never the _rawTaggedData directly
|
||||
// The writeJSON function will filter out _rawTaggedData automatically
|
||||
writeJSON(tasksPath, rawData, options.projectRoot, currentTag);
|
||||
writeJSON(tasksPath, rawData, options.projectRoot, tag);
|
||||
|
||||
if (generateFiles) {
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
|
||||
tag: tag,
|
||||
projectRoot: projectRoot
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
@@ -19,7 +19,6 @@ import {
|
||||
import { generateObjectService } from '../ai-services-unified.js';
|
||||
import { getDebugFlag } from '../config-manager.js';
|
||||
import { getPromptManager } from '../prompt-manager.js';
|
||||
import generateTaskFiles from './generate-task-files.js';
|
||||
import { displayAiUsageSummary } from '../ui.js';
|
||||
|
||||
// Define the Zod schema for a SINGLE task object
|
||||
@@ -76,7 +75,7 @@ async function parsePRD(prdPath, tasksPath, numTasks, options = {}) {
|
||||
const outputFormat = isMCP ? 'json' : 'text';
|
||||
|
||||
// Use the provided tag, or the current active tag, or default to 'master'
|
||||
const targetTag = tag || getCurrentTag(projectRoot) || 'master';
|
||||
const targetTag = tag;
|
||||
|
||||
const logFn = mcpLog
|
||||
? mcpLog
|
||||
|
||||
@@ -9,20 +9,23 @@ import generateTaskFiles from './generate-task-files.js';
|
||||
* @param {boolean} convertToTask - Whether to convert the subtask to a standalone task
|
||||
* @param {boolean} generateFiles - Whether to regenerate task files after removing the subtask
|
||||
* @param {Object} context - Context object containing projectRoot and tag information
|
||||
* @param {string} [context.projectRoot] - Project root path
|
||||
* @param {string} [context.tag] - Tag for the task
|
||||
* @returns {Object|null} The removed subtask if convertToTask is true, otherwise null
|
||||
*/
|
||||
async function removeSubtask(
|
||||
tasksPath,
|
||||
subtaskId,
|
||||
convertToTask = false,
|
||||
generateFiles = true,
|
||||
generateFiles = false,
|
||||
context = {}
|
||||
) {
|
||||
const { projectRoot, tag } = context;
|
||||
try {
|
||||
log('info', `Removing subtask ${subtaskId}...`);
|
||||
|
||||
// Read the existing tasks with proper context
|
||||
const data = readJSON(tasksPath, context.projectRoot, context.tag);
|
||||
const data = readJSON(tasksPath, projectRoot, tag);
|
||||
if (!data || !data.tasks) {
|
||||
throw new Error(`Invalid or missing tasks file at ${tasksPath}`);
|
||||
}
|
||||
@@ -103,12 +106,12 @@ async function removeSubtask(
|
||||
}
|
||||
|
||||
// Write the updated tasks back to the file with proper context
|
||||
writeJSON(tasksPath, data, context.projectRoot, context.tag);
|
||||
writeJSON(tasksPath, data, projectRoot, tag);
|
||||
|
||||
// Generate task files if requested
|
||||
if (generateFiles) {
|
||||
log('info', 'Regenerating task files...');
|
||||
// await generateTaskFiles(tasksPath, path.dirname(tasksPath), context);
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath), context);
|
||||
}
|
||||
|
||||
return convertedTask;
|
||||
|
||||
@@ -9,6 +9,8 @@ import taskExists from './task-exists.js';
|
||||
* @param {string} tasksPath - Path to the tasks file
|
||||
* @param {string} taskIds - Comma-separated string of task/subtask IDs to remove (e.g., '5,6.1,7')
|
||||
* @param {Object} context - Context object containing projectRoot and tag information
|
||||
* @param {string} [context.projectRoot] - Project root path
|
||||
* @param {string} [context.tag] - Tag for the task
|
||||
* @returns {Object} Result object with success status, messages, and removed task info
|
||||
*/
|
||||
async function removeTask(tasksPath, taskIds, context = {}) {
|
||||
@@ -32,7 +34,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
|
||||
|
||||
try {
|
||||
// Read the tasks file ONCE before the loop, preserving the full tagged structure
|
||||
const rawData = readJSON(tasksPath, projectRoot); // Read raw data
|
||||
const rawData = readJSON(tasksPath, projectRoot, tag); // Read raw data
|
||||
if (!rawData) {
|
||||
throw new Error(`Could not read tasks file at ${tasksPath}`);
|
||||
}
|
||||
@@ -40,19 +42,18 @@ async function removeTask(tasksPath, taskIds, context = {}) {
|
||||
// Use the full tagged data if available, otherwise use the data as is
|
||||
const fullTaggedData = rawData._rawTaggedData || rawData;
|
||||
|
||||
const currentTag = tag || rawData.tag || 'master';
|
||||
if (!fullTaggedData[currentTag] || !fullTaggedData[currentTag].tasks) {
|
||||
throw new Error(`Tag '${currentTag}' not found or has no tasks.`);
|
||||
if (!fullTaggedData[tag] || !fullTaggedData[tag].tasks) {
|
||||
throw new Error(`Tag '${tag}' not found or has no tasks.`);
|
||||
}
|
||||
|
||||
const tasks = fullTaggedData[currentTag].tasks; // Work with tasks from the correct tag
|
||||
const tasks = fullTaggedData[tag].tasks; // Work with tasks from the correct tag
|
||||
|
||||
const tasksToDeleteFiles = []; // Collect IDs of main tasks whose files should be deleted
|
||||
|
||||
for (const taskId of taskIdsToRemove) {
|
||||
// Check if the task ID exists *before* attempting removal
|
||||
if (!taskExists(tasks, taskId)) {
|
||||
const errorMsg = `Task with ID ${taskId} in tag '${currentTag}' not found or already removed.`;
|
||||
const errorMsg = `Task with ID ${taskId} in tag '${tag}' not found or already removed.`;
|
||||
results.errors.push(errorMsg);
|
||||
results.success = false; // Mark overall success as false if any error occurs
|
||||
continue; // Skip to the next ID
|
||||
@@ -94,7 +95,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
|
||||
parentTask.subtasks.splice(subtaskIndex, 1);
|
||||
|
||||
results.messages.push(
|
||||
`Successfully removed subtask ${taskId} from tag '${currentTag}'`
|
||||
`Successfully removed subtask ${taskId} from tag '${tag}'`
|
||||
);
|
||||
}
|
||||
// Handle main task removal
|
||||
@@ -102,9 +103,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
|
||||
const taskIdNum = parseInt(taskId, 10);
|
||||
const taskIndex = tasks.findIndex((t) => t.id === taskIdNum);
|
||||
if (taskIndex === -1) {
|
||||
throw new Error(
|
||||
`Task with ID ${taskId} not found in tag '${currentTag}'`
|
||||
);
|
||||
throw new Error(`Task with ID ${taskId} not found in tag '${tag}'`);
|
||||
}
|
||||
|
||||
// Store the task info before removal
|
||||
@@ -116,7 +115,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
|
||||
tasks.splice(taskIndex, 1);
|
||||
|
||||
results.messages.push(
|
||||
`Successfully removed task ${taskId} from tag '${currentTag}'`
|
||||
`Successfully removed task ${taskId} from tag '${tag}'`
|
||||
);
|
||||
}
|
||||
} catch (innerError) {
|
||||
@@ -139,7 +138,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
|
||||
);
|
||||
|
||||
// Update the tasks in the current tag of the full data structure
|
||||
fullTaggedData[currentTag].tasks = tasks;
|
||||
fullTaggedData[tag].tasks = tasks;
|
||||
|
||||
// Remove dependencies from all tags
|
||||
for (const tagName in fullTaggedData) {
|
||||
@@ -171,7 +170,7 @@ async function removeTask(tasksPath, taskIds, context = {}) {
|
||||
}
|
||||
|
||||
// Save the updated raw data structure
|
||||
writeJSON(tasksPath, fullTaggedData, projectRoot, currentTag);
|
||||
writeJSON(tasksPath, fullTaggedData, projectRoot, tag);
|
||||
|
||||
// Delete task files AFTER saving tasks.json
|
||||
for (const taskIdNum of tasksToDeleteFiles) {
|
||||
@@ -193,18 +192,18 @@ async function removeTask(tasksPath, taskIds, context = {}) {
|
||||
}
|
||||
|
||||
// Generate updated task files ONCE, with context
|
||||
try {
|
||||
await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
|
||||
projectRoot,
|
||||
tag: currentTag
|
||||
});
|
||||
results.messages.push('Task files regenerated successfully.');
|
||||
} catch (genError) {
|
||||
const genErrMsg = `Failed to regenerate task files: ${genError.message}`;
|
||||
results.errors.push(genErrMsg);
|
||||
results.success = false;
|
||||
log('warn', genErrMsg);
|
||||
}
|
||||
// try {
|
||||
// await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
|
||||
// projectRoot,
|
||||
// tag
|
||||
// });
|
||||
// results.messages.push('Task files regenerated successfully.');
|
||||
// } catch (genError) {
|
||||
// const genErrMsg = `Failed to regenerate task files: ${genError.message}`;
|
||||
// results.errors.push(genErrMsg);
|
||||
// results.success = false;
|
||||
// log('warn', genErrMsg);
|
||||
// }
|
||||
} else if (results.errors.length === 0) {
|
||||
results.messages.push('No tasks found matching the provided IDs.');
|
||||
}
|
||||
|
||||
@@ -35,6 +35,7 @@ import {
|
||||
* @param {boolean} [options.includeProjectTree] - Include project file tree
|
||||
* @param {string} [options.detailLevel] - Detail level: 'low', 'medium', 'high'
|
||||
* @param {string} [options.projectRoot] - Project root directory
|
||||
* @param {string} [options.tag] - Tag for the task
|
||||
* @param {boolean} [options.saveToFile] - Whether to save results to file (MCP mode)
|
||||
* @param {Object} [context] - Execution context
|
||||
* @param {Object} [context.session] - MCP session object
|
||||
@@ -59,6 +60,7 @@ async function performResearch(
|
||||
includeProjectTree = false,
|
||||
detailLevel = 'medium',
|
||||
projectRoot: providedProjectRoot,
|
||||
tag,
|
||||
saveToFile = false
|
||||
} = options;
|
||||
|
||||
@@ -101,7 +103,7 @@ async function performResearch(
|
||||
|
||||
try {
|
||||
// Initialize context gatherer
|
||||
const contextGatherer = new ContextGatherer(projectRoot);
|
||||
const contextGatherer = new ContextGatherer(projectRoot, tag);
|
||||
|
||||
// Auto-discover relevant tasks using fuzzy search to supplement provided tasks
|
||||
let finalTaskIds = [...taskIds]; // Start with explicitly provided tasks
|
||||
@@ -114,7 +116,7 @@ async function performResearch(
|
||||
'tasks',
|
||||
'tasks.json'
|
||||
);
|
||||
const tasksData = await readJSON(tasksPath, projectRoot);
|
||||
const tasksData = await readJSON(tasksPath, projectRoot, tag);
|
||||
|
||||
if (tasksData && tasksData.tasks && tasksData.tasks.length > 0) {
|
||||
// Flatten tasks to include subtasks for fuzzy search
|
||||
@@ -769,10 +771,7 @@ async function handleSaveToTask(
|
||||
return;
|
||||
}
|
||||
|
||||
// Validate ID exists - use tag from context
|
||||
const { getCurrentTag } = await import('../utils.js');
|
||||
const tag = context.tag || getCurrentTag(projectRoot) || 'master';
|
||||
const data = readJSON(tasksPath, projectRoot, tag);
|
||||
const data = readJSON(tasksPath, projectRoot, context.tag);
|
||||
if (!data || !data.tasks) {
|
||||
console.log(chalk.red('❌ No valid tasks found.'));
|
||||
return;
|
||||
@@ -806,7 +805,7 @@ async function handleSaveToTask(
|
||||
trimmedTaskId,
|
||||
conversationThread,
|
||||
false, // useResearch = false for simple append
|
||||
{ ...context, tag },
|
||||
context,
|
||||
'text'
|
||||
);
|
||||
|
||||
@@ -833,7 +832,7 @@ async function handleSaveToTask(
|
||||
taskIdNum,
|
||||
conversationThread,
|
||||
false, // useResearch = false for simple append
|
||||
{ ...context, tag },
|
||||
context,
|
||||
'text',
|
||||
true // appendMode = true
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@ function setResponseLanguage(lang, options = {}) {
|
||||
error: {
|
||||
code: 'CONFIG_MISSING',
|
||||
message:
|
||||
'The configuration file is missing. Run "task-master models --setup" to create it.'
|
||||
'The configuration file is missing. Run "task-master init" to create it.'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@ import {
|
||||
readJSON,
|
||||
writeJSON,
|
||||
findTaskById,
|
||||
getCurrentTag,
|
||||
ensureTagMetadata
|
||||
} from '../utils.js';
|
||||
import { displayBanner } from '../ui.js';
|
||||
@@ -26,16 +25,13 @@ import {
|
||||
* @param {string} taskIdInput - Task ID(s) to update
|
||||
* @param {string} newStatus - New status
|
||||
* @param {Object} options - Additional options (mcpLog for MCP mode, projectRoot for tag resolution)
|
||||
* @param {string} tag - Optional tag to override current tag resolution
|
||||
* @param {string} [options.projectRoot] - Project root path
|
||||
* @param {string} [options.tag] - Optional tag to override current tag resolution
|
||||
* @param {string} [options.mcpLog] - MCP logger object
|
||||
* @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode
|
||||
*/
|
||||
async function setTaskStatus(
|
||||
tasksPath,
|
||||
taskIdInput,
|
||||
newStatus,
|
||||
options = {},
|
||||
tag = null
|
||||
) {
|
||||
async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) {
|
||||
const { projectRoot, tag } = options;
|
||||
try {
|
||||
if (!isValidTaskStatus(newStatus)) {
|
||||
throw new Error(
|
||||
@@ -59,7 +55,7 @@ async function setTaskStatus(
|
||||
log('info', `Reading tasks from ${tasksPath}...`);
|
||||
|
||||
// Read the raw data without tag resolution to preserve tagged structure
|
||||
let rawData = readJSON(tasksPath, options.projectRoot); // No tag parameter
|
||||
let rawData = readJSON(tasksPath, projectRoot, tag); // No tag parameter
|
||||
|
||||
// Handle the case where readJSON returns resolved data with _rawTaggedData
|
||||
if (rawData && rawData._rawTaggedData) {
|
||||
@@ -67,24 +63,17 @@ async function setTaskStatus(
|
||||
rawData = rawData._rawTaggedData;
|
||||
}
|
||||
|
||||
// Determine the current tag
|
||||
const currentTag = tag || getCurrentTag(options.projectRoot) || 'master';
|
||||
|
||||
// Ensure the tag exists in the raw data
|
||||
if (
|
||||
!rawData ||
|
||||
!rawData[currentTag] ||
|
||||
!Array.isArray(rawData[currentTag].tasks)
|
||||
) {
|
||||
if (!rawData || !rawData[tag] || !Array.isArray(rawData[tag].tasks)) {
|
||||
throw new Error(
|
||||
`Invalid tasks file or tag "${currentTag}" not found at ${tasksPath}`
|
||||
`Invalid tasks file or tag "${tag}" not found at ${tasksPath}`
|
||||
);
|
||||
}
|
||||
|
||||
// Get the tasks for the current tag
|
||||
const data = {
|
||||
tasks: rawData[currentTag].tasks,
|
||||
tag: currentTag,
|
||||
tasks: rawData[tag].tasks,
|
||||
tag,
|
||||
_rawTaggedData: rawData
|
||||
};
|
||||
|
||||
@@ -123,16 +112,16 @@ async function setTaskStatus(
|
||||
}
|
||||
|
||||
// Update the raw data structure with the modified tasks
|
||||
rawData[currentTag].tasks = data.tasks;
|
||||
rawData[tag].tasks = data.tasks;
|
||||
|
||||
// Ensure the tag has proper metadata
|
||||
ensureTagMetadata(rawData[currentTag], {
|
||||
description: `Tasks for ${currentTag} context`
|
||||
ensureTagMetadata(rawData[tag], {
|
||||
description: `Tasks for ${tag} context`
|
||||
});
|
||||
|
||||
// Write the updated raw data back to the file
|
||||
// The writeJSON function will automatically filter out _rawTaggedData
|
||||
writeJSON(tasksPath, rawData, options.projectRoot, currentTag);
|
||||
writeJSON(tasksPath, rawData, projectRoot, tag);
|
||||
|
||||
// Validate dependencies after status update
|
||||
log('info', 'Validating dependencies after status update...');
|
||||
|
||||
@@ -17,8 +17,7 @@ import {
|
||||
truncate,
|
||||
isSilentMode,
|
||||
findProjectRoot,
|
||||
flattenTasksWithSubtasks,
|
||||
getCurrentTag
|
||||
flattenTasksWithSubtasks
|
||||
} from '../utils.js';
|
||||
import { generateTextService } from '../ai-services-unified.js';
|
||||
import { getDebugFlag } from '../config-manager.js';
|
||||
@@ -37,6 +36,7 @@ import { FuzzyTaskSearch } from '../utils/fuzzyTaskSearch.js';
|
||||
* @param {Object} [context.session] - Session object from MCP server.
|
||||
* @param {Object} [context.mcpLog] - MCP logger object.
|
||||
* @param {string} [context.projectRoot] - Project root path (needed for AI service key resolution).
|
||||
* @param {string} [context.tag] - Tag for the task
|
||||
* @param {string} [outputFormat='text'] - Output format ('text' or 'json'). Automatically 'json' if mcpLog is present.
|
||||
* @returns {Promise<Object|null>} - The updated subtask or null if update failed.
|
||||
*/
|
||||
@@ -92,10 +92,7 @@ async function updateSubtaskById(
|
||||
throw new Error('Could not determine project root directory');
|
||||
}
|
||||
|
||||
// Determine the tag to use
|
||||
const currentTag = tag || getCurrentTag(projectRoot) || 'master';
|
||||
|
||||
const data = readJSON(tasksPath, projectRoot, currentTag);
|
||||
const data = readJSON(tasksPath, projectRoot, tag);
|
||||
if (!data || !data.tasks) {
|
||||
throw new Error(
|
||||
`No valid tasks found in ${tasksPath}. The file may be corrupted or have an invalid format.`
|
||||
@@ -142,7 +139,7 @@ async function updateSubtaskById(
|
||||
// --- Context Gathering ---
|
||||
let gatheredContext = '';
|
||||
try {
|
||||
const contextGatherer = new ContextGatherer(projectRoot);
|
||||
const contextGatherer = new ContextGatherer(projectRoot, tag);
|
||||
const allTasksFlat = flattenTasksWithSubtasks(data.tasks);
|
||||
const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'update-subtask');
|
||||
const searchQuery = `${parentTask.title} ${subtask.title} ${prompt}`;
|
||||
@@ -331,13 +328,17 @@ async function updateSubtaskById(
|
||||
if (outputFormat === 'text' && getDebugFlag(session)) {
|
||||
console.log('>>> DEBUG: About to call writeJSON with updated data...');
|
||||
}
|
||||
writeJSON(tasksPath, data, projectRoot, currentTag);
|
||||
writeJSON(tasksPath, data, projectRoot, tag);
|
||||
if (outputFormat === 'text' && getDebugFlag(session)) {
|
||||
console.log('>>> DEBUG: writeJSON call completed.');
|
||||
}
|
||||
|
||||
report('success', `Successfully updated subtask ${subtaskId}`);
|
||||
// await generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
||||
// Updated function call to make sure if uncommented it will generate the task files for the updated subtask based on the tag
|
||||
// await generateTaskFiles(tasksPath, path.dirname(tasksPath), {
|
||||
// tag: tag,
|
||||
// projectRoot: projectRoot
|
||||
// });
|
||||
|
||||
if (outputFormat === 'text') {
|
||||
if (loadingIndicator) {
|
||||
|
||||
@@ -12,8 +12,7 @@ import {
|
||||
truncate,
|
||||
isSilentMode,
|
||||
flattenTasksWithSubtasks,
|
||||
findProjectRoot,
|
||||
getCurrentTag
|
||||
findProjectRoot
|
||||
} from '../utils.js';
|
||||
|
||||
import {
|
||||
@@ -190,8 +189,45 @@ function parseUpdatedTaskFromText(text, expectedTaskId, logFn, isMCP) {
|
||||
throw new Error('Parsed AI response is not a valid JSON object.');
|
||||
}
|
||||
|
||||
// Preprocess the task to ensure subtasks have proper structure
|
||||
const preprocessedTask = {
|
||||
...parsedTask,
|
||||
status: parsedTask.status || 'pending',
|
||||
dependencies: Array.isArray(parsedTask.dependencies)
|
||||
? parsedTask.dependencies
|
||||
: [],
|
||||
details:
|
||||
typeof parsedTask.details === 'string'
|
||||
? parsedTask.details
|
||||
: String(parsedTask.details || ''),
|
||||
testStrategy:
|
||||
typeof parsedTask.testStrategy === 'string'
|
||||
? parsedTask.testStrategy
|
||||
: String(parsedTask.testStrategy || ''),
|
||||
// Ensure subtasks is an array and each subtask has required fields
|
||||
subtasks: Array.isArray(parsedTask.subtasks)
|
||||
? parsedTask.subtasks.map((subtask) => ({
|
||||
...subtask,
|
||||
title: subtask.title || '',
|
||||
description: subtask.description || '',
|
||||
status: subtask.status || 'pending',
|
||||
dependencies: Array.isArray(subtask.dependencies)
|
||||
? subtask.dependencies
|
||||
: [],
|
||||
details:
|
||||
typeof subtask.details === 'string'
|
||||
? subtask.details
|
||||
: String(subtask.details || ''),
|
||||
testStrategy:
|
||||
typeof subtask.testStrategy === 'string'
|
||||
? subtask.testStrategy
|
||||
: String(subtask.testStrategy || '')
|
||||
}))
|
||||
: []
|
||||
};
|
||||
|
||||
// Validate the parsed task object using Zod
|
||||
const validationResult = updatedTaskSchema.safeParse(parsedTask);
|
||||
const validationResult = updatedTaskSchema.safeParse(preprocessedTask);
|
||||
if (!validationResult.success) {
|
||||
report('error', 'Parsed task object failed Zod validation.');
|
||||
validationResult.error.errors.forEach((err) => {
|
||||
@@ -225,6 +261,7 @@ function parseUpdatedTaskFromText(text, expectedTaskId, logFn, isMCP) {
|
||||
* @param {Object} [context.session] - Session object from MCP server.
|
||||
* @param {Object} [context.mcpLog] - MCP logger object.
|
||||
* @param {string} [context.projectRoot] - Project root path.
|
||||
* @param {string} [context.tag] - Tag for the task
|
||||
* @param {string} [outputFormat='text'] - Output format ('text' or 'json').
|
||||
* @param {boolean} [appendMode=false] - If true, append to details instead of full update.
|
||||
* @returns {Promise<Object|null>} - The updated task or null if update failed.
|
||||
@@ -283,11 +320,8 @@ async function updateTaskById(
|
||||
throw new Error('Could not determine project root directory');
|
||||
}
|
||||
|
||||
// Determine the tag to use
|
||||
const currentTag = tag || getCurrentTag(projectRoot) || 'master';
|
||||
|
||||
// --- Task Loading and Status Check (Keep existing) ---
|
||||
const data = readJSON(tasksPath, projectRoot, currentTag);
|
||||
const data = readJSON(tasksPath, projectRoot, tag);
|
||||
if (!data || !data.tasks)
|
||||
throw new Error(`No valid tasks found in ${tasksPath}.`);
|
||||
const taskIndex = data.tasks.findIndex((task) => task.id === taskId);
|
||||
@@ -327,7 +361,7 @@ async function updateTaskById(
|
||||
// --- Context Gathering ---
|
||||
let gatheredContext = '';
|
||||
try {
|
||||
const contextGatherer = new ContextGatherer(projectRoot);
|
||||
const contextGatherer = new ContextGatherer(projectRoot, tag);
|
||||
const allTasksFlat = flattenTasksWithSubtasks(data.tasks);
|
||||
const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'update-task');
|
||||
const searchQuery = `${taskToUpdate.title} ${taskToUpdate.description} ${prompt}`;
|
||||
@@ -522,7 +556,7 @@ async function updateTaskById(
|
||||
|
||||
// Write the updated task back to file
|
||||
data.tasks[taskIndex] = taskToUpdate;
|
||||
writeJSON(tasksPath, data, projectRoot, currentTag);
|
||||
writeJSON(tasksPath, data, projectRoot, tag);
|
||||
report('success', `Successfully appended to task ${taskId}`);
|
||||
|
||||
// Display success message for CLI
|
||||
@@ -667,7 +701,7 @@ async function updateTaskById(
|
||||
// --- End Update Task Data ---
|
||||
|
||||
// --- Write File and Generate (Unchanged) ---
|
||||
writeJSON(tasksPath, data, projectRoot, currentTag);
|
||||
writeJSON(tasksPath, data, projectRoot, tag);
|
||||
report('success', `Successfully updated task ${taskId}`);
|
||||
// await generateTaskFiles(tasksPath, path.dirname(tasksPath));
|
||||
// --- End Write File ---
|
||||
|
||||
@@ -9,8 +9,7 @@ import {
|
||||
readJSON,
|
||||
writeJSON,
|
||||
truncate,
|
||||
isSilentMode,
|
||||
getCurrentTag
|
||||
isSilentMode
|
||||
} from '../utils.js';
|
||||
|
||||
import {
|
||||
@@ -43,7 +42,39 @@ const updatedTaskSchema = z
|
||||
subtasks: z.array(z.any()).nullable() // Keep subtasks flexible for now
|
||||
})
|
||||
.strip(); // Allow potential extra fields during parsing if needed, then validate structure
|
||||
|
||||
// Preprocessing schema that adds defaults before validation
|
||||
const preprocessTaskSchema = z.preprocess((task) => {
|
||||
// Ensure task is an object
|
||||
if (typeof task !== 'object' || task === null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
// Return task with defaults for missing fields
|
||||
return {
|
||||
...task,
|
||||
// Add defaults for required fields if missing
|
||||
id: task.id ?? 0,
|
||||
title: task.title ?? 'Untitled Task',
|
||||
description: task.description ?? '',
|
||||
status: task.status ?? 'pending',
|
||||
dependencies: Array.isArray(task.dependencies) ? task.dependencies : [],
|
||||
// Optional fields - preserve undefined/null distinction
|
||||
priority: task.hasOwnProperty('priority') ? task.priority : null,
|
||||
details: task.hasOwnProperty('details') ? task.details : null,
|
||||
testStrategy: task.hasOwnProperty('testStrategy')
|
||||
? task.testStrategy
|
||||
: null,
|
||||
subtasks: Array.isArray(task.subtasks)
|
||||
? task.subtasks
|
||||
: task.subtasks === null
|
||||
? null
|
||||
: []
|
||||
};
|
||||
}, updatedTaskSchema);
|
||||
|
||||
const updatedTaskArraySchema = z.array(updatedTaskSchema);
|
||||
const preprocessedTaskArraySchema = z.array(preprocessTaskSchema);
|
||||
|
||||
/**
|
||||
* Parses an array of task objects from AI's text response.
|
||||
@@ -196,21 +227,50 @@ function parseUpdatedTasksFromText(text, expectedCount, logFn, isMCP) {
|
||||
);
|
||||
}
|
||||
|
||||
const validationResult = updatedTaskArraySchema.safeParse(parsedTasks);
|
||||
if (!validationResult.success) {
|
||||
report('error', 'Parsed task array failed Zod validation.');
|
||||
validationResult.error.errors.forEach((err) => {
|
||||
report('error', ` - Path '${err.path.join('.')}': ${err.message}`);
|
||||
});
|
||||
throw new Error(
|
||||
`AI response failed task structure validation: ${validationResult.error.message}`
|
||||
// Log missing fields for debugging before preprocessing
|
||||
let hasWarnings = false;
|
||||
parsedTasks.forEach((task, index) => {
|
||||
const missingFields = [];
|
||||
if (!task.hasOwnProperty('id')) missingFields.push('id');
|
||||
if (!task.hasOwnProperty('status')) missingFields.push('status');
|
||||
if (!task.hasOwnProperty('dependencies'))
|
||||
missingFields.push('dependencies');
|
||||
|
||||
if (missingFields.length > 0) {
|
||||
hasWarnings = true;
|
||||
report(
|
||||
'warn',
|
||||
`Task ${index} is missing fields: ${missingFields.join(', ')} - will use defaults`
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (hasWarnings) {
|
||||
report(
|
||||
'warn',
|
||||
'Some tasks were missing required fields. Applying defaults...'
|
||||
);
|
||||
}
|
||||
|
||||
report('info', 'Successfully validated task structure.');
|
||||
return validationResult.data.slice(
|
||||
// Use the preprocessing schema to add defaults and validate
|
||||
const preprocessResult = preprocessedTaskArraySchema.safeParse(parsedTasks);
|
||||
|
||||
if (!preprocessResult.success) {
|
||||
// This should rarely happen now since preprocessing adds defaults
|
||||
report('error', 'Failed to validate task array even after preprocessing.');
|
||||
preprocessResult.error.errors.forEach((err) => {
|
||||
report('error', ` - Path '${err.path.join('.')}': ${err.message}`);
|
||||
});
|
||||
|
||||
throw new Error(
|
||||
`AI response failed validation: ${preprocessResult.error.message}`
|
||||
);
|
||||
}
|
||||
|
||||
report('info', 'Successfully validated and transformed task structure.');
|
||||
return preprocessResult.data.slice(
|
||||
0,
|
||||
expectedCount || validationResult.data.length
|
||||
expectedCount || preprocessResult.data.length
|
||||
);
|
||||
}
|
||||
|
||||
@@ -223,8 +283,8 @@ function parseUpdatedTasksFromText(text, expectedCount, logFn, isMCP) {
|
||||
* @param {Object} context - Context object containing session and mcpLog.
|
||||
* @param {Object} [context.session] - Session object from MCP server.
|
||||
* @param {Object} [context.mcpLog] - MCP logger object.
|
||||
* @param {string} [context.tag] - Tag for the task
|
||||
* @param {string} [outputFormat='text'] - Output format ('text' or 'json').
|
||||
* @param {string} [tag=null] - Tag associated with the tasks.
|
||||
*/
|
||||
async function updateTasks(
|
||||
tasksPath,
|
||||
@@ -258,11 +318,8 @@ async function updateTasks(
|
||||
throw new Error('Could not determine project root directory');
|
||||
}
|
||||
|
||||
// Determine the current tag - prioritize explicit tag, then context.tag, then current tag
|
||||
const currentTag = tag || getCurrentTag(projectRoot) || 'master';
|
||||
|
||||
// --- Task Loading/Filtering (Updated to pass projectRoot and tag) ---
|
||||
const data = readJSON(tasksPath, projectRoot, currentTag);
|
||||
const data = readJSON(tasksPath, projectRoot, tag);
|
||||
if (!data || !data.tasks)
|
||||
throw new Error(`No valid tasks found in ${tasksPath}`);
|
||||
const tasksToUpdate = data.tasks.filter(
|
||||
@@ -281,7 +338,7 @@ async function updateTasks(
|
||||
// --- Context Gathering ---
|
||||
let gatheredContext = '';
|
||||
try {
|
||||
const contextGatherer = new ContextGatherer(projectRoot);
|
||||
const contextGatherer = new ContextGatherer(projectRoot, tag);
|
||||
const allTasksFlat = flattenTasksWithSubtasks(data.tasks);
|
||||
const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'update');
|
||||
const searchResults = fuzzySearch.findRelevantTasks(prompt, {
|
||||
@@ -442,7 +499,17 @@ async function updateTasks(
|
||||
data.tasks.forEach((task, index) => {
|
||||
if (updatedTasksMap.has(task.id)) {
|
||||
// Only update if the task was part of the set sent to AI
|
||||
data.tasks[index] = updatedTasksMap.get(task.id);
|
||||
const updatedTask = updatedTasksMap.get(task.id);
|
||||
// Merge the updated task with the existing one to preserve fields like subtasks
|
||||
data.tasks[index] = {
|
||||
...task, // Keep all existing fields
|
||||
...updatedTask, // Override with updated fields
|
||||
// Ensure subtasks field is preserved if not provided by AI
|
||||
subtasks:
|
||||
updatedTask.subtasks !== undefined
|
||||
? updatedTask.subtasks
|
||||
: task.subtasks
|
||||
};
|
||||
actualUpdateCount++;
|
||||
}
|
||||
});
|
||||
@@ -457,7 +524,7 @@ async function updateTasks(
|
||||
);
|
||||
|
||||
// Fix: Pass projectRoot and currentTag to writeJSON
|
||||
writeJSON(tasksPath, data, projectRoot, currentTag);
|
||||
writeJSON(tasksPath, data, projectRoot, tag);
|
||||
if (isMCP)
|
||||
logFn.info(
|
||||
`Successfully updated ${actualUpdateCount} tasks in ${tasksPath}`
|
||||
|
||||
@@ -1197,18 +1197,18 @@ async function displayNextTask(
|
||||
* @param {string|number} taskId - The ID of the task to display
|
||||
* @param {string} complexityReportPath - Path to the complexity report file
|
||||
* @param {string} [statusFilter] - Optional status to filter subtasks by
|
||||
* @param {string} tag - Optional tag to override current tag resolution
|
||||
* @param {object} context - Context object containing projectRoot and tag
|
||||
* @param {string} context.projectRoot - Project root path
|
||||
* @param {string} context.tag - Tag for the task
|
||||
*/
|
||||
async function displayTaskById(
|
||||
tasksPath,
|
||||
taskId,
|
||||
complexityReportPath = null,
|
||||
statusFilter = null,
|
||||
tag = null,
|
||||
context = {}
|
||||
) {
|
||||
// Extract projectRoot from context
|
||||
const projectRoot = context.projectRoot || null;
|
||||
const { projectRoot, tag } = context;
|
||||
|
||||
// Read the tasks file with proper projectRoot for tag resolution
|
||||
const data = readJSON(tasksPath, projectRoot, tag);
|
||||
@@ -2251,7 +2251,9 @@ function displayAiUsageSummary(telemetryData, outputType = 'cli') {
|
||||
* @param {Array<string>} taskIds - Array of task IDs to display
|
||||
* @param {string} complexityReportPath - Path to complexity report
|
||||
* @param {string} statusFilter - Optional status filter for subtasks
|
||||
* @param {Object} context - Optional context object containing projectRoot and tag
|
||||
* @param {Object} context - Context object containing projectRoot and tag
|
||||
* @param {string} [context.projectRoot] - Project root path
|
||||
* @param {string} [context.tag] - Tag for the task
|
||||
*/
|
||||
async function displayMultipleTasksSummary(
|
||||
tasksPath,
|
||||
@@ -2602,7 +2604,6 @@ async function displayMultipleTasksSummary(
|
||||
choice.trim(),
|
||||
complexityReportPath,
|
||||
statusFilter,
|
||||
tag,
|
||||
context
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1190,6 +1190,7 @@ function aggregateTelemetry(telemetryArray, overallCommandName) {
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use TaskMaster.getCurrentTag() instead
|
||||
* Gets the current tag from state.json or falls back to defaultTag from config
|
||||
* @param {string} projectRoot - The project root directory (required)
|
||||
* @returns {string} The current tag name
|
||||
|
||||
@@ -21,7 +21,7 @@ const { encode } = pkg;
|
||||
* Context Gatherer class for collecting and formatting context from various sources
|
||||
*/
|
||||
export class ContextGatherer {
|
||||
constructor(projectRoot) {
|
||||
constructor(projectRoot, tag) {
|
||||
this.projectRoot = projectRoot;
|
||||
this.tasksPath = path.join(
|
||||
projectRoot,
|
||||
@@ -29,12 +29,13 @@ export class ContextGatherer {
|
||||
'tasks',
|
||||
'tasks.json'
|
||||
);
|
||||
this.tag = tag;
|
||||
this.allTasks = this._loadAllTasks();
|
||||
}
|
||||
|
||||
_loadAllTasks() {
|
||||
try {
|
||||
const data = readJSON(this.tasksPath, this.projectRoot);
|
||||
const data = readJSON(this.tasksPath, this.projectRoot, this.tag);
|
||||
const tasks = data?.tasks || [];
|
||||
return tasks;
|
||||
} catch (error) {
|
||||
@@ -958,10 +959,15 @@ export class ContextGatherer {
|
||||
/**
|
||||
* Factory function to create a context gatherer instance
|
||||
* @param {string} projectRoot - Project root directory
|
||||
* @param {string} tag - Tag for the task
|
||||
* @returns {ContextGatherer} Context gatherer instance
|
||||
* @throws {Error} If tag is not provided
|
||||
*/
|
||||
export function createContextGatherer(projectRoot) {
|
||||
return new ContextGatherer(projectRoot);
|
||||
export function createContextGatherer(projectRoot, tag) {
|
||||
if (!tag) {
|
||||
throw new Error('Tag is required');
|
||||
}
|
||||
return new ContextGatherer(projectRoot, tag);
|
||||
}
|
||||
|
||||
export default ContextGatherer;
|
||||
|
||||
Reference in New Issue
Block a user