fix(tasks): ensure list tasks triggers silent migration if necessary.
This commit is contained in:
@@ -1249,7 +1249,14 @@ function registerCommands(programInstance) {
|
||||
chalk.blue(`Setting status of task(s) ${taskId} to: ${status}`)
|
||||
);
|
||||
|
||||
await setTaskStatus(tasksPath, taskId, status);
|
||||
// Find project root for tag resolution
|
||||
const projectRoot = findProjectRoot();
|
||||
if (!projectRoot) {
|
||||
console.error(chalk.red('Error: Could not find project root.'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
await setTaskStatus(tasksPath, taskId, status, { projectRoot });
|
||||
});
|
||||
|
||||
// list command
|
||||
@@ -1269,6 +1276,12 @@ function registerCommands(programInstance) {
|
||||
.option('-s, --status <status>', 'Filter by status')
|
||||
.option('--with-subtasks', 'Show subtasks for each task')
|
||||
.action(async (options) => {
|
||||
const projectRoot = findProjectRoot();
|
||||
if (!projectRoot) {
|
||||
console.error(chalk.red('Error: Could not find project root.'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const tasksPath = options.file || TASKMASTER_TASKS_FILE;
|
||||
const reportPath = options.report;
|
||||
const statusFilter = options.status;
|
||||
@@ -1282,7 +1295,15 @@ function registerCommands(programInstance) {
|
||||
console.log(chalk.blue('Including subtasks in listing'));
|
||||
}
|
||||
|
||||
await listTasks(tasksPath, statusFilter, reportPath, withSubtasks);
|
||||
await listTasks(
|
||||
tasksPath,
|
||||
statusFilter,
|
||||
reportPath,
|
||||
withSubtasks,
|
||||
'text',
|
||||
null,
|
||||
{ projectRoot }
|
||||
);
|
||||
});
|
||||
|
||||
// expand command
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
* @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
|
||||
* @returns {Object} - Task list result for json format
|
||||
*/
|
||||
function listTasks(
|
||||
@@ -35,10 +36,13 @@ function listTasks(
|
||||
reportPath = null,
|
||||
withSubtasks = false,
|
||||
outputFormat = 'text',
|
||||
tag = null
|
||||
tag = null,
|
||||
context = {}
|
||||
) {
|
||||
try {
|
||||
const data = readJSON(tasksPath, null, tag); // Pass tag to readJSON
|
||||
// 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}`);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,14 @@ import path from 'path';
|
||||
import chalk from 'chalk';
|
||||
import boxen from 'boxen';
|
||||
|
||||
import { log, readJSON, writeJSON, findTaskById } from '../utils.js';
|
||||
import {
|
||||
log,
|
||||
readJSON,
|
||||
writeJSON,
|
||||
findTaskById,
|
||||
getCurrentTag,
|
||||
ensureTagMetadata
|
||||
} from '../utils.js';
|
||||
import { displayBanner } from '../ui.js';
|
||||
import { validateTaskDependencies } from '../dependency-manager.js';
|
||||
import { getDebugFlag } from '../config-manager.js';
|
||||
@@ -18,7 +25,7 @@ import {
|
||||
* @param {string} tasksPath - Path to the tasks.json file
|
||||
* @param {string} taskIdInput - Task ID(s) to update
|
||||
* @param {string} newStatus - New status
|
||||
* @param {Object} options - Additional options (mcpLog for MCP mode)
|
||||
* @param {Object} options - Additional options (mcpLog for MCP mode, projectRoot for tag resolution)
|
||||
* @param {string} tag - Optional tag to override current tag resolution
|
||||
* @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode
|
||||
*/
|
||||
@@ -50,7 +57,37 @@ async function setTaskStatus(
|
||||
}
|
||||
|
||||
log('info', `Reading tasks from ${tasksPath}...`);
|
||||
const data = readJSON(tasksPath, null, tag);
|
||||
|
||||
// Read the raw data without tag resolution to preserve tagged structure
|
||||
let rawData = readJSON(tasksPath, options.projectRoot); // No tag parameter
|
||||
|
||||
// Handle the case where readJSON returns resolved data with _rawTaggedData
|
||||
if (rawData && rawData._rawTaggedData) {
|
||||
// Use the raw tagged data and discard the resolved view
|
||||
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)
|
||||
) {
|
||||
throw new Error(
|
||||
`Invalid tasks file or tag "${currentTag}" not found at ${tasksPath}`
|
||||
);
|
||||
}
|
||||
|
||||
// Get the tasks for the current tag
|
||||
const data = {
|
||||
tasks: rawData[currentTag].tasks,
|
||||
tag: currentTag,
|
||||
_rawTaggedData: rawData
|
||||
};
|
||||
|
||||
if (!data || !data.tasks) {
|
||||
throw new Error(`No valid tasks found in ${tasksPath}`);
|
||||
}
|
||||
@@ -65,8 +102,17 @@ async function setTaskStatus(
|
||||
updatedTasks.push(id);
|
||||
}
|
||||
|
||||
// Write the updated tasks to the file
|
||||
writeJSON(tasksPath, data);
|
||||
// Update the raw data structure with the modified tasks
|
||||
rawData[currentTag].tasks = data.tasks;
|
||||
|
||||
// Ensure the tag has proper metadata
|
||||
ensureTagMetadata(rawData[currentTag], {
|
||||
description: `Tasks for ${currentTag} context`
|
||||
});
|
||||
|
||||
// Write the updated raw data back to the file
|
||||
// The writeJSON function will automatically filter out _rawTaggedData
|
||||
writeJSON(tasksPath, rawData);
|
||||
|
||||
// Validate dependencies after status update
|
||||
log('info', 'Validating dependencies after status update...');
|
||||
|
||||
@@ -234,14 +234,55 @@ function readJSON(filepath, projectRoot = null, tag = null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If it's not a tasks.json file or already in legacy format, return as-is
|
||||
if (!filepath.includes('tasks.json') || !data || Array.isArray(data.tasks)) {
|
||||
// If it's not a tasks.json file, return as-is
|
||||
if (!filepath.includes('tasks.json') || !data) {
|
||||
if (isDebug) {
|
||||
console.log(`File is not tagged format or is legacy, returning as-is`);
|
||||
console.log(`File is not tasks.json or data is null, returning as-is`);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
// Check if this is legacy format that needs migration
|
||||
if (Array.isArray(data.tasks)) {
|
||||
if (isDebug) {
|
||||
console.log(`File is in legacy format, performing migration...`);
|
||||
}
|
||||
|
||||
// This is legacy format - migrate it to tagged format
|
||||
const migratedData = {
|
||||
master: {
|
||||
tasks: data.tasks,
|
||||
metadata: data.metadata || {
|
||||
created: new Date().toISOString(),
|
||||
updated: new Date().toISOString(),
|
||||
description: 'Tasks for master context'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Write the migrated data back to the file
|
||||
try {
|
||||
writeJSON(filepath, migratedData);
|
||||
if (isDebug) {
|
||||
console.log(`Successfully migrated legacy format to tagged format`);
|
||||
}
|
||||
|
||||
// Perform complete migration (config.json, state.json)
|
||||
performCompleteTagMigration(filepath);
|
||||
|
||||
// Mark for migration notice
|
||||
markMigrationForNotice(filepath);
|
||||
} catch (writeError) {
|
||||
if (isDebug) {
|
||||
console.log(`Error writing migrated data: ${writeError.message}`);
|
||||
}
|
||||
// If write fails, continue with the original data
|
||||
}
|
||||
|
||||
// Continue processing with the migrated data structure
|
||||
data = migratedData;
|
||||
}
|
||||
|
||||
// If we have tagged data, we need to resolve which tag to use
|
||||
if (typeof data === 'object' && !data.tasks) {
|
||||
// This is tagged format
|
||||
|
||||
Reference in New Issue
Block a user