fix(tasks): ensure list tasks triggers silent migration if necessary.

This commit is contained in:
Eyal Toledano
2025-06-12 18:27:49 -04:00
parent 9efbd38f10
commit b28479a09d
7 changed files with 130 additions and 18 deletions

View File

@@ -164,13 +164,13 @@ STATE MANAGEMENT: Updated scripts/init.js to create state.json during initializa
### Details:
Test all workflows for legacy users and ensure no regressions.
## 9. Update Documentation and Help Menus [pending]
## 9. Update Documentation and Help Menus [done]
### Dependencies: 103.4, 103.5, 103.6, 103.8
### Description: Revise user documentation, migration notes, and CLI help menus to reflect new tag-related features and usage patterns, specifically documenting the add-tag command.
### Details:
Provide clear instructions and examples for all tag management features, ensuring add-tag command is properly documented with consistent naming.
## 10. Conduct Comprehensive System Testing and QA [pending]
## 10. Conduct Comprehensive System Testing and QA [done]
### Dependencies: 103.8, 103.9
### Description: Perform end-to-end testing of the tagged task lists system, including migration, tag management, task operations, and context switching.
### Details:

View File

@@ -1,4 +1,4 @@
# Task ID: 106
# Task ID: 105
# Title: Implement Fun Easter Egg Commands for Developer Delight
# Status: pending
# Dependencies: 2, 4

View File

@@ -6587,7 +6587,7 @@
8
],
"details": "Provide clear instructions and examples for all tag management features, ensuring add-tag command is properly documented with consistent naming.",
"status": "pending",
"status": "done",
"testStrategy": "Review documentation for completeness and clarity; user acceptance testing."
},
{
@@ -6599,7 +6599,7 @@
9
],
"details": "Ensure all features work as intended and meet quality standards, with specific focus on add-tag command functionality.",
"status": "pending",
"status": "done",
"testStrategy": "Execute test cases covering all user scenarios, including edge cases and error handling."
},
{
@@ -6710,7 +6710,7 @@
]
},
{
"id": 106,
"id": 105,
"title": "Implement Fun Easter Egg Commands for Developer Delight",
"description": "Add playful easter egg commands to the CLI that provide entertainment and stress relief for developers while maintaining the professional nature of the tool.",
"details": "## Core Problem Statement\n\nDevelopers often work long hours and need moments of levity to maintain productivity and morale. Adding fun, non-intrusive easter egg commands can:\n\n1. **Boost Developer Morale**: Provide moments of humor and surprise during intense work sessions\n2. **Showcase Tool Personality**: Give Task Master a friendly, approachable character\n3. **Create Community Engagement**: Fun features often become talking points and increase tool adoption\n4. **Stress Relief**: Offer quick mental breaks without leaving the development environment\n\n## Implementation Approach\n\n1. **Add Easter Egg Commands**: Implement hidden/fun commands in commands.js:\n - `fortune` - Display random programming wisdom or motivational quotes\n - `joke` - Show developer-friendly programming jokes\n - `zen` - Display programming zen principles (like Python's zen)\n - `coffee` - ASCII art coffee cup with brewing animation\n - `rubber-duck` - Rubber duck debugging assistant with encouraging messages\n - `praise` - Random praise messages for completed tasks\n\n2. **Command Structure**: Follow existing CLI patterns but make commands discoverable through:\n - Hidden help section (accessible via `--fun` flag on help command)\n - Occasional hints when users complete milestones or long work sessions\n\n3. **Content Management**: Create a separate `easter-eggs.js` module containing:\n - Arrays of quotes, jokes, zen principles\n - ASCII art templates\n - Motivational messages\n - Randomization logic for content selection\n\n4. **Integration Points**:\n - Add subtle hints after task completions (\"Try 'task coffee' for a break!\")\n - Include fun stats in status displays (e.g., \"You've completed X tasks - that deserves a joke!\")\n - Optional daily/weekly fun fact notifications\n\n5. **Configuration**: Add optional config settings:\n - `enableEasterEggs` (default: true)\n - `funNotificationFrequency` (never, rare, occasional, frequent)\n - `favoriteEasterEgg` for personalized defaults\n\n6. **ASCII Art and Animations**: Implement simple text-based animations:\n - Coffee brewing progress bars\n - Rubber duck \"thinking\" animations\n - Celebration ASCII art for major milestones",

View File

@@ -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

View File

@@ -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}`);
}

View File

@@ -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...');

View File

@@ -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