From bf2053e14097e07c22ac88fe975f3c7331471f35 Mon Sep 17 00:00:00 2001 From: Eyal Toledano Date: Sat, 7 Jun 2025 12:57:45 -0400 Subject: [PATCH 01/10] feat(ui): replace emoji complexity indicators with clean filled circle characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace ๐ŸŸข, ๐ŸŸก, ๐Ÿ”ด emojis with โ— character in getComplexityWithColor function Update corresponding unit tests to expect โ— instead of emojis Improves UI continuity --- .taskmaster/tasks/task_095.txt | 6 +- .taskmaster/tasks/tasks.json | 6 +- scripts/modules/ui.js | 3510 ++++++++++++++++---------------- tests/unit/ui.test.js | 384 ++-- 4 files changed, 1953 insertions(+), 1953 deletions(-) diff --git a/.taskmaster/tasks/task_095.txt b/.taskmaster/tasks/task_095.txt index 39038951..fec48871 100644 --- a/.taskmaster/tasks/task_095.txt +++ b/.taskmaster/tasks/task_095.txt @@ -1,6 +1,6 @@ # Task ID: 95 # Title: Implement .taskmaster Directory Structure -# Status: in-progress +# Status: done # Dependencies: 1, 3, 4, 17 # Priority: high # Description: Consolidate all Task Master-managed files in user projects into a clean, centralized .taskmaster/ directory structure to improve organization and keep user project directories clean, based on GitHub issue #275. @@ -105,7 +105,7 @@ Update Task Master's file handling code to use the new paths: tasks in .taskmast ### Details: Update the task file generation system to create and read task files from .taskmaster/tasks/ instead of tasks/. Ensure all template paths are updated. Modify any path resolution logic specific to task file handling. -## 4. Implement backward compatibility logic [in-progress] +## 4. Implement backward compatibility logic [done] ### Dependencies: 95.2, 95.3 ### Description: Add fallback mechanisms to support both old and new file locations during transition ### Details: @@ -141,7 +141,7 @@ Update README.md and other documentation to reflect the new .taskmaster structur ### Details: Create functionality to support user-defined templates in .taskmaster/templates/. Allow users to store custom task templates, PRD templates, or other reusable files. Update Task Master commands to recognize and use templates from this directory when available. -## 10. Verify clean user project directories [in-progress] +## 10. Verify clean user project directories [done] ### Dependencies: 95.8, 95.9 ### Description: Ensure the new structure keeps user project root directories clean and organized ### Details: diff --git a/.taskmaster/tasks/tasks.json b/.taskmaster/tasks/tasks.json index 4553f8cc..797456da 100644 --- a/.taskmaster/tasks/tasks.json +++ b/.taskmaster/tasks/tasks.json @@ -5681,7 +5681,7 @@ "id": 95, "title": "Implement .taskmaster Directory Structure", "description": "Consolidate all Task Master-managed files in user projects into a clean, centralized .taskmaster/ directory structure to improve organization and keep user project directories clean, based on GitHub issue #275.", - "status": "in-progress", + "status": "done", "dependencies": [ 1, 3, @@ -5732,7 +5732,7 @@ 3 ], "details": "Implement path fallback logic that checks both old and new locations when files aren't found. Add deprecation warnings when old paths are used, informing users about the new structure. Ensure error messages are clear about the transition.", - "status": "in-progress", + "status": "done", "testStrategy": "Test with both old and new directory structures to verify fallback works correctly. Verify deprecation warnings appear when using old paths." }, { @@ -5804,7 +5804,7 @@ 9 ], "details": "Validate that after implementing the new structure, user project root directories only contain their actual project files plus the single .taskmaster/ directory. Verify that no Task Master files are created outside of .taskmaster/. Test that users can easily add .taskmaster/ to .gitignore if they choose to exclude Task Master files from version control.", - "status": "in-progress", + "status": "done", "testStrategy": "Test complete workflows and verify only .taskmaster/ directory is created in project root. Check that all Task Master operations respect the new file organization. Verify .gitignore compatibility." } ] diff --git a/scripts/modules/ui.js b/scripts/modules/ui.js index c17d6b80..1118f8c7 100644 --- a/scripts/modules/ui.js +++ b/scripts/modules/ui.js @@ -3,73 +3,73 @@ * User interface functions for the Task Master CLI */ -import chalk from 'chalk'; -import figlet from 'figlet'; -import boxen from 'boxen'; -import ora from 'ora'; -import Table from 'cli-table3'; -import gradient from 'gradient-string'; +import chalk from "chalk"; +import figlet from "figlet"; +import boxen from "boxen"; +import ora from "ora"; +import Table from "cli-table3"; +import gradient from "gradient-string"; import { - log, - findTaskById, - readJSON, - truncate, - isSilentMode -} from './utils.js'; -import fs from 'fs'; + log, + findTaskById, + readJSON, + truncate, + isSilentMode, +} from "./utils.js"; +import fs from "fs"; import { - findNextTask, - analyzeTaskComplexity, - readComplexityReport -} from './task-manager.js'; -import { getProjectName, getDefaultSubtasks } from './config-manager.js'; -import { TASK_STATUS_OPTIONS } from '../../src/constants/task-status.js'; + findNextTask, + analyzeTaskComplexity, + readComplexityReport, +} from "./task-manager.js"; +import { getProjectName, getDefaultSubtasks } from "./config-manager.js"; +import { TASK_STATUS_OPTIONS } from "../../src/constants/task-status.js"; import { - TASKMASTER_CONFIG_FILE, - TASKMASTER_TASKS_FILE -} from '../../src/constants/paths.js'; -import { getTaskMasterVersion } from '../../src/utils/getVersion.js'; + TASKMASTER_CONFIG_FILE, + TASKMASTER_TASKS_FILE, +} from "../../src/constants/paths.js"; +import { getTaskMasterVersion } from "../../src/utils/getVersion.js"; // Create a color gradient for the banner -const coolGradient = gradient(['#00b4d8', '#0077b6', '#03045e']); -const warmGradient = gradient(['#fb8b24', '#e36414', '#9a031e']); +const coolGradient = gradient(["#00b4d8", "#0077b6", "#03045e"]); +const warmGradient = gradient(["#fb8b24", "#e36414", "#9a031e"]); /** * Display a fancy banner for the CLI */ function displayBanner() { - if (isSilentMode()) return; + if (isSilentMode()) return; - console.clear(); - const bannerText = figlet.textSync('Task Master', { - font: 'Standard', - horizontalLayout: 'default', - verticalLayout: 'default' - }); + console.clear(); + const bannerText = figlet.textSync("Task Master", { + font: "Standard", + horizontalLayout: "default", + verticalLayout: "default", + }); - console.log(coolGradient(bannerText)); + console.log(coolGradient(bannerText)); - // Add creator credit line below the banner - console.log( - chalk.dim('by ') + chalk.cyan.underline('https://x.com/eyaltoledano') - ); + // Add creator credit line below the banner + console.log( + chalk.dim("by ") + chalk.cyan.underline("https://x.com/eyaltoledano") + ); - // Read version directly from package.json - const version = getTaskMasterVersion(); + // Read version directly from package.json + const version = getTaskMasterVersion(); - console.log( - boxen( - chalk.white( - `${chalk.bold('Version:')} ${version} ${chalk.bold('Project:')} ${getProjectName(null)}` - ), - { - padding: 1, - margin: { top: 0, bottom: 1 }, - borderStyle: 'round', - borderColor: 'cyan' - } - ) - ); + console.log( + boxen( + chalk.white( + `${chalk.bold("Version:")} ${version} ${chalk.bold("Project:")} ${getProjectName(null)}` + ), + { + padding: 1, + margin: { top: 0, bottom: 1 }, + borderStyle: "round", + borderColor: "cyan", + } + ) + ); } /** @@ -78,12 +78,12 @@ function displayBanner() { * @returns {Object} Spinner object */ function startLoadingIndicator(message) { - const spinner = ora({ - text: message, - color: 'cyan' - }).start(); + const spinner = ora({ + text: message, + color: "cyan", + }).start(); - return spinner; + return spinner; } /** @@ -91,9 +91,9 @@ function startLoadingIndicator(message) { * @param {Object} spinner - Spinner object to stop */ function stopLoadingIndicator(spinner) { - if (spinner && spinner.stop) { - spinner.stop(); - } + if (spinner && spinner.stop) { + spinner.stop(); + } } /** @@ -104,120 +104,120 @@ function stopLoadingIndicator(spinner) { * @returns {string} The formatted progress bar */ function createProgressBar(percent, length = 30, statusBreakdown = null) { - // Adjust the percent to treat deferred and cancelled as complete - const effectivePercent = statusBreakdown - ? Math.min( - 100, - percent + - (statusBreakdown.deferred || 0) + - (statusBreakdown.cancelled || 0) - ) - : percent; + // Adjust the percent to treat deferred and cancelled as complete + const effectivePercent = statusBreakdown + ? Math.min( + 100, + percent + + (statusBreakdown.deferred || 0) + + (statusBreakdown.cancelled || 0) + ) + : percent; - // Calculate how many characters to fill for "true completion" - const trueCompletedFilled = Math.round((percent * length) / 100); + // Calculate how many characters to fill for "true completion" + const trueCompletedFilled = Math.round((percent * length) / 100); - // Calculate how many characters to fill for "effective completion" (including deferred/cancelled) - const effectiveCompletedFilled = Math.round( - (effectivePercent * length) / 100 - ); + // Calculate how many characters to fill for "effective completion" (including deferred/cancelled) + const effectiveCompletedFilled = Math.round( + (effectivePercent * length) / 100 + ); - // The "deferred/cancelled" section (difference between true and effective) - const deferredCancelledFilled = - effectiveCompletedFilled - trueCompletedFilled; + // The "deferred/cancelled" section (difference between true and effective) + const deferredCancelledFilled = + effectiveCompletedFilled - trueCompletedFilled; - // Set the empty section (remaining after effective completion) - const empty = length - effectiveCompletedFilled; + // Set the empty section (remaining after effective completion) + const empty = length - effectiveCompletedFilled; - // Determine color based on percentage for the completed section - let completedColor; - if (percent < 25) { - completedColor = chalk.red; - } else if (percent < 50) { - completedColor = chalk.hex('#FFA500'); // Orange - } else if (percent < 75) { - completedColor = chalk.yellow; - } else if (percent < 100) { - completedColor = chalk.green; - } else { - completedColor = chalk.hex('#006400'); // Dark green - } + // Determine color based on percentage for the completed section + let completedColor; + if (percent < 25) { + completedColor = chalk.red; + } else if (percent < 50) { + completedColor = chalk.hex("#FFA500"); // Orange + } else if (percent < 75) { + completedColor = chalk.yellow; + } else if (percent < 100) { + completedColor = chalk.green; + } else { + completedColor = chalk.hex("#006400"); // Dark green + } - // Create colored sections - const completedSection = completedColor('โ–ˆ'.repeat(trueCompletedFilled)); + // Create colored sections + const completedSection = completedColor("โ–ˆ".repeat(trueCompletedFilled)); - // Gray section for deferred/cancelled items - const deferredCancelledSection = chalk.gray( - 'โ–ˆ'.repeat(deferredCancelledFilled) - ); + // Gray section for deferred/cancelled items + const deferredCancelledSection = chalk.gray( + "โ–ˆ".repeat(deferredCancelledFilled) + ); - // If we have a status breakdown, create a multi-colored remaining section - let remainingSection = ''; + // If we have a status breakdown, create a multi-colored remaining section + let remainingSection = ""; - if (statusBreakdown && empty > 0) { - // Status colors (matching the statusConfig colors in getStatusWithColor) - const statusColors = { - pending: chalk.yellow, - 'in-progress': chalk.hex('#FFA500'), // Orange - blocked: chalk.red, - review: chalk.magenta - // Deferred and cancelled are treated as part of the completed section - }; + if (statusBreakdown && empty > 0) { + // Status colors (matching the statusConfig colors in getStatusWithColor) + const statusColors = { + pending: chalk.yellow, + "in-progress": chalk.hex("#FFA500"), // Orange + blocked: chalk.red, + review: chalk.magenta, + // Deferred and cancelled are treated as part of the completed section + }; - // Calculate proportions for each status - const totalRemaining = Object.entries(statusBreakdown) - .filter( - ([status]) => - !['deferred', 'cancelled', 'done', 'completed'].includes(status) - ) - .reduce((sum, [_, val]) => sum + val, 0); + // Calculate proportions for each status + const totalRemaining = Object.entries(statusBreakdown) + .filter( + ([status]) => + !["deferred", "cancelled", "done", "completed"].includes(status) + ) + .reduce((sum, [_, val]) => sum + val, 0); - // If no remaining tasks with tracked statuses, just use gray - if (totalRemaining <= 0) { - remainingSection = chalk.gray('โ–‘'.repeat(empty)); - } else { - // Track how many characters we've added - let addedChars = 0; + // If no remaining tasks with tracked statuses, just use gray + if (totalRemaining <= 0) { + remainingSection = chalk.gray("โ–‘".repeat(empty)); + } else { + // Track how many characters we've added + let addedChars = 0; - // Add each status section proportionally - for (const [status, percentage] of Object.entries(statusBreakdown)) { - // Skip statuses that are considered complete - if (['deferred', 'cancelled', 'done', 'completed'].includes(status)) - continue; + // Add each status section proportionally + for (const [status, percentage] of Object.entries(statusBreakdown)) { + // Skip statuses that are considered complete + if (["deferred", "cancelled", "done", "completed"].includes(status)) + continue; - // Calculate how many characters this status should fill - const statusChars = Math.round((percentage / totalRemaining) * empty); + // Calculate how many characters this status should fill + const statusChars = Math.round((percentage / totalRemaining) * empty); - // Make sure we don't exceed the total length due to rounding - const actualChars = Math.min(statusChars, empty - addedChars); + // Make sure we don't exceed the total length due to rounding + const actualChars = Math.min(statusChars, empty - addedChars); - // Add colored section for this status - const colorFn = statusColors[status] || chalk.gray; - remainingSection += colorFn('โ–‘'.repeat(actualChars)); + // Add colored section for this status + const colorFn = statusColors[status] || chalk.gray; + remainingSection += colorFn("โ–‘".repeat(actualChars)); - addedChars += actualChars; - } + addedChars += actualChars; + } - // If we have any remaining space due to rounding, fill with gray - if (addedChars < empty) { - remainingSection += chalk.gray('โ–‘'.repeat(empty - addedChars)); - } - } - } else { - // Default to gray for the empty section if no breakdown provided - remainingSection = chalk.gray('โ–‘'.repeat(empty)); - } + // If we have any remaining space due to rounding, fill with gray + if (addedChars < empty) { + remainingSection += chalk.gray("โ–‘".repeat(empty - addedChars)); + } + } + } else { + // Default to gray for the empty section if no breakdown provided + remainingSection = chalk.gray("โ–‘".repeat(empty)); + } - // Effective percentage text color should reflect the highest category - const percentTextColor = - percent === 100 - ? chalk.hex('#006400') // Dark green for 100% - : effectivePercent === 100 - ? chalk.gray // Gray for 100% with deferred/cancelled - : completedColor; // Otherwise match the completed color + // Effective percentage text color should reflect the highest category + const percentTextColor = + percent === 100 + ? chalk.hex("#006400") // Dark green for 100% + : effectivePercent === 100 + ? chalk.gray // Gray for 100% with deferred/cancelled + : completedColor; // Otherwise match the completed color - // Build the complete progress bar - return `${completedSection}${deferredCancelledSection}${remainingSection} ${percentTextColor(`${effectivePercent.toFixed(0)}%`)}`; + // Build the complete progress bar + return `${completedSection}${deferredCancelledSection}${remainingSection} ${percentTextColor(`${effectivePercent.toFixed(0)}%`)}`; } /** @@ -227,44 +227,44 @@ function createProgressBar(percent, length = 30, statusBreakdown = null) { * @returns {string} Colored status string */ function getStatusWithColor(status, forTable = false) { - if (!status) { - return chalk.gray('โ“ unknown'); - } + if (!status) { + return chalk.gray("โ“ unknown"); + } - const statusConfig = { - done: { color: chalk.green, icon: 'โœ…', tableIcon: 'โœ“' }, - completed: { color: chalk.green, icon: 'โœ…', tableIcon: 'โœ“' }, - pending: { color: chalk.yellow, icon: 'โฑ๏ธ', tableIcon: 'โฑ' }, - 'in-progress': { color: chalk.hex('#FFA500'), icon: '๐Ÿ”„', tableIcon: 'โ–บ' }, - deferred: { color: chalk.gray, icon: 'โฑ๏ธ', tableIcon: 'โฑ' }, - blocked: { color: chalk.red, icon: 'โŒ', tableIcon: 'โœ—' }, - review: { color: chalk.magenta, icon: '๐Ÿ‘€', tableIcon: '๐Ÿ‘' }, - cancelled: { color: chalk.gray, icon: 'โŒ', tableIcon: 'โœ—' } - }; + const statusConfig = { + done: { color: chalk.green, icon: "โœ…", tableIcon: "โœ“" }, + completed: { color: chalk.green, icon: "โœ…", tableIcon: "โœ“" }, + pending: { color: chalk.yellow, icon: "โฑ๏ธ", tableIcon: "โฑ" }, + "in-progress": { color: chalk.hex("#FFA500"), icon: "๐Ÿ”„", tableIcon: "โ–บ" }, + deferred: { color: chalk.gray, icon: "โฑ๏ธ", tableIcon: "โฑ" }, + blocked: { color: chalk.red, icon: "โŒ", tableIcon: "โœ—" }, + review: { color: chalk.magenta, icon: "๐Ÿ‘€", tableIcon: "๐Ÿ‘" }, + cancelled: { color: chalk.gray, icon: "โŒ", tableIcon: "โœ—" }, + }; - const config = statusConfig[status.toLowerCase()] || { - color: chalk.red, - icon: 'โŒ', - tableIcon: 'โœ—' - }; + const config = statusConfig[status.toLowerCase()] || { + color: chalk.red, + icon: "โŒ", + tableIcon: "โœ—", + }; - // Use simpler icons for table display to prevent border issues - if (forTable) { - // Use ASCII characters instead of Unicode for completely stable display - const simpleIcons = { - done: 'โœ“', - completed: 'โœ“', - pending: 'โ—‹', - 'in-progress': 'โ–บ', - deferred: 'x', - blocked: '!', // Using plain x character for better compatibility - review: '?' // Using circled dot symbol - }; - const simpleIcon = simpleIcons[status.toLowerCase()] || 'x'; - return config.color(`${simpleIcon} ${status}`); - } + // Use simpler icons for table display to prevent border issues + if (forTable) { + // Use ASCII characters instead of Unicode for completely stable display + const simpleIcons = { + done: "โœ“", + completed: "โœ“", + pending: "โ—‹", + "in-progress": "โ–บ", + deferred: "x", + blocked: "!", // Using plain x character for better compatibility + review: "?", // Using circled dot symbol + }; + const simpleIcon = simpleIcons[status.toLowerCase()] || "x"; + return config.color(`${simpleIcon} ${status}`); + } - return config.color(`${config.icon} ${status}`); + return config.color(`${config.icon} ${status}`); } /** @@ -276,467 +276,467 @@ function getStatusWithColor(status, forTable = false) { * @returns {string} Formatted dependencies string */ function formatDependenciesWithStatus( - dependencies, - allTasks, - forConsole = false, - complexityReport = null // Add complexityReport parameter + dependencies, + allTasks, + forConsole = false, + complexityReport = null // Add complexityReport parameter ) { - if ( - !dependencies || - !Array.isArray(dependencies) || - dependencies.length === 0 - ) { - return forConsole ? chalk.gray('None') : 'None'; - } + if ( + !dependencies || + !Array.isArray(dependencies) || + dependencies.length === 0 + ) { + return forConsole ? chalk.gray("None") : "None"; + } - const formattedDeps = dependencies.map((depId) => { - const depIdStr = depId.toString(); // Ensure string format for display + const formattedDeps = dependencies.map((depId) => { + const depIdStr = depId.toString(); // Ensure string format for display - // Check if it's already a fully qualified subtask ID (like "22.1") - if (depIdStr.includes('.')) { - const [parentId, subtaskId] = depIdStr - .split('.') - .map((id) => parseInt(id, 10)); + // Check if it's already a fully qualified subtask ID (like "22.1") + if (depIdStr.includes(".")) { + const [parentId, subtaskId] = depIdStr + .split(".") + .map((id) => parseInt(id, 10)); - // Find the parent task - const parentTask = allTasks.find((t) => t.id === parentId); - if (!parentTask || !parentTask.subtasks) { - return forConsole - ? chalk.red(`${depIdStr} (Not found)`) - : `${depIdStr} (Not found)`; - } + // Find the parent task + const parentTask = allTasks.find((t) => t.id === parentId); + if (!parentTask || !parentTask.subtasks) { + return forConsole + ? chalk.red(`${depIdStr} (Not found)`) + : `${depIdStr} (Not found)`; + } - // Find the subtask - const subtask = parentTask.subtasks.find((st) => st.id === subtaskId); - if (!subtask) { - return forConsole - ? chalk.red(`${depIdStr} (Not found)`) - : `${depIdStr} (Not found)`; - } + // Find the subtask + const subtask = parentTask.subtasks.find((st) => st.id === subtaskId); + if (!subtask) { + return forConsole + ? chalk.red(`${depIdStr} (Not found)`) + : `${depIdStr} (Not found)`; + } - // Format with status - const status = subtask.status || 'pending'; - const isDone = - status.toLowerCase() === 'done' || status.toLowerCase() === 'completed'; - const isInProgress = status.toLowerCase() === 'in-progress'; + // Format with status + const status = subtask.status || "pending"; + const isDone = + status.toLowerCase() === "done" || status.toLowerCase() === "completed"; + const isInProgress = status.toLowerCase() === "in-progress"; - if (forConsole) { - if (isDone) { - return chalk.green.bold(depIdStr); - } else if (isInProgress) { - return chalk.hex('#FFA500').bold(depIdStr); - } else { - return chalk.red.bold(depIdStr); - } - } + if (forConsole) { + if (isDone) { + return chalk.green.bold(depIdStr); + } else if (isInProgress) { + return chalk.hex("#FFA500").bold(depIdStr); + } else { + return chalk.red.bold(depIdStr); + } + } - // For plain text output (task files), return just the ID without any formatting or emoji - return depIdStr; - } + // For plain text output (task files), return just the ID without any formatting or emoji + return depIdStr; + } - // If depId is a number less than 100, it's likely a reference to a subtask ID in the current task - // This case is typically handled elsewhere (in task-specific code) before calling this function + // If depId is a number less than 100, it's likely a reference to a subtask ID in the current task + // This case is typically handled elsewhere (in task-specific code) before calling this function - // For regular task dependencies (not subtasks) - // Convert string depId to number if needed - const numericDepId = - typeof depId === 'string' ? parseInt(depId, 10) : depId; + // For regular task dependencies (not subtasks) + // Convert string depId to number if needed + const numericDepId = + typeof depId === "string" ? parseInt(depId, 10) : depId; - // Look up the task using the numeric ID - const depTaskResult = findTaskById( - allTasks, - numericDepId, - complexityReport - ); - const depTask = depTaskResult.task; // Access the task object from the result + // Look up the task using the numeric ID + const depTaskResult = findTaskById( + allTasks, + numericDepId, + complexityReport + ); + const depTask = depTaskResult.task; // Access the task object from the result - if (!depTask) { - return forConsole - ? chalk.red(`${depIdStr} (Not found)`) - : `${depIdStr} (Not found)`; - } + if (!depTask) { + return forConsole + ? chalk.red(`${depIdStr} (Not found)`) + : `${depIdStr} (Not found)`; + } - // Format with status - const status = depTask.status || 'pending'; - const isDone = - status.toLowerCase() === 'done' || status.toLowerCase() === 'completed'; - const isInProgress = status.toLowerCase() === 'in-progress'; + // Format with status + const status = depTask.status || "pending"; + const isDone = + status.toLowerCase() === "done" || status.toLowerCase() === "completed"; + const isInProgress = status.toLowerCase() === "in-progress"; - if (forConsole) { - if (isDone) { - return chalk.green.bold(depIdStr); - } else if (isInProgress) { - return chalk.yellow.bold(depIdStr); - } else { - return chalk.red.bold(depIdStr); - } - } + if (forConsole) { + if (isDone) { + return chalk.green.bold(depIdStr); + } else if (isInProgress) { + return chalk.yellow.bold(depIdStr); + } else { + return chalk.red.bold(depIdStr); + } + } - // For plain text output (task files), return just the ID without any formatting or emoji - return depIdStr; - }); + // For plain text output (task files), return just the ID without any formatting or emoji + return depIdStr; + }); - return formattedDeps.join(', '); + return formattedDeps.join(", "); } /** * Display a comprehensive help guide */ function displayHelp() { - displayBanner(); + displayBanner(); - // Get terminal width - moved to top of function to make it available throughout - const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect + // Get terminal width - moved to top of function to make it available throughout + const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect - console.log( - boxen(chalk.white.bold('Task Master CLI'), { - padding: 1, - borderColor: 'blue', - borderStyle: 'round', - margin: { top: 1, bottom: 1 } - }) - ); + console.log( + boxen(chalk.white.bold("Task Master CLI"), { + padding: 1, + borderColor: "blue", + borderStyle: "round", + margin: { top: 1, bottom: 1 }, + }) + ); - // Command categories - const commandCategories = [ - { - title: 'Project Setup & Configuration', - color: 'blue', - commands: [ - { - name: 'init', - args: '[--name=] [--description=] [-y]', - desc: 'Initialize a new project with Task Master structure' - }, - { - name: 'models', - args: '', - desc: 'View current AI model configuration and available models' - }, - { - name: 'models --setup', - args: '', - desc: 'Run interactive setup to configure AI models' - }, - { - name: 'models --set-main', - args: '', - desc: 'Set the primary model for task generation' - }, - { - name: 'models --set-research', - args: '', - desc: 'Set the model for research operations' - }, - { - name: 'models --set-fallback', - args: '', - desc: 'Set the fallback model (optional)' - } - ] - }, - { - title: 'Task Generation', - color: 'cyan', - commands: [ - { - name: 'parse-prd', - args: '--input= [--num-tasks=10]', - desc: 'Generate tasks from a PRD document' - }, - { - name: 'generate', - args: '', - desc: 'Create individual task files from tasks.json' - } - ] - }, - { - title: 'Task Management', - color: 'green', - commands: [ - { - name: 'list', - args: '[--status=] [--with-subtasks]', - desc: 'List all tasks with their status' - }, - { - name: 'set-status', - args: '--id= --status=', - desc: `Update task status (${TASK_STATUS_OPTIONS.join(', ')})` - }, - { - name: 'update', - args: '--from= --prompt=""', - desc: 'Update multiple tasks based on new requirements' - }, - { - name: 'update-task', - args: '--id= --prompt=""', - desc: 'Update a single specific task with new information' - }, - { - name: 'update-subtask', - args: '--id= --prompt=""', - desc: 'Append additional information to a subtask' - }, - { - name: 'add-task', - args: '--prompt="" [--dependencies=] [--priority=]', - desc: 'Add a new task using AI' - }, - { - name: 'remove-task', - args: '--id= [-y]', - desc: 'Permanently remove a task or subtask' - } - ] - }, - { - title: 'Subtask Management', - color: 'yellow', - commands: [ - { - name: 'add-subtask', - args: '--parent= --title="" [--description="<desc>"]', - desc: 'Add a new subtask to a parent task' - }, - { - name: 'add-subtask', - args: '--parent=<id> --task-id=<id>', - desc: 'Convert an existing task into a subtask' - }, - { - name: 'remove-subtask', - args: '--id=<parentId.subtaskId> [--convert]', - desc: 'Remove a subtask (optionally convert to standalone task)' - }, - { - name: 'clear-subtasks', - args: '--id=<id>', - desc: 'Remove all subtasks from specified tasks' - }, - { - name: 'clear-subtasks --all', - args: '', - desc: 'Remove subtasks from all tasks' - } - ] - }, - { - title: 'Task Analysis & Breakdown', - color: 'magenta', - commands: [ - { - name: 'analyze-complexity', - args: '[--research] [--threshold=5]', - desc: 'Analyze tasks and generate expansion recommendations' - }, - { - name: 'complexity-report', - args: '[--file=<path>]', - desc: 'Display the complexity analysis report' - }, - { - name: 'expand', - args: '--id=<id> [--num=5] [--research] [--prompt="<context>"]', - desc: 'Break down tasks into detailed subtasks' - }, - { - name: 'expand --all', - args: '[--force] [--research]', - desc: 'Expand all pending tasks with subtasks' - } - ] - }, - { - title: 'Task Navigation & Viewing', - color: 'cyan', - commands: [ - { - name: 'next', - args: '', - desc: 'Show the next task to work on based on dependencies' - }, - { - name: 'show', - args: '<id>', - desc: 'Display detailed information about a specific task' - } - ] - }, - { - title: 'Dependency Management', - color: 'blue', - commands: [ - { - name: 'add-dependency', - args: '--id=<id> --depends-on=<id>', - desc: 'Add a dependency to a task' - }, - { - name: 'remove-dependency', - args: '--id=<id> --depends-on=<id>', - desc: 'Remove a dependency from a task' - }, - { - name: 'validate-dependencies', - args: '', - desc: 'Identify invalid dependencies without fixing them' - }, - { - name: 'fix-dependencies', - args: '', - desc: 'Fix invalid dependencies automatically' - } - ] - } - ]; + // Command categories + const commandCategories = [ + { + title: "Project Setup & Configuration", + color: "blue", + commands: [ + { + name: "init", + args: "[--name=<name>] [--description=<desc>] [-y]", + desc: "Initialize a new project with Task Master structure", + }, + { + name: "models", + args: "", + desc: "View current AI model configuration and available models", + }, + { + name: "models --setup", + args: "", + desc: "Run interactive setup to configure AI models", + }, + { + name: "models --set-main", + args: "<model_id>", + desc: "Set the primary model for task generation", + }, + { + name: "models --set-research", + args: "<model_id>", + desc: "Set the model for research operations", + }, + { + name: "models --set-fallback", + args: "<model_id>", + desc: "Set the fallback model (optional)", + }, + ], + }, + { + title: "Task Generation", + color: "cyan", + commands: [ + { + name: "parse-prd", + args: "--input=<file.txt> [--num-tasks=10]", + desc: "Generate tasks from a PRD document", + }, + { + name: "generate", + args: "", + desc: "Create individual task files from tasks.json", + }, + ], + }, + { + title: "Task Management", + color: "green", + commands: [ + { + name: "list", + args: "[--status=<status>] [--with-subtasks]", + desc: "List all tasks with their status", + }, + { + name: "set-status", + args: "--id=<id> --status=<status>", + desc: `Update task status (${TASK_STATUS_OPTIONS.join(", ")})`, + }, + { + name: "update", + args: '--from=<id> --prompt="<context>"', + desc: "Update multiple tasks based on new requirements", + }, + { + name: "update-task", + args: '--id=<id> --prompt="<context>"', + desc: "Update a single specific task with new information", + }, + { + name: "update-subtask", + args: '--id=<parentId.subtaskId> --prompt="<context>"', + desc: "Append additional information to a subtask", + }, + { + name: "add-task", + args: '--prompt="<text>" [--dependencies=<ids>] [--priority=<priority>]', + desc: "Add a new task using AI", + }, + { + name: "remove-task", + args: "--id=<id> [-y]", + desc: "Permanently remove a task or subtask", + }, + ], + }, + { + title: "Subtask Management", + color: "yellow", + commands: [ + { + name: "add-subtask", + args: '--parent=<id> --title="<title>" [--description="<desc>"]', + desc: "Add a new subtask to a parent task", + }, + { + name: "add-subtask", + args: "--parent=<id> --task-id=<id>", + desc: "Convert an existing task into a subtask", + }, + { + name: "remove-subtask", + args: "--id=<parentId.subtaskId> [--convert]", + desc: "Remove a subtask (optionally convert to standalone task)", + }, + { + name: "clear-subtasks", + args: "--id=<id>", + desc: "Remove all subtasks from specified tasks", + }, + { + name: "clear-subtasks --all", + args: "", + desc: "Remove subtasks from all tasks", + }, + ], + }, + { + title: "Task Analysis & Breakdown", + color: "magenta", + commands: [ + { + name: "analyze-complexity", + args: "[--research] [--threshold=5]", + desc: "Analyze tasks and generate expansion recommendations", + }, + { + name: "complexity-report", + args: "[--file=<path>]", + desc: "Display the complexity analysis report", + }, + { + name: "expand", + args: '--id=<id> [--num=5] [--research] [--prompt="<context>"]', + desc: "Break down tasks into detailed subtasks", + }, + { + name: "expand --all", + args: "[--force] [--research]", + desc: "Expand all pending tasks with subtasks", + }, + ], + }, + { + title: "Task Navigation & Viewing", + color: "cyan", + commands: [ + { + name: "next", + args: "", + desc: "Show the next task to work on based on dependencies", + }, + { + name: "show", + args: "<id>", + desc: "Display detailed information about a specific task", + }, + ], + }, + { + title: "Dependency Management", + color: "blue", + commands: [ + { + name: "add-dependency", + args: "--id=<id> --depends-on=<id>", + desc: "Add a dependency to a task", + }, + { + name: "remove-dependency", + args: "--id=<id> --depends-on=<id>", + desc: "Remove a dependency from a task", + }, + { + name: "validate-dependencies", + args: "", + desc: "Identify invalid dependencies without fixing them", + }, + { + name: "fix-dependencies", + args: "", + desc: "Fix invalid dependencies automatically", + }, + ], + }, + ]; - // Display each category - commandCategories.forEach((category) => { - console.log( - boxen(chalk[category.color].bold(category.title), { - padding: { left: 2, right: 2, top: 0, bottom: 0 }, - margin: { top: 1, bottom: 0 }, - borderColor: category.color, - borderStyle: 'round' - }) - ); + // Display each category + commandCategories.forEach((category) => { + console.log( + boxen(chalk[category.color].bold(category.title), { + padding: { left: 2, right: 2, top: 0, bottom: 0 }, + margin: { top: 1, bottom: 0 }, + borderColor: category.color, + borderStyle: "round", + }) + ); - // Calculate dynamic column widths - adjust ratios as needed - const nameWidth = Math.max(25, Math.floor(terminalWidth * 0.2)); // 20% of width but min 25 - const argsWidth = Math.max(40, Math.floor(terminalWidth * 0.35)); // 35% of width but min 40 - const descWidth = Math.max(45, Math.floor(terminalWidth * 0.45) - 10); // 45% of width but min 45, minus some buffer + // Calculate dynamic column widths - adjust ratios as needed + const nameWidth = Math.max(25, Math.floor(terminalWidth * 0.2)); // 20% of width but min 25 + const argsWidth = Math.max(40, Math.floor(terminalWidth * 0.35)); // 35% of width but min 40 + const descWidth = Math.max(45, Math.floor(terminalWidth * 0.45) - 10); // 45% of width but min 45, minus some buffer - const commandTable = new Table({ - colWidths: [nameWidth, argsWidth, descWidth], - chars: { - top: '', - 'top-mid': '', - 'top-left': '', - 'top-right': '', - bottom: '', - 'bottom-mid': '', - 'bottom-left': '', - 'bottom-right': '', - left: '', - 'left-mid': '', - mid: '', - 'mid-mid': '', - right: '', - 'right-mid': '', - middle: ' ' - }, - style: { border: [], 'padding-left': 4 }, - wordWrap: true - }); + const commandTable = new Table({ + colWidths: [nameWidth, argsWidth, descWidth], + chars: { + top: "", + "top-mid": "", + "top-left": "", + "top-right": "", + bottom: "", + "bottom-mid": "", + "bottom-left": "", + "bottom-right": "", + left: "", + "left-mid": "", + mid: "", + "mid-mid": "", + right: "", + "right-mid": "", + middle: " ", + }, + style: { border: [], "padding-left": 4 }, + wordWrap: true, + }); - category.commands.forEach((cmd, index) => { - commandTable.push([ - `${chalk.yellow.bold(cmd.name)}${chalk.reset('')}`, - `${chalk.white(cmd.args)}${chalk.reset('')}`, - `${chalk.dim(cmd.desc)}${chalk.reset('')}` - ]); - }); + category.commands.forEach((cmd, index) => { + commandTable.push([ + `${chalk.yellow.bold(cmd.name)}${chalk.reset("")}`, + `${chalk.white(cmd.args)}${chalk.reset("")}`, + `${chalk.dim(cmd.desc)}${chalk.reset("")}`, + ]); + }); - console.log(commandTable.toString()); - console.log(''); - }); + console.log(commandTable.toString()); + console.log(""); + }); - // Display configuration section - console.log( - boxen(chalk.cyan.bold('Configuration'), { - padding: { left: 2, right: 2, top: 0, bottom: 0 }, - margin: { top: 1, bottom: 0 }, - borderColor: 'cyan', - borderStyle: 'round' - }) - ); + // Display configuration section + console.log( + boxen(chalk.cyan.bold("Configuration"), { + padding: { left: 2, right: 2, top: 0, bottom: 0 }, + margin: { top: 1, bottom: 0 }, + borderColor: "cyan", + borderStyle: "round", + }) + ); - // Get terminal width if not already defined - const configTerminalWidth = terminalWidth || process.stdout.columns || 100; + // Get terminal width if not already defined + const configTerminalWidth = terminalWidth || process.stdout.columns || 100; - // Calculate dynamic column widths for config table - const configKeyWidth = Math.max(30, Math.floor(configTerminalWidth * 0.25)); - const configDescWidth = Math.max(50, Math.floor(configTerminalWidth * 0.45)); - const configValueWidth = Math.max( - 30, - Math.floor(configTerminalWidth * 0.3) - 10 - ); + // Calculate dynamic column widths for config table + const configKeyWidth = Math.max(30, Math.floor(configTerminalWidth * 0.25)); + const configDescWidth = Math.max(50, Math.floor(configTerminalWidth * 0.45)); + const configValueWidth = Math.max( + 30, + Math.floor(configTerminalWidth * 0.3) - 10 + ); - const configTable = new Table({ - colWidths: [configKeyWidth, configDescWidth, configValueWidth], - chars: { - top: '', - 'top-mid': '', - 'top-left': '', - 'top-right': '', - bottom: '', - 'bottom-mid': '', - 'bottom-left': '', - 'bottom-right': '', - left: '', - 'left-mid': '', - mid: '', - 'mid-mid': '', - right: '', - 'right-mid': '', - middle: ' ' - }, - style: { border: [], 'padding-left': 4 }, - wordWrap: true - }); + const configTable = new Table({ + colWidths: [configKeyWidth, configDescWidth, configValueWidth], + chars: { + top: "", + "top-mid": "", + "top-left": "", + "top-right": "", + bottom: "", + "bottom-mid": "", + "bottom-left": "", + "bottom-right": "", + left: "", + "left-mid": "", + mid: "", + "mid-mid": "", + right: "", + "right-mid": "", + middle: " ", + }, + style: { border: [], "padding-left": 4 }, + wordWrap: true, + }); - configTable.push( - [ - `${chalk.yellow(TASKMASTER_CONFIG_FILE)}${chalk.reset('')}`, - `${chalk.white('AI model configuration file (project root)')}${chalk.reset('')}`, - `${chalk.dim('Managed by models cmd')}${chalk.reset('')}` - ], - [ - `${chalk.yellow('API Keys (.env)')}${chalk.reset('')}`, - `${chalk.white('API keys for AI providers (ANTHROPIC_API_KEY, etc.)')}${chalk.reset('')}`, - `${chalk.dim('Required in .env file')}${chalk.reset('')}` - ], - [ - `${chalk.yellow('MCP Keys (mcp.json)')}${chalk.reset('')}`, - `${chalk.white('API keys for Cursor integration')}${chalk.reset('')}`, - `${chalk.dim('Required in .cursor/')}${chalk.reset('')}` - ] - ); + configTable.push( + [ + `${chalk.yellow(TASKMASTER_CONFIG_FILE)}${chalk.reset("")}`, + `${chalk.white("AI model configuration file (project root)")}${chalk.reset("")}`, + `${chalk.dim("Managed by models cmd")}${chalk.reset("")}`, + ], + [ + `${chalk.yellow("API Keys (.env)")}${chalk.reset("")}`, + `${chalk.white("API keys for AI providers (ANTHROPIC_API_KEY, etc.)")}${chalk.reset("")}`, + `${chalk.dim("Required in .env file")}${chalk.reset("")}`, + ], + [ + `${chalk.yellow("MCP Keys (mcp.json)")}${chalk.reset("")}`, + `${chalk.white("API keys for Cursor integration")}${chalk.reset("")}`, + `${chalk.dim("Required in .cursor/")}${chalk.reset("")}`, + ] + ); - console.log(configTable.toString()); - console.log(''); + console.log(configTable.toString()); + console.log(""); - // Show helpful hints - console.log( - boxen( - chalk.white.bold('Quick Start:') + - '\n\n' + - chalk.cyan('1. Create Project: ') + - chalk.white('task-master init') + - '\n' + - chalk.cyan('2. Setup Models: ') + - chalk.white('task-master models --setup') + - '\n' + - chalk.cyan('3. Parse PRD: ') + - chalk.white('task-master parse-prd --input=<prd-file>') + - '\n' + - chalk.cyan('4. List Tasks: ') + - chalk.white('task-master list') + - '\n' + - chalk.cyan('5. Find Next Task: ') + - chalk.white('task-master next'), - { - padding: 1, - borderColor: 'yellow', - borderStyle: 'round', - margin: { top: 1 }, - width: Math.min(configTerminalWidth - 10, 100) // Limit width to terminal width minus padding, max 100 - } - ) - ); + // Show helpful hints + console.log( + boxen( + chalk.white.bold("Quick Start:") + + "\n\n" + + chalk.cyan("1. Create Project: ") + + chalk.white("task-master init") + + "\n" + + chalk.cyan("2. Setup Models: ") + + chalk.white("task-master models --setup") + + "\n" + + chalk.cyan("3. Parse PRD: ") + + chalk.white("task-master parse-prd --input=<prd-file>") + + "\n" + + chalk.cyan("4. List Tasks: ") + + chalk.white("task-master list") + + "\n" + + chalk.cyan("5. Find Next Task: ") + + chalk.white("task-master next"), + { + padding: 1, + borderColor: "yellow", + borderStyle: "round", + margin: { top: 1 }, + width: Math.min(configTerminalWidth - 10, 100), // Limit width to terminal width minus padding, max 100 + } + ) + ); } /** @@ -745,9 +745,9 @@ function displayHelp() { * @returns {string} Colored complexity score */ function getComplexityWithColor(score) { - if (score <= 3) return chalk.green(`๐ŸŸข ${score}`); - if (score <= 6) return chalk.yellow(`๐ŸŸก ${score}`); - return chalk.red(`๐Ÿ”ด ${score}`); + if (score <= 3) return chalk.green(`โ— ${score}`); + if (score <= 6) return chalk.yellow(`โ— ${score}`); + return chalk.red(`โ— ${score}`); } /** @@ -757,9 +757,9 @@ function getComplexityWithColor(score) { * @returns {string} Truncated string */ function truncateString(str, maxLength) { - if (!str) return ''; - if (str.length <= maxLength) return str; - return str.substring(0, maxLength - 3) + '...'; + if (!str) return ""; + if (str.length <= maxLength) return str; + return str.substring(0, maxLength - 3) + "..."; } /** @@ -767,264 +767,264 @@ function truncateString(str, maxLength) { * @param {string} tasksPath - Path to the tasks.json file */ async function displayNextTask(tasksPath, complexityReportPath = null) { - displayBanner(); + displayBanner(); - // Read the tasks file - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', 'No valid tasks found.'); - process.exit(1); - } + // Read the tasks file + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log("error", "No valid tasks found."); + process.exit(1); + } - // Read complexity report once - const complexityReport = readComplexityReport(complexityReportPath); + // Read complexity report once + const complexityReport = readComplexityReport(complexityReportPath); - // Find the next task - const nextTask = findNextTask(data.tasks, complexityReport); + // Find the next task + const nextTask = findNextTask(data.tasks, complexityReport); - if (!nextTask) { - console.log( - boxen( - chalk.yellow('No eligible tasks found!\n\n') + - 'All pending tasks have unsatisfied dependencies, or all tasks are completed.', - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'yellow', - borderStyle: 'round', - margin: { top: 1 } - } - ) - ); - return; - } + if (!nextTask) { + console.log( + boxen( + chalk.yellow("No eligible tasks found!\n\n") + + "All pending tasks have unsatisfied dependencies, or all tasks are completed.", + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "yellow", + borderStyle: "round", + margin: { top: 1 }, + } + ) + ); + return; + } - // Display the task in a nice format - console.log( - boxen(chalk.white.bold(`Next Task: #${nextTask.id} - ${nextTask.title}`), { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'blue', - borderStyle: 'round', - margin: { top: 1, bottom: 0 } - }) - ); + // Display the task in a nice format + console.log( + boxen(chalk.white.bold(`Next Task: #${nextTask.id} - ${nextTask.title}`), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "blue", + borderStyle: "round", + margin: { top: 1, bottom: 0 }, + }) + ); - // Create a table with task details - const taskTable = new Table({ - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, - colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], - wordWrap: true - }); + // Create a table with task details + const taskTable = new Table({ + style: { + head: [], + border: [], + "padding-top": 0, + "padding-bottom": 0, + compact: true, + }, + chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, + colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], + wordWrap: true, + }); - // Priority with color - const priorityColors = { - high: chalk.red.bold, - medium: chalk.yellow, - low: chalk.gray - }; - const priorityColor = - priorityColors[nextTask.priority || 'medium'] || chalk.white; + // Priority with color + const priorityColors = { + high: chalk.red.bold, + medium: chalk.yellow, + low: chalk.gray, + }; + const priorityColor = + priorityColors[nextTask.priority || "medium"] || chalk.white; - // Add task details to table - taskTable.push( - [chalk.cyan.bold('ID:'), nextTask.id.toString()], - [chalk.cyan.bold('Title:'), nextTask.title], - [ - chalk.cyan.bold('Priority:'), - priorityColor(nextTask.priority || 'medium') - ], - [ - chalk.cyan.bold('Dependencies:'), - formatDependenciesWithStatus( - nextTask.dependencies, - data.tasks, - true, - complexityReport - ) - ], - [ - chalk.cyan.bold('Complexity:'), - nextTask.complexityScore - ? getComplexityWithColor(nextTask.complexityScore) - : chalk.gray('N/A') - ], - [chalk.cyan.bold('Description:'), nextTask.description] - ); + // Add task details to table + taskTable.push( + [chalk.cyan.bold("ID:"), nextTask.id.toString()], + [chalk.cyan.bold("Title:"), nextTask.title], + [ + chalk.cyan.bold("Priority:"), + priorityColor(nextTask.priority || "medium"), + ], + [ + chalk.cyan.bold("Dependencies:"), + formatDependenciesWithStatus( + nextTask.dependencies, + data.tasks, + true, + complexityReport + ), + ], + [ + chalk.cyan.bold("Complexity:"), + nextTask.complexityScore + ? getComplexityWithColor(nextTask.complexityScore) + : chalk.gray("N/A"), + ], + [chalk.cyan.bold("Description:"), nextTask.description] + ); - console.log(taskTable.toString()); + console.log(taskTable.toString()); - // If task has details, show them in a separate box - if (nextTask.details && nextTask.details.trim().length > 0) { - console.log( - boxen( - chalk.white.bold('Implementation Details:') + '\n\n' + nextTask.details, - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'cyan', - borderStyle: 'round', - margin: { top: 1, bottom: 0 } - } - ) - ); - } + // If task has details, show them in a separate box + if (nextTask.details && nextTask.details.trim().length > 0) { + console.log( + boxen( + chalk.white.bold("Implementation Details:") + "\n\n" + nextTask.details, + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "cyan", + borderStyle: "round", + margin: { top: 1, bottom: 0 }, + } + ) + ); + } - // Determine if the nextTask is a subtask - const isSubtask = !!nextTask.parentId; + // Determine if the nextTask is a subtask + const isSubtask = !!nextTask.parentId; - // Show subtasks if they exist (only for parent tasks) - if (!isSubtask && nextTask.subtasks && nextTask.subtasks.length > 0) { - console.log( - boxen(chalk.white.bold('Subtasks'), { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - margin: { top: 1, bottom: 0 }, - borderColor: 'magenta', - borderStyle: 'round' - }) - ); + // Show subtasks if they exist (only for parent tasks) + if (!isSubtask && nextTask.subtasks && nextTask.subtasks.length > 0) { + console.log( + boxen(chalk.white.bold("Subtasks"), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 1, bottom: 0 }, + borderColor: "magenta", + borderStyle: "round", + }) + ); - // Calculate available width for the subtask table - const availableWidth = process.stdout.columns - 10 || 100; // Default to 100 if can't detect + // Calculate available width for the subtask table + const availableWidth = process.stdout.columns - 10 || 100; // Default to 100 if can't detect - // Define percentage-based column widths - const idWidthPct = 8; - const statusWidthPct = 15; - const depsWidthPct = 25; - const titleWidthPct = 100 - idWidthPct - statusWidthPct - depsWidthPct; + // Define percentage-based column widths + const idWidthPct = 8; + const statusWidthPct = 15; + const depsWidthPct = 25; + const titleWidthPct = 100 - idWidthPct - statusWidthPct - depsWidthPct; - // Calculate actual column widths - const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); - const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); - const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); - const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); + // Calculate actual column widths + const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); + const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); + const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); + const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); - // Create a table for subtasks with improved handling - const subtaskTable = new Table({ - head: [ - chalk.magenta.bold('ID'), - chalk.magenta.bold('Status'), - chalk.magenta.bold('Title'), - chalk.magenta.bold('Deps') - ], - colWidths: [idWidth, statusWidth, titleWidth, depsWidth], - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, - wordWrap: true - }); + // Create a table for subtasks with improved handling + const subtaskTable = new Table({ + head: [ + chalk.magenta.bold("ID"), + chalk.magenta.bold("Status"), + chalk.magenta.bold("Title"), + chalk.magenta.bold("Deps"), + ], + colWidths: [idWidth, statusWidth, titleWidth, depsWidth], + style: { + head: [], + border: [], + "padding-top": 0, + "padding-bottom": 0, + compact: true, + }, + chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, + wordWrap: true, + }); - // Add subtasks to table - nextTask.subtasks.forEach((st) => { - const statusColor = - { - done: chalk.green, - completed: chalk.green, - pending: chalk.yellow, - 'in-progress': chalk.blue - }[st.status || 'pending'] || chalk.white; + // Add subtasks to table + nextTask.subtasks.forEach((st) => { + const statusColor = + { + done: chalk.green, + completed: chalk.green, + pending: chalk.yellow, + "in-progress": chalk.blue, + }[st.status || "pending"] || chalk.white; - // Format subtask dependencies - let subtaskDeps = 'None'; - if (st.dependencies && st.dependencies.length > 0) { - // Format dependencies with correct notation - const formattedDeps = st.dependencies.map((depId) => { - if (typeof depId === 'number' && depId < 100) { - const foundSubtask = nextTask.subtasks.find( - (st) => st.id === depId - ); - if (foundSubtask) { - const isDone = - foundSubtask.status === 'done' || - foundSubtask.status === 'completed'; - const isInProgress = foundSubtask.status === 'in-progress'; + // Format subtask dependencies + let subtaskDeps = "None"; + if (st.dependencies && st.dependencies.length > 0) { + // Format dependencies with correct notation + const formattedDeps = st.dependencies.map((depId) => { + if (typeof depId === "number" && depId < 100) { + const foundSubtask = nextTask.subtasks.find( + (st) => st.id === depId + ); + if (foundSubtask) { + const isDone = + foundSubtask.status === "done" || + foundSubtask.status === "completed"; + const isInProgress = foundSubtask.status === "in-progress"; - // Use consistent color formatting instead of emojis - if (isDone) { - return chalk.green.bold(`${nextTask.id}.${depId}`); - } else if (isInProgress) { - return chalk.hex('#FFA500').bold(`${nextTask.id}.${depId}`); - } else { - return chalk.red.bold(`${nextTask.id}.${depId}`); - } - } - return chalk.red(`${nextTask.id}.${depId} (Not found)`); - } - return depId; - }); + // Use consistent color formatting instead of emojis + if (isDone) { + return chalk.green.bold(`${nextTask.id}.${depId}`); + } else if (isInProgress) { + return chalk.hex("#FFA500").bold(`${nextTask.id}.${depId}`); + } else { + return chalk.red.bold(`${nextTask.id}.${depId}`); + } + } + return chalk.red(`${nextTask.id}.${depId} (Not found)`); + } + return depId; + }); - // Join the formatted dependencies directly instead of passing to formatDependenciesWithStatus again - subtaskDeps = - formattedDeps.length === 1 - ? formattedDeps[0] - : formattedDeps.join(chalk.white(', ')); - } + // Join the formatted dependencies directly instead of passing to formatDependenciesWithStatus again + subtaskDeps = + formattedDeps.length === 1 + ? formattedDeps[0] + : formattedDeps.join(chalk.white(", ")); + } - subtaskTable.push([ - `${nextTask.id}.${st.id}`, - statusColor(st.status || 'pending'), - st.title, - subtaskDeps - ]); - }); + subtaskTable.push([ + `${nextTask.id}.${st.id}`, + statusColor(st.status || "pending"), + st.title, + subtaskDeps, + ]); + }); - console.log(subtaskTable.toString()); - } + console.log(subtaskTable.toString()); + } - // Suggest expanding if no subtasks (only for parent tasks without subtasks) - if (!isSubtask && (!nextTask.subtasks || nextTask.subtasks.length === 0)) { - console.log( - boxen( - chalk.yellow('No subtasks found. Consider breaking down this task:') + - '\n' + - chalk.white( - `Run: ${chalk.cyan(`task-master expand --id=${nextTask.id}`)}` - ), - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'yellow', - borderStyle: 'round', - margin: { top: 1, bottom: 0 } - } - ) - ); - } + // Suggest expanding if no subtasks (only for parent tasks without subtasks) + if (!isSubtask && (!nextTask.subtasks || nextTask.subtasks.length === 0)) { + console.log( + boxen( + chalk.yellow("No subtasks found. Consider breaking down this task:") + + "\n" + + chalk.white( + `Run: ${chalk.cyan(`task-master expand --id=${nextTask.id}`)}` + ), + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "yellow", + borderStyle: "round", + margin: { top: 1, bottom: 0 }, + } + ) + ); + } - // Show action suggestions - let suggestedActionsContent = chalk.white.bold('Suggested Actions:') + '\n'; - if (isSubtask) { - // Suggested actions for a subtask - suggestedActionsContent += - `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` + - `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` + - `${chalk.cyan('3.')} View parent task: ${chalk.yellow(`task-master show --id=${nextTask.parentId}`)}`; - } else { - // Suggested actions for a parent task - suggestedActionsContent += - `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` + - `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` + - (nextTask.subtasks && nextTask.subtasks.length > 0 - ? `${chalk.cyan('3.')} Update subtask status: ${chalk.yellow(`task-master set-status --id=${nextTask.id}.1 --status=done`)}` // Example: first subtask - : `${chalk.cyan('3.')} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${nextTask.id}`)}`); - } + // Show action suggestions + let suggestedActionsContent = chalk.white.bold("Suggested Actions:") + "\n"; + if (isSubtask) { + // Suggested actions for a subtask + suggestedActionsContent += + `${chalk.cyan("1.")} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` + + `${chalk.cyan("2.")} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` + + `${chalk.cyan("3.")} View parent task: ${chalk.yellow(`task-master show --id=${nextTask.parentId}`)}`; + } else { + // Suggested actions for a parent task + suggestedActionsContent += + `${chalk.cyan("1.")} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` + + `${chalk.cyan("2.")} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` + + (nextTask.subtasks && nextTask.subtasks.length > 0 + ? `${chalk.cyan("3.")} Update subtask status: ${chalk.yellow(`task-master set-status --id=${nextTask.id}.1 --status=done`)}` // Example: first subtask + : `${chalk.cyan("3.")} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${nextTask.id}`)}`); + } - console.log( - boxen(suggestedActionsContent, { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'green', - borderStyle: 'round', - margin: { top: 1 } - }) - ); + console.log( + boxen(suggestedActionsContent, { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "green", + borderStyle: "round", + margin: { top: 1 }, + }) + ); } /** @@ -1034,460 +1034,460 @@ async function displayNextTask(tasksPath, complexityReportPath = null) { * @param {string} [statusFilter] - Optional status to filter subtasks by */ async function displayTaskById( - tasksPath, - taskId, - complexityReportPath = null, - statusFilter = null + tasksPath, + taskId, + complexityReportPath = null, + statusFilter = null ) { - displayBanner(); + displayBanner(); - // Read the tasks file - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', 'No valid tasks found.'); - process.exit(1); - } + // Read the tasks file + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log("error", "No valid tasks found."); + process.exit(1); + } - // Read complexity report once - const complexityReport = readComplexityReport(complexityReportPath); + // Read complexity report once + const complexityReport = readComplexityReport(complexityReportPath); - // Find the task by ID, applying the status filter if provided - // Returns { task, originalSubtaskCount, originalSubtasks } - const { task, originalSubtaskCount, originalSubtasks } = findTaskById( - data.tasks, - taskId, - complexityReport, - statusFilter - ); + // Find the task by ID, applying the status filter if provided + // Returns { task, originalSubtaskCount, originalSubtasks } + const { task, originalSubtaskCount, originalSubtasks } = findTaskById( + data.tasks, + taskId, + complexityReport, + statusFilter + ); - if (!task) { - console.log( - boxen(chalk.yellow(`Task with ID ${taskId} not found!`), { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'yellow', - borderStyle: 'round', - margin: { top: 1 } - }) - ); - return; - } + if (!task) { + console.log( + boxen(chalk.yellow(`Task with ID ${taskId} not found!`), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "yellow", + borderStyle: "round", + margin: { top: 1 }, + }) + ); + return; + } - // Handle subtask display specially (This logic remains the same) - if (task.isSubtask || task.parentTask) { - console.log( - boxen( - chalk.white.bold( - `Subtask: #${task.parentTask.id}.${task.id} - ${task.title}` - ), - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'magenta', - borderStyle: 'round', - margin: { top: 1, bottom: 0 } - } - ) - ); + // Handle subtask display specially (This logic remains the same) + if (task.isSubtask || task.parentTask) { + console.log( + boxen( + chalk.white.bold( + `Subtask: #${task.parentTask.id}.${task.id} - ${task.title}` + ), + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "magenta", + borderStyle: "round", + margin: { top: 1, bottom: 0 }, + } + ) + ); - const subtaskTable = new Table({ - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, - colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], - wordWrap: true - }); - subtaskTable.push( - [chalk.cyan.bold('ID:'), `${task.parentTask.id}.${task.id}`], - [ - chalk.cyan.bold('Parent Task:'), - `#${task.parentTask.id} - ${task.parentTask.title}` - ], - [chalk.cyan.bold('Title:'), task.title], - [ - chalk.cyan.bold('Status:'), - getStatusWithColor(task.status || 'pending', true) - ], - [ - chalk.cyan.bold('Complexity:'), - task.complexityScore - ? getComplexityWithColor(task.complexityScore) - : chalk.gray('N/A') - ], - [ - chalk.cyan.bold('Description:'), - task.description || 'No description provided.' - ] - ); - console.log(subtaskTable.toString()); + const subtaskTable = new Table({ + style: { + head: [], + border: [], + "padding-top": 0, + "padding-bottom": 0, + compact: true, + }, + chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, + colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], + wordWrap: true, + }); + subtaskTable.push( + [chalk.cyan.bold("ID:"), `${task.parentTask.id}.${task.id}`], + [ + chalk.cyan.bold("Parent Task:"), + `#${task.parentTask.id} - ${task.parentTask.title}`, + ], + [chalk.cyan.bold("Title:"), task.title], + [ + chalk.cyan.bold("Status:"), + getStatusWithColor(task.status || "pending", true), + ], + [ + chalk.cyan.bold("Complexity:"), + task.complexityScore + ? getComplexityWithColor(task.complexityScore) + : chalk.gray("N/A"), + ], + [ + chalk.cyan.bold("Description:"), + task.description || "No description provided.", + ] + ); + console.log(subtaskTable.toString()); - if (task.details && task.details.trim().length > 0) { - console.log( - boxen( - chalk.white.bold('Implementation Details:') + '\n\n' + task.details, - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'cyan', - borderStyle: 'round', - margin: { top: 1, bottom: 0 } - } - ) - ); - } + if (task.details && task.details.trim().length > 0) { + console.log( + boxen( + chalk.white.bold("Implementation Details:") + "\n\n" + task.details, + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "cyan", + borderStyle: "round", + margin: { top: 1, bottom: 0 }, + } + ) + ); + } - console.log( - boxen( - chalk.white.bold('Suggested Actions:') + - '\n' + - `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=in-progress`)}\n` + - `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=done`)}\n` + - `${chalk.cyan('3.')} View parent task: ${chalk.yellow(`task-master show --id=${task.parentTask.id}`)}`, - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'green', - borderStyle: 'round', - margin: { top: 1 } - } - ) - ); - return; // Exit after displaying subtask details - } + console.log( + boxen( + chalk.white.bold("Suggested Actions:") + + "\n" + + `${chalk.cyan("1.")} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=in-progress`)}\n` + + `${chalk.cyan("2.")} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=done`)}\n` + + `${chalk.cyan("3.")} View parent task: ${chalk.yellow(`task-master show --id=${task.parentTask.id}`)}`, + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "green", + borderStyle: "round", + margin: { top: 1 }, + } + ) + ); + return; // Exit after displaying subtask details + } - // --- Display Regular Task Details --- - console.log( - boxen(chalk.white.bold(`Task: #${task.id} - ${task.title}`), { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'blue', - borderStyle: 'round', - margin: { top: 1, bottom: 0 } - }) - ); + // --- Display Regular Task Details --- + console.log( + boxen(chalk.white.bold(`Task: #${task.id} - ${task.title}`), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "blue", + borderStyle: "round", + margin: { top: 1, bottom: 0 }, + }) + ); - const taskTable = new Table({ - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, - colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], - wordWrap: true - }); - const priorityColors = { - high: chalk.red.bold, - medium: chalk.yellow, - low: chalk.gray - }; - const priorityColor = - priorityColors[task.priority || 'medium'] || chalk.white; - taskTable.push( - [chalk.cyan.bold('ID:'), task.id.toString()], - [chalk.cyan.bold('Title:'), task.title], - [ - chalk.cyan.bold('Status:'), - getStatusWithColor(task.status || 'pending', true) - ], - [chalk.cyan.bold('Priority:'), priorityColor(task.priority || 'medium')], - [ - chalk.cyan.bold('Dependencies:'), - formatDependenciesWithStatus( - task.dependencies, - data.tasks, - true, - complexityReport - ) - ], - [ - chalk.cyan.bold('Complexity:'), - task.complexityScore - ? getComplexityWithColor(task.complexityScore) - : chalk.gray('N/A') - ], - [chalk.cyan.bold('Description:'), task.description] - ); - console.log(taskTable.toString()); + const taskTable = new Table({ + style: { + head: [], + border: [], + "padding-top": 0, + "padding-bottom": 0, + compact: true, + }, + chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, + colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], + wordWrap: true, + }); + const priorityColors = { + high: chalk.red.bold, + medium: chalk.yellow, + low: chalk.gray, + }; + const priorityColor = + priorityColors[task.priority || "medium"] || chalk.white; + taskTable.push( + [chalk.cyan.bold("ID:"), task.id.toString()], + [chalk.cyan.bold("Title:"), task.title], + [ + chalk.cyan.bold("Status:"), + getStatusWithColor(task.status || "pending", true), + ], + [chalk.cyan.bold("Priority:"), priorityColor(task.priority || "medium")], + [ + chalk.cyan.bold("Dependencies:"), + formatDependenciesWithStatus( + task.dependencies, + data.tasks, + true, + complexityReport + ), + ], + [ + chalk.cyan.bold("Complexity:"), + task.complexityScore + ? getComplexityWithColor(task.complexityScore) + : chalk.gray("N/A"), + ], + [chalk.cyan.bold("Description:"), task.description] + ); + console.log(taskTable.toString()); - if (task.details && task.details.trim().length > 0) { - console.log( - boxen( - chalk.white.bold('Implementation Details:') + '\n\n' + task.details, - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'cyan', - borderStyle: 'round', - margin: { top: 1, bottom: 0 } - } - ) - ); - } - if (task.testStrategy && task.testStrategy.trim().length > 0) { - console.log( - boxen(chalk.white.bold('Test Strategy:') + '\n\n' + task.testStrategy, { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'cyan', - borderStyle: 'round', - margin: { top: 1, bottom: 0 } - }) - ); - } + if (task.details && task.details.trim().length > 0) { + console.log( + boxen( + chalk.white.bold("Implementation Details:") + "\n\n" + task.details, + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "cyan", + borderStyle: "round", + margin: { top: 1, bottom: 0 }, + } + ) + ); + } + if (task.testStrategy && task.testStrategy.trim().length > 0) { + console.log( + boxen(chalk.white.bold("Test Strategy:") + "\n\n" + task.testStrategy, { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "cyan", + borderStyle: "round", + margin: { top: 1, bottom: 0 }, + }) + ); + } - // --- Subtask Table Display (uses filtered list: task.subtasks) --- - if (task.subtasks && task.subtasks.length > 0) { - console.log( - boxen(chalk.white.bold('Subtasks'), { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - margin: { top: 1, bottom: 0 }, - borderColor: 'magenta', - borderStyle: 'round' - }) - ); + // --- Subtask Table Display (uses filtered list: task.subtasks) --- + if (task.subtasks && task.subtasks.length > 0) { + console.log( + boxen(chalk.white.bold("Subtasks"), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 1, bottom: 0 }, + borderColor: "magenta", + borderStyle: "round", + }) + ); - const availableWidth = process.stdout.columns - 10 || 100; - const idWidthPct = 10; - const statusWidthPct = 15; - const depsWidthPct = 25; - const titleWidthPct = 100 - idWidthPct - statusWidthPct - depsWidthPct; - const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); - const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); - const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); - const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); + const availableWidth = process.stdout.columns - 10 || 100; + const idWidthPct = 10; + const statusWidthPct = 15; + const depsWidthPct = 25; + const titleWidthPct = 100 - idWidthPct - statusWidthPct - depsWidthPct; + const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); + const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); + const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); + const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); - const subtaskTable = new Table({ - head: [ - chalk.magenta.bold('ID'), - chalk.magenta.bold('Status'), - chalk.magenta.bold('Title'), - chalk.magenta.bold('Deps') - ], - colWidths: [idWidth, statusWidth, titleWidth, depsWidth], - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, - wordWrap: true - }); + const subtaskTable = new Table({ + head: [ + chalk.magenta.bold("ID"), + chalk.magenta.bold("Status"), + chalk.magenta.bold("Title"), + chalk.magenta.bold("Deps"), + ], + colWidths: [idWidth, statusWidth, titleWidth, depsWidth], + style: { + head: [], + border: [], + "padding-top": 0, + "padding-bottom": 0, + compact: true, + }, + chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, + wordWrap: true, + }); - // Populate table with the potentially filtered subtasks - task.subtasks.forEach((st) => { - const statusColorMap = { - done: chalk.green, - completed: chalk.green, - pending: chalk.yellow, - 'in-progress': chalk.blue - }; - const statusColor = statusColorMap[st.status || 'pending'] || chalk.white; - let subtaskDeps = 'None'; - if (st.dependencies && st.dependencies.length > 0) { - const formattedDeps = st.dependencies.map((depId) => { - // Use the original, unfiltered list for dependency status lookup - const sourceListForDeps = originalSubtasks || task.subtasks; - const foundDepSubtask = - typeof depId === 'number' && depId < 100 - ? sourceListForDeps.find((sub) => sub.id === depId) - : null; + // Populate table with the potentially filtered subtasks + task.subtasks.forEach((st) => { + const statusColorMap = { + done: chalk.green, + completed: chalk.green, + pending: chalk.yellow, + "in-progress": chalk.blue, + }; + const statusColor = statusColorMap[st.status || "pending"] || chalk.white; + let subtaskDeps = "None"; + if (st.dependencies && st.dependencies.length > 0) { + const formattedDeps = st.dependencies.map((depId) => { + // Use the original, unfiltered list for dependency status lookup + const sourceListForDeps = originalSubtasks || task.subtasks; + const foundDepSubtask = + typeof depId === "number" && depId < 100 + ? sourceListForDeps.find((sub) => sub.id === depId) + : null; - if (foundDepSubtask) { - const isDone = - foundDepSubtask.status === 'done' || - foundDepSubtask.status === 'completed'; - const isInProgress = foundDepSubtask.status === 'in-progress'; - const color = isDone - ? chalk.green.bold - : isInProgress - ? chalk.hex('#FFA500').bold - : chalk.red.bold; - return color(`${task.id}.${depId}`); - } else if (typeof depId === 'number' && depId < 100) { - return chalk.red(`${task.id}.${depId} (Not found)`); - } - return depId; // Assume it's a top-level task ID if not a number < 100 - }); - subtaskDeps = - formattedDeps.length === 1 - ? formattedDeps[0] - : formattedDeps.join(chalk.white(', ')); - } - subtaskTable.push([ - `${task.id}.${st.id}`, - statusColor(st.status || 'pending'), - st.title, - subtaskDeps - ]); - }); - console.log(subtaskTable.toString()); + if (foundDepSubtask) { + const isDone = + foundDepSubtask.status === "done" || + foundDepSubtask.status === "completed"; + const isInProgress = foundDepSubtask.status === "in-progress"; + const color = isDone + ? chalk.green.bold + : isInProgress + ? chalk.hex("#FFA500").bold + : chalk.red.bold; + return color(`${task.id}.${depId}`); + } else if (typeof depId === "number" && depId < 100) { + return chalk.red(`${task.id}.${depId} (Not found)`); + } + return depId; // Assume it's a top-level task ID if not a number < 100 + }); + subtaskDeps = + formattedDeps.length === 1 + ? formattedDeps[0] + : formattedDeps.join(chalk.white(", ")); + } + subtaskTable.push([ + `${task.id}.${st.id}`, + statusColor(st.status || "pending"), + st.title, + subtaskDeps, + ]); + }); + console.log(subtaskTable.toString()); - // Display filter summary line *immediately after the table* if a filter was applied - if (statusFilter && originalSubtaskCount !== null) { - console.log( - chalk.cyan( - ` Filtered by status: ${chalk.bold(statusFilter)}. Showing ${chalk.bold(task.subtasks.length)} of ${chalk.bold(originalSubtaskCount)} subtasks.` - ) - ); - // Add a newline for spacing before the progress bar if the filter line was shown - console.log(); - } - // --- Conditional Messages for No Subtasks Shown --- - } else if (statusFilter && originalSubtaskCount === 0) { - // Case where filter applied, but the parent task had 0 subtasks originally - console.log( - boxen( - chalk.yellow( - `No subtasks found matching status: ${statusFilter} (Task has no subtasks)` - ), - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - margin: { top: 1, bottom: 0 }, - borderColor: 'yellow', - borderStyle: 'round' - } - ) - ); - } else if ( - statusFilter && - originalSubtaskCount > 0 && - task.subtasks.length === 0 - ) { - // Case where filter applied, original subtasks existed, but none matched - console.log( - boxen( - chalk.yellow( - `No subtasks found matching status: ${statusFilter} (out of ${originalSubtaskCount} total)` - ), - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - margin: { top: 1, bottom: 0 }, - borderColor: 'yellow', - borderStyle: 'round' - } - ) - ); - } else if ( - !statusFilter && - (!originalSubtasks || originalSubtasks.length === 0) - ) { - // Case where NO filter applied AND the task genuinely has no subtasks - // Use the authoritative originalSubtasks if it exists (from filtering), else check task.subtasks - const actualSubtasks = originalSubtasks || task.subtasks; - if (!actualSubtasks || actualSubtasks.length === 0) { - console.log( - boxen( - chalk.yellow('No subtasks found. Consider breaking down this task:') + - '\n' + - chalk.white( - `Run: ${chalk.cyan(`task-master expand --id=${task.id}`)}` - ), - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'yellow', - borderStyle: 'round', - margin: { top: 1, bottom: 0 } - } - ) - ); - } - } + // Display filter summary line *immediately after the table* if a filter was applied + if (statusFilter && originalSubtaskCount !== null) { + console.log( + chalk.cyan( + ` Filtered by status: ${chalk.bold(statusFilter)}. Showing ${chalk.bold(task.subtasks.length)} of ${chalk.bold(originalSubtaskCount)} subtasks.` + ) + ); + // Add a newline for spacing before the progress bar if the filter line was shown + console.log(); + } + // --- Conditional Messages for No Subtasks Shown --- + } else if (statusFilter && originalSubtaskCount === 0) { + // Case where filter applied, but the parent task had 0 subtasks originally + console.log( + boxen( + chalk.yellow( + `No subtasks found matching status: ${statusFilter} (Task has no subtasks)` + ), + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 1, bottom: 0 }, + borderColor: "yellow", + borderStyle: "round", + } + ) + ); + } else if ( + statusFilter && + originalSubtaskCount > 0 && + task.subtasks.length === 0 + ) { + // Case where filter applied, original subtasks existed, but none matched + console.log( + boxen( + chalk.yellow( + `No subtasks found matching status: ${statusFilter} (out of ${originalSubtaskCount} total)` + ), + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 1, bottom: 0 }, + borderColor: "yellow", + borderStyle: "round", + } + ) + ); + } else if ( + !statusFilter && + (!originalSubtasks || originalSubtasks.length === 0) + ) { + // Case where NO filter applied AND the task genuinely has no subtasks + // Use the authoritative originalSubtasks if it exists (from filtering), else check task.subtasks + const actualSubtasks = originalSubtasks || task.subtasks; + if (!actualSubtasks || actualSubtasks.length === 0) { + console.log( + boxen( + chalk.yellow("No subtasks found. Consider breaking down this task:") + + "\n" + + chalk.white( + `Run: ${chalk.cyan(`task-master expand --id=${task.id}`)}` + ), + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "yellow", + borderStyle: "round", + margin: { top: 1, bottom: 0 }, + } + ) + ); + } + } - // --- Subtask Progress Bar Display (uses originalSubtasks or task.subtasks) --- - // Determine the list to use for progress calculation (always the original if available and filtering happened) - const subtasksForProgress = originalSubtasks || task.subtasks; // Use original if filtering occurred, else the potentially empty task.subtasks + // --- Subtask Progress Bar Display (uses originalSubtasks or task.subtasks) --- + // Determine the list to use for progress calculation (always the original if available and filtering happened) + const subtasksForProgress = originalSubtasks || task.subtasks; // Use original if filtering occurred, else the potentially empty task.subtasks - // Only show progress if there are actually subtasks - if (subtasksForProgress && subtasksForProgress.length > 0) { - const totalSubtasks = subtasksForProgress.length; - const completedSubtasks = subtasksForProgress.filter( - (st) => st.status === 'done' || st.status === 'completed' - ).length; + // Only show progress if there are actually subtasks + if (subtasksForProgress && subtasksForProgress.length > 0) { + const totalSubtasks = subtasksForProgress.length; + const completedSubtasks = subtasksForProgress.filter( + (st) => st.status === "done" || st.status === "completed" + ).length; - // Count other statuses from the original/complete list - const inProgressSubtasks = subtasksForProgress.filter( - (st) => st.status === 'in-progress' - ).length; - const pendingSubtasks = subtasksForProgress.filter( - (st) => st.status === 'pending' - ).length; - const blockedSubtasks = subtasksForProgress.filter( - (st) => st.status === 'blocked' - ).length; - const deferredSubtasks = subtasksForProgress.filter( - (st) => st.status === 'deferred' - ).length; - const cancelledSubtasks = subtasksForProgress.filter( - (st) => st.status === 'cancelled' - ).length; + // Count other statuses from the original/complete list + const inProgressSubtasks = subtasksForProgress.filter( + (st) => st.status === "in-progress" + ).length; + const pendingSubtasks = subtasksForProgress.filter( + (st) => st.status === "pending" + ).length; + const blockedSubtasks = subtasksForProgress.filter( + (st) => st.status === "blocked" + ).length; + const deferredSubtasks = subtasksForProgress.filter( + (st) => st.status === "deferred" + ).length; + const cancelledSubtasks = subtasksForProgress.filter( + (st) => st.status === "cancelled" + ).length; - const statusBreakdown = { - // Calculate breakdown based on the complete list - 'in-progress': (inProgressSubtasks / totalSubtasks) * 100, - pending: (pendingSubtasks / totalSubtasks) * 100, - blocked: (blockedSubtasks / totalSubtasks) * 100, - deferred: (deferredSubtasks / totalSubtasks) * 100, - cancelled: (cancelledSubtasks / totalSubtasks) * 100 - }; - const completionPercentage = (completedSubtasks / totalSubtasks) * 100; + const statusBreakdown = { + // Calculate breakdown based on the complete list + "in-progress": (inProgressSubtasks / totalSubtasks) * 100, + pending: (pendingSubtasks / totalSubtasks) * 100, + blocked: (blockedSubtasks / totalSubtasks) * 100, + deferred: (deferredSubtasks / totalSubtasks) * 100, + cancelled: (cancelledSubtasks / totalSubtasks) * 100, + }; + const completionPercentage = (completedSubtasks / totalSubtasks) * 100; - const availableWidth = process.stdout.columns || 80; - const boxPadding = 2; - const boxBorders = 2; - const percentTextLength = 5; - const progressBarLength = Math.max( - 20, - Math.min( - 60, - availableWidth - boxPadding - boxBorders - percentTextLength - 35 - ) - ); + const availableWidth = process.stdout.columns || 80; + const boxPadding = 2; + const boxBorders = 2; + const percentTextLength = 5; + const progressBarLength = Math.max( + 20, + Math.min( + 60, + availableWidth - boxPadding - boxBorders - percentTextLength - 35 + ) + ); - const statusCounts = - `${chalk.green('โœ“ Done:')} ${completedSubtasks} ${chalk.hex('#FFA500')('โ–บ In Progress:')} ${inProgressSubtasks} ${chalk.yellow('โ—‹ Pending:')} ${pendingSubtasks}\n` + - `${chalk.red('! Blocked:')} ${blockedSubtasks} ${chalk.gray('โฑ Deferred:')} ${deferredSubtasks} ${chalk.gray('โœ— Cancelled:')} ${cancelledSubtasks}`; + const statusCounts = + `${chalk.green("โœ“ Done:")} ${completedSubtasks} ${chalk.hex("#FFA500")("โ–บ In Progress:")} ${inProgressSubtasks} ${chalk.yellow("โ—‹ Pending:")} ${pendingSubtasks}\n` + + `${chalk.red("! Blocked:")} ${blockedSubtasks} ${chalk.gray("โฑ Deferred:")} ${deferredSubtasks} ${chalk.gray("โœ— Cancelled:")} ${cancelledSubtasks}`; - console.log( - boxen( - chalk.white.bold('Subtask Progress:') + - '\n\n' + - `${chalk.cyan('Completed:')} ${completedSubtasks}/${totalSubtasks} (${completionPercentage.toFixed(1)}%)\n` + - `${statusCounts}\n` + - `${chalk.cyan('Progress:')} ${createProgressBar(completionPercentage, progressBarLength, statusBreakdown)}`, - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'blue', - borderStyle: 'round', - margin: { top: 1, bottom: 0 }, - width: Math.min(availableWidth - 10, 100), - textAlignment: 'left' - } - ) - ); - } + console.log( + boxen( + chalk.white.bold("Subtask Progress:") + + "\n\n" + + `${chalk.cyan("Completed:")} ${completedSubtasks}/${totalSubtasks} (${completionPercentage.toFixed(1)}%)\n` + + `${statusCounts}\n` + + `${chalk.cyan("Progress:")} ${createProgressBar(completionPercentage, progressBarLength, statusBreakdown)}`, + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "blue", + borderStyle: "round", + margin: { top: 1, bottom: 0 }, + width: Math.min(availableWidth - 10, 100), + textAlignment: "left", + } + ) + ); + } - // --- Suggested Actions --- - console.log( - boxen( - chalk.white.bold('Suggested Actions:') + - '\n' + - `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}\n` + - `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.id} --status=done`)}\n` + - // Determine action 3 based on whether subtasks *exist* (use the source list for progress) - (subtasksForProgress && subtasksForProgress.length > 0 - ? `${chalk.cyan('3.')} Update subtask status: ${chalk.yellow(`task-master set-status --id=${task.id}.1 --status=done`)}` // Example uses .1 - : `${chalk.cyan('3.')} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${task.id}`)}`), - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: 'green', - borderStyle: 'round', - margin: { top: 1 } - } - ) - ); + // --- Suggested Actions --- + console.log( + boxen( + chalk.white.bold("Suggested Actions:") + + "\n" + + `${chalk.cyan("1.")} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}\n` + + `${chalk.cyan("2.")} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.id} --status=done`)}\n` + + // Determine action 3 based on whether subtasks *exist* (use the source list for progress) + (subtasksForProgress && subtasksForProgress.length > 0 + ? `${chalk.cyan("3.")} Update subtask status: ${chalk.yellow(`task-master set-status --id=${task.id}.1 --status=done`)}` // Example uses .1 + : `${chalk.cyan("3.")} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${task.id}`)}`), + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: "green", + borderStyle: "round", + margin: { top: 1 }, + } + ) + ); } /** @@ -1495,251 +1495,251 @@ async function displayTaskById( * @param {string} reportPath - Path to the complexity report file */ async function displayComplexityReport(reportPath) { - displayBanner(); + displayBanner(); - // Check if the report exists - if (!fs.existsSync(reportPath)) { - console.log( - boxen( - chalk.yellow(`No complexity report found at ${reportPath}\n\n`) + - 'Would you like to generate one now?', - { - padding: 1, - borderColor: 'yellow', - borderStyle: 'round', - margin: { top: 1 } - } - ) - ); + // Check if the report exists + if (!fs.existsSync(reportPath)) { + console.log( + boxen( + chalk.yellow(`No complexity report found at ${reportPath}\n\n`) + + "Would you like to generate one now?", + { + padding: 1, + borderColor: "yellow", + borderStyle: "round", + margin: { top: 1 }, + } + ) + ); - const readline = require('readline').createInterface({ - input: process.stdin, - output: process.stdout - }); + const readline = require("readline").createInterface({ + input: process.stdin, + output: process.stdout, + }); - const answer = await new Promise((resolve) => { - readline.question( - chalk.cyan('Generate complexity report? (y/n): '), - resolve - ); - }); - readline.close(); + const answer = await new Promise((resolve) => { + readline.question( + chalk.cyan("Generate complexity report? (y/n): "), + resolve + ); + }); + readline.close(); - if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') { - // Call the analyze-complexity command - console.log(chalk.blue('Generating complexity report...')); - const tasksPath = TASKMASTER_TASKS_FILE; - if (!fs.existsSync(tasksPath)) { - console.error( - 'โŒ No tasks.json file found. Please run "task-master init" or create a tasks.json file.' - ); - return null; - } + if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") { + // Call the analyze-complexity command + console.log(chalk.blue("Generating complexity report...")); + const tasksPath = TASKMASTER_TASKS_FILE; + if (!fs.existsSync(tasksPath)) { + console.error( + 'โŒ No tasks.json file found. Please run "task-master init" or create a tasks.json file.' + ); + return null; + } - await analyzeTaskComplexity({ - output: reportPath, - research: false, // Default to no research for speed - file: tasksPath - }); - // Read the newly generated report - return displayComplexityReport(reportPath); - } else { - console.log(chalk.yellow('Report generation cancelled.')); - return; - } - } + await analyzeTaskComplexity({ + output: reportPath, + research: false, // Default to no research for speed + file: tasksPath, + }); + // Read the newly generated report + return displayComplexityReport(reportPath); + } else { + console.log(chalk.yellow("Report generation cancelled.")); + return; + } + } - // Read the report - let report; - try { - report = JSON.parse(fs.readFileSync(reportPath, 'utf8')); - } catch (error) { - log('error', `Error reading complexity report: ${error.message}`); - return; - } + // Read the report + let report; + try { + report = JSON.parse(fs.readFileSync(reportPath, "utf8")); + } catch (error) { + log("error", `Error reading complexity report: ${error.message}`); + return; + } - // Display report header - console.log( - boxen(chalk.white.bold('Task Complexity Analysis Report'), { - padding: 1, - borderColor: 'blue', - borderStyle: 'round', - margin: { top: 1, bottom: 1 } - }) - ); + // Display report header + console.log( + boxen(chalk.white.bold("Task Complexity Analysis Report"), { + padding: 1, + borderColor: "blue", + borderStyle: "round", + margin: { top: 1, bottom: 1 }, + }) + ); - // Display metadata - const metaTable = new Table({ - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { - mid: '', - 'left-mid': '', - 'mid-mid': '', - 'right-mid': '' - }, - colWidths: [20, 50] - }); + // Display metadata + const metaTable = new Table({ + style: { + head: [], + border: [], + "padding-top": 0, + "padding-bottom": 0, + compact: true, + }, + chars: { + mid: "", + "left-mid": "", + "mid-mid": "", + "right-mid": "", + }, + colWidths: [20, 50], + }); - metaTable.push( - [ - chalk.cyan.bold('Generated:'), - new Date(report.meta.generatedAt).toLocaleString() - ], - [chalk.cyan.bold('Tasks Analyzed:'), report.meta.tasksAnalyzed], - [chalk.cyan.bold('Threshold Score:'), report.meta.thresholdScore], - [chalk.cyan.bold('Project:'), report.meta.projectName], - [ - chalk.cyan.bold('Research-backed:'), - report.meta.usedResearch ? 'Yes' : 'No' - ] - ); + metaTable.push( + [ + chalk.cyan.bold("Generated:"), + new Date(report.meta.generatedAt).toLocaleString(), + ], + [chalk.cyan.bold("Tasks Analyzed:"), report.meta.tasksAnalyzed], + [chalk.cyan.bold("Threshold Score:"), report.meta.thresholdScore], + [chalk.cyan.bold("Project:"), report.meta.projectName], + [ + chalk.cyan.bold("Research-backed:"), + report.meta.usedResearch ? "Yes" : "No", + ] + ); - console.log(metaTable.toString()); + console.log(metaTable.toString()); - // Sort tasks by complexity score (highest first) - const sortedTasks = [...report.complexityAnalysis].sort( - (a, b) => b.complexityScore - a.complexityScore - ); + // Sort tasks by complexity score (highest first) + const sortedTasks = [...report.complexityAnalysis].sort( + (a, b) => b.complexityScore - a.complexityScore + ); - // Determine which tasks need expansion based on threshold - const tasksNeedingExpansion = sortedTasks.filter( - (task) => task.complexityScore >= report.meta.thresholdScore - ); - const simpleTasks = sortedTasks.filter( - (task) => task.complexityScore < report.meta.thresholdScore - ); + // Determine which tasks need expansion based on threshold + const tasksNeedingExpansion = sortedTasks.filter( + (task) => task.complexityScore >= report.meta.thresholdScore + ); + const simpleTasks = sortedTasks.filter( + (task) => task.complexityScore < report.meta.thresholdScore + ); - // Create progress bar to show complexity distribution - const complexityDistribution = [0, 0, 0]; // Low (0-4), Medium (5-7), High (8-10) - sortedTasks.forEach((task) => { - if (task.complexityScore < 5) complexityDistribution[0]++; - else if (task.complexityScore < 8) complexityDistribution[1]++; - else complexityDistribution[2]++; - }); + // Create progress bar to show complexity distribution + const complexityDistribution = [0, 0, 0]; // Low (0-4), Medium (5-7), High (8-10) + sortedTasks.forEach((task) => { + if (task.complexityScore < 5) complexityDistribution[0]++; + else if (task.complexityScore < 8) complexityDistribution[1]++; + else complexityDistribution[2]++; + }); - const percentLow = Math.round( - (complexityDistribution[0] / sortedTasks.length) * 100 - ); - const percentMedium = Math.round( - (complexityDistribution[1] / sortedTasks.length) * 100 - ); - const percentHigh = Math.round( - (complexityDistribution[2] / sortedTasks.length) * 100 - ); + const percentLow = Math.round( + (complexityDistribution[0] / sortedTasks.length) * 100 + ); + const percentMedium = Math.round( + (complexityDistribution[1] / sortedTasks.length) * 100 + ); + const percentHigh = Math.round( + (complexityDistribution[2] / sortedTasks.length) * 100 + ); - console.log( - boxen( - chalk.white.bold('Complexity Distribution\n\n') + - `${chalk.green.bold('Low (1-4):')} ${complexityDistribution[0]} tasks (${percentLow}%)\n` + - `${chalk.yellow.bold('Medium (5-7):')} ${complexityDistribution[1]} tasks (${percentMedium}%)\n` + - `${chalk.red.bold('High (8-10):')} ${complexityDistribution[2]} tasks (${percentHigh}%)`, - { - padding: 1, - borderColor: 'cyan', - borderStyle: 'round', - margin: { top: 1, bottom: 1 } - } - ) - ); + console.log( + boxen( + chalk.white.bold("Complexity Distribution\n\n") + + `${chalk.green.bold("Low (1-4):")} ${complexityDistribution[0]} tasks (${percentLow}%)\n` + + `${chalk.yellow.bold("Medium (5-7):")} ${complexityDistribution[1]} tasks (${percentMedium}%)\n` + + `${chalk.red.bold("High (8-10):")} ${complexityDistribution[2]} tasks (${percentHigh}%)`, + { + padding: 1, + borderColor: "cyan", + borderStyle: "round", + margin: { top: 1, bottom: 1 }, + } + ) + ); - // Get terminal width - const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect + // Get terminal width + const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect - // Calculate dynamic column widths - const idWidth = 12; - const titleWidth = Math.floor(terminalWidth * 0.25); // 25% of width - const scoreWidth = 8; - const subtasksWidth = 8; - // Command column gets the remaining space (minus some buffer for borders) - const commandWidth = - terminalWidth - idWidth - titleWidth - scoreWidth - subtasksWidth - 10; + // Calculate dynamic column widths + const idWidth = 12; + const titleWidth = Math.floor(terminalWidth * 0.25); // 25% of width + const scoreWidth = 8; + const subtasksWidth = 8; + // Command column gets the remaining space (minus some buffer for borders) + const commandWidth = + terminalWidth - idWidth - titleWidth - scoreWidth - subtasksWidth - 10; - // Create table with new column widths and word wrapping - const complexTable = new Table({ - head: [ - chalk.yellow.bold('ID'), - chalk.yellow.bold('Title'), - chalk.yellow.bold('Score'), - chalk.yellow.bold('Subtasks'), - chalk.yellow.bold('Expansion Command') - ], - colWidths: [idWidth, titleWidth, scoreWidth, subtasksWidth, commandWidth], - style: { head: [], border: [] }, - wordWrap: true, - wrapOnWordBoundary: true - }); + // Create table with new column widths and word wrapping + const complexTable = new Table({ + head: [ + chalk.yellow.bold("ID"), + chalk.yellow.bold("Title"), + chalk.yellow.bold("Score"), + chalk.yellow.bold("Subtasks"), + chalk.yellow.bold("Expansion Command"), + ], + colWidths: [idWidth, titleWidth, scoreWidth, subtasksWidth, commandWidth], + style: { head: [], border: [] }, + wordWrap: true, + wrapOnWordBoundary: true, + }); - // When adding rows, don't truncate the expansion command - tasksNeedingExpansion.forEach((task) => { - const expansionCommand = `task-master expand --id=${task.taskId} --num=${task.recommendedSubtasks}${task.expansionPrompt ? ` --prompt="${task.expansionPrompt}"` : ''}`; + // When adding rows, don't truncate the expansion command + tasksNeedingExpansion.forEach((task) => { + const expansionCommand = `task-master expand --id=${task.taskId} --num=${task.recommendedSubtasks}${task.expansionPrompt ? ` --prompt="${task.expansionPrompt}"` : ""}`; - complexTable.push([ - task.taskId, - truncate(task.taskTitle, titleWidth - 3), // Still truncate title for readability - getComplexityWithColor(task.complexityScore), - task.recommendedSubtasks, - chalk.cyan(expansionCommand) // Don't truncate - allow wrapping - ]); - }); + complexTable.push([ + task.taskId, + truncate(task.taskTitle, titleWidth - 3), // Still truncate title for readability + getComplexityWithColor(task.complexityScore), + task.recommendedSubtasks, + chalk.cyan(expansionCommand), // Don't truncate - allow wrapping + ]); + }); - console.log(complexTable.toString()); + console.log(complexTable.toString()); - // Create table for simple tasks - if (simpleTasks.length > 0) { - console.log( - boxen(chalk.green.bold(`Simple Tasks (${simpleTasks.length})`), { - padding: { left: 2, right: 2, top: 0, bottom: 0 }, - margin: { top: 1, bottom: 0 }, - borderColor: 'green', - borderStyle: 'round' - }) - ); + // Create table for simple tasks + if (simpleTasks.length > 0) { + console.log( + boxen(chalk.green.bold(`Simple Tasks (${simpleTasks.length})`), { + padding: { left: 2, right: 2, top: 0, bottom: 0 }, + margin: { top: 1, bottom: 0 }, + borderColor: "green", + borderStyle: "round", + }) + ); - const simpleTable = new Table({ - head: [ - chalk.green.bold('ID'), - chalk.green.bold('Title'), - chalk.green.bold('Score'), - chalk.green.bold('Reasoning') - ], - colWidths: [5, 40, 8, 50], - style: { head: [], border: [] } - }); + const simpleTable = new Table({ + head: [ + chalk.green.bold("ID"), + chalk.green.bold("Title"), + chalk.green.bold("Score"), + chalk.green.bold("Reasoning"), + ], + colWidths: [5, 40, 8, 50], + style: { head: [], border: [] }, + }); - simpleTasks.forEach((task) => { - simpleTable.push([ - task.taskId, - truncate(task.taskTitle, 37), - getComplexityWithColor(task.complexityScore), - truncate(task.reasoning, 47) - ]); - }); + simpleTasks.forEach((task) => { + simpleTable.push([ + task.taskId, + truncate(task.taskTitle, 37), + getComplexityWithColor(task.complexityScore), + truncate(task.reasoning, 47), + ]); + }); - console.log(simpleTable.toString()); - } + console.log(simpleTable.toString()); + } - // Show action suggestions - console.log( - boxen( - chalk.white.bold('Suggested Actions:') + - '\n\n' + - `${chalk.cyan('1.')} Expand all complex tasks: ${chalk.yellow(`task-master expand --all`)}\n` + - `${chalk.cyan('2.')} Expand a specific task: ${chalk.yellow(`task-master expand --id=<id>`)}\n` + - `${chalk.cyan('3.')} Regenerate with research: ${chalk.yellow(`task-master analyze-complexity --research`)}`, - { - padding: 1, - borderColor: 'cyan', - borderStyle: 'round', - margin: { top: 1 } - } - ) - ); + // Show action suggestions + console.log( + boxen( + chalk.white.bold("Suggested Actions:") + + "\n\n" + + `${chalk.cyan("1.")} Expand all complex tasks: ${chalk.yellow(`task-master expand --all`)}\n` + + `${chalk.cyan("2.")} Expand a specific task: ${chalk.yellow(`task-master expand --id=<id>`)}\n` + + `${chalk.cyan("3.")} Regenerate with research: ${chalk.yellow(`task-master analyze-complexity --research`)}`, + { + padding: 1, + borderColor: "cyan", + borderStyle: "round", + margin: { top: 1 }, + } + ) + ); } /** @@ -1748,21 +1748,21 @@ async function displayComplexityReport(reportPath) { * @returns {string} Generated prompt */ function generateComplexityAnalysisPrompt(tasksData) { - const defaultSubtasks = getDefaultSubtasks(null); // Use the getter - return `Analyze the complexity of the following tasks and provide recommendations for subtask breakdown: + const defaultSubtasks = getDefaultSubtasks(null); // Use the getter + return `Analyze the complexity of the following tasks and provide recommendations for subtask breakdown: ${tasksData.tasks - .map( - (task) => ` + .map( + (task) => ` Task ID: ${task.id} Title: ${task.title} Description: ${task.description} Details: ${task.details} Dependencies: ${JSON.stringify(task.dependencies || [])} -Priority: ${task.priority || 'medium'} +Priority: ${task.priority || "medium"} ` - ) - .join('\n---\n')} + ) + .join("\n---\n")} Analyze each task and return a JSON array with the following structure for each task: [ @@ -1787,39 +1787,39 @@ IMPORTANT: Make sure to include an analysis for EVERY task listed above, with th * @returns {Promise<boolean>} - Promise resolving to true if user confirms, false otherwise */ async function confirmTaskOverwrite(tasksPath) { - console.log( - boxen( - chalk.yellow( - "It looks like you've already generated tasks for this project.\n" - ) + - chalk.yellow( - 'Executing this command will overwrite any existing tasks.' - ), - { - padding: 1, - borderColor: 'yellow', - borderStyle: 'round', - margin: { top: 1 } - } - ) - ); + console.log( + boxen( + chalk.yellow( + "It looks like you've already generated tasks for this project.\n" + ) + + chalk.yellow( + "Executing this command will overwrite any existing tasks." + ), + { + padding: 1, + borderColor: "yellow", + borderStyle: "round", + margin: { top: 1 }, + } + ) + ); - // Use dynamic import to get the readline module - const readline = await import('readline'); - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout - }); + // Use dynamic import to get the readline module + const readline = await import("readline"); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout, + }); - const answer = await new Promise((resolve) => { - rl.question( - chalk.cyan('Are you sure you wish to continue? (y/N): '), - resolve - ); - }); - rl.close(); + const answer = await new Promise((resolve) => { + rl.question( + chalk.cyan("Are you sure you wish to continue? (y/N): "), + resolve + ); + }); + rl.close(); - return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'; + return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes"; } /** @@ -1827,75 +1827,75 @@ async function confirmTaskOverwrite(tasksPath) { * @param {Array<{provider: string, cli: boolean, mcp: boolean}>} statusReport - The report generated by getApiKeyStatusReport. */ function displayApiKeyStatus(statusReport) { - if (!statusReport || statusReport.length === 0) { - console.log(chalk.yellow('No API key status information available.')); - return; - } + if (!statusReport || statusReport.length === 0) { + console.log(chalk.yellow("No API key status information available.")); + return; + } - const table = new Table({ - head: [ - chalk.cyan('Provider'), - chalk.cyan('CLI Key (.env)'), - chalk.cyan('MCP Key (mcp.json)') - ], - colWidths: [15, 20, 25], - chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' } - }); + const table = new Table({ + head: [ + chalk.cyan("Provider"), + chalk.cyan("CLI Key (.env)"), + chalk.cyan("MCP Key (mcp.json)"), + ], + colWidths: [15, 20, 25], + chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, + }); - statusReport.forEach(({ provider, cli, mcp }) => { - const cliStatus = cli ? chalk.green('โœ… Found') : chalk.red('โŒ Missing'); - const mcpStatus = mcp ? chalk.green('โœ… Found') : chalk.red('โŒ Missing'); - // Capitalize provider name for display - const providerName = provider.charAt(0).toUpperCase() + provider.slice(1); - table.push([providerName, cliStatus, mcpStatus]); - }); + statusReport.forEach(({ provider, cli, mcp }) => { + const cliStatus = cli ? chalk.green("โœ… Found") : chalk.red("โŒ Missing"); + const mcpStatus = mcp ? chalk.green("โœ… Found") : chalk.red("โŒ Missing"); + // Capitalize provider name for display + const providerName = provider.charAt(0).toUpperCase() + provider.slice(1); + table.push([providerName, cliStatus, mcpStatus]); + }); - console.log(chalk.bold('\n๐Ÿ”‘ API Key Status:')); - console.log(table.toString()); - console.log( - chalk.gray( - ` Note: Some providers (e.g., Azure, Ollama) may require additional endpoint configuration in ${TASKMASTER_CONFIG_FILE}.` - ) - ); + console.log(chalk.bold("\n๐Ÿ”‘ API Key Status:")); + console.log(table.toString()); + console.log( + chalk.gray( + ` Note: Some providers (e.g., Azure, Ollama) may require additional endpoint configuration in ${TASKMASTER_CONFIG_FILE}.` + ) + ); } // --- Formatting Helpers (Potentially move some to utils.js if reusable) --- const formatSweScoreWithTertileStars = (score, allModels) => { - // ... (Implementation from previous version or refine) ... - if (score === null || score === undefined || score <= 0) return 'N/A'; - const formattedPercentage = `${(score * 100).toFixed(1)}%`; + // ... (Implementation from previous version or refine) ... + if (score === null || score === undefined || score <= 0) return "N/A"; + const formattedPercentage = `${(score * 100).toFixed(1)}%`; - const validScores = allModels - .map((m) => m.sweScore) - .filter((s) => s !== null && s !== undefined && s > 0); - const sortedScores = [...validScores].sort((a, b) => b - a); - const n = sortedScores.length; - let stars = chalk.gray('โ˜†โ˜†โ˜†'); + const validScores = allModels + .map((m) => m.sweScore) + .filter((s) => s !== null && s !== undefined && s > 0); + const sortedScores = [...validScores].sort((a, b) => b - a); + const n = sortedScores.length; + let stars = chalk.gray("โ˜†โ˜†โ˜†"); - if (n > 0) { - const topThirdIndex = Math.max(0, Math.floor(n / 3) - 1); - const midThirdIndex = Math.max(0, Math.floor((2 * n) / 3) - 1); - if (score >= sortedScores[topThirdIndex]) stars = chalk.yellow('โ˜…โ˜…โ˜…'); - else if (score >= sortedScores[midThirdIndex]) - stars = chalk.yellow('โ˜…โ˜…') + chalk.gray('โ˜†'); - else stars = chalk.yellow('โ˜…') + chalk.gray('โ˜†โ˜†'); - } - return `${formattedPercentage} ${stars}`; + if (n > 0) { + const topThirdIndex = Math.max(0, Math.floor(n / 3) - 1); + const midThirdIndex = Math.max(0, Math.floor((2 * n) / 3) - 1); + if (score >= sortedScores[topThirdIndex]) stars = chalk.yellow("โ˜…โ˜…โ˜…"); + else if (score >= sortedScores[midThirdIndex]) + stars = chalk.yellow("โ˜…โ˜…") + chalk.gray("โ˜†"); + else stars = chalk.yellow("โ˜…") + chalk.gray("โ˜†โ˜†"); + } + return `${formattedPercentage} ${stars}`; }; const formatCost = (costObj) => { - // ... (Implementation from previous version or refine) ... - if (!costObj) return 'N/A'; - if (costObj.input === 0 && costObj.output === 0) { - return chalk.green('Free'); - } - const formatSingleCost = (costValue) => { - if (costValue === null || costValue === undefined) return 'N/A'; - const isInteger = Number.isInteger(costValue); - return `$${costValue.toFixed(isInteger ? 0 : 2)}`; - }; - return `${formatSingleCost(costObj.input)} in, ${formatSingleCost(costObj.output)} out`; + // ... (Implementation from previous version or refine) ... + if (!costObj) return "N/A"; + if (costObj.input === 0 && costObj.output === 0) { + return chalk.green("Free"); + } + const formatSingleCost = (costValue) => { + if (costValue === null || costValue === undefined) return "N/A"; + const isInteger = Number.isInteger(costValue); + return `$${costValue.toFixed(isInteger ? 0 : 2)}`; + }; + return `${formatSingleCost(costObj.input)} in, ${formatSingleCost(costObj.output)} out`; }; // --- Display Functions --- @@ -1906,63 +1906,63 @@ const formatCost = (costObj) => { * @param {AvailableModel[]} allAvailableModels - Needed for SWE score tertiles. */ function displayModelConfiguration(configData, allAvailableModels = []) { - console.log(chalk.cyan.bold('\nActive Model Configuration:')); - const active = configData.activeModels; - const activeTable = new Table({ - head: [ - 'Role', - 'Provider', - 'Model ID', - 'SWE Score', - 'Cost ($/1M tkns)' - // 'API Key Status' // Removed, handled by separate displayApiKeyStatus - ].map((h) => chalk.cyan.bold(h)), - colWidths: [10, 14, 30, 18, 20 /*, 28 */], // Adjusted widths - style: { head: ['cyan', 'bold'] } - }); + console.log(chalk.cyan.bold("\nActive Model Configuration:")); + const active = configData.activeModels; + const activeTable = new Table({ + head: [ + "Role", + "Provider", + "Model ID", + "SWE Score", + "Cost ($/1M tkns)", + // 'API Key Status' // Removed, handled by separate displayApiKeyStatus + ].map((h) => chalk.cyan.bold(h)), + colWidths: [10, 14, 30, 18, 20 /*, 28 */], // Adjusted widths + style: { head: ["cyan", "bold"] }, + }); - activeTable.push([ - chalk.white('Main'), - active.main.provider, - active.main.modelId, - formatSweScoreWithTertileStars(active.main.sweScore, allAvailableModels), - formatCost(active.main.cost) - // getCombinedStatus(active.main.keyStatus) // Removed - ]); - activeTable.push([ - chalk.white('Research'), - active.research.provider, - active.research.modelId, - formatSweScoreWithTertileStars( - active.research.sweScore, - allAvailableModels - ), - formatCost(active.research.cost) - // getCombinedStatus(active.research.keyStatus) // Removed - ]); - if (active.fallback && active.fallback.provider && active.fallback.modelId) { - activeTable.push([ - chalk.white('Fallback'), - active.fallback.provider, - active.fallback.modelId, - formatSweScoreWithTertileStars( - active.fallback.sweScore, - allAvailableModels - ), - formatCost(active.fallback.cost) - // getCombinedStatus(active.fallback.keyStatus) // Removed - ]); - } else { - activeTable.push([ - chalk.white('Fallback'), - chalk.gray('-'), - chalk.gray('(Not Set)'), - chalk.gray('-'), - chalk.gray('-') - // chalk.gray('-') // Removed - ]); - } - console.log(activeTable.toString()); + activeTable.push([ + chalk.white("Main"), + active.main.provider, + active.main.modelId, + formatSweScoreWithTertileStars(active.main.sweScore, allAvailableModels), + formatCost(active.main.cost), + // getCombinedStatus(active.main.keyStatus) // Removed + ]); + activeTable.push([ + chalk.white("Research"), + active.research.provider, + active.research.modelId, + formatSweScoreWithTertileStars( + active.research.sweScore, + allAvailableModels + ), + formatCost(active.research.cost), + // getCombinedStatus(active.research.keyStatus) // Removed + ]); + if (active.fallback && active.fallback.provider && active.fallback.modelId) { + activeTable.push([ + chalk.white("Fallback"), + active.fallback.provider, + active.fallback.modelId, + formatSweScoreWithTertileStars( + active.fallback.sweScore, + allAvailableModels + ), + formatCost(active.fallback.cost), + // getCombinedStatus(active.fallback.keyStatus) // Removed + ]); + } else { + activeTable.push([ + chalk.white("Fallback"), + chalk.gray("-"), + chalk.gray("(Not Set)"), + chalk.gray("-"), + chalk.gray("-"), + // chalk.gray('-') // Removed + ]); + } + console.log(activeTable.toString()); } /** @@ -1970,64 +1970,64 @@ function displayModelConfiguration(configData, allAvailableModels = []) { * @param {AvailableModel[]} availableModels - List of available models. */ function displayAvailableModels(availableModels) { - if (!availableModels || availableModels.length === 0) { - console.log( - chalk.gray('\n(No other models available or all are configured)') - ); - return; - } + if (!availableModels || availableModels.length === 0) { + console.log( + chalk.gray("\n(No other models available or all are configured)") + ); + return; + } - console.log(chalk.cyan.bold('\nOther Available Models:')); - const availableTable = new Table({ - head: ['Provider', 'Model ID', 'SWE Score', 'Cost ($/1M tkns)'].map((h) => - chalk.cyan.bold(h) - ), - colWidths: [15, 40, 18, 25], - style: { head: ['cyan', 'bold'] } - }); + console.log(chalk.cyan.bold("\nOther Available Models:")); + const availableTable = new Table({ + head: ["Provider", "Model ID", "SWE Score", "Cost ($/1M tkns)"].map((h) => + chalk.cyan.bold(h) + ), + colWidths: [15, 40, 18, 25], + style: { head: ["cyan", "bold"] }, + }); - availableModels.forEach((model) => { - availableTable.push([ - model.provider, - model.modelId, - formatSweScoreWithTertileStars(model.sweScore, availableModels), // Pass itself for comparison - formatCost(model.cost) - ]); - }); - console.log(availableTable.toString()); + availableModels.forEach((model) => { + availableTable.push([ + model.provider, + model.modelId, + formatSweScoreWithTertileStars(model.sweScore, availableModels), // Pass itself for comparison + formatCost(model.cost), + ]); + }); + console.log(availableTable.toString()); - // --- Suggested Actions Section (moved here from models command) --- - console.log( - boxen( - chalk.white.bold('Next Steps:') + - '\n' + - chalk.cyan( - `1. Set main model: ${chalk.yellow('task-master models --set-main <model_id>')}` - ) + - '\n' + - chalk.cyan( - `2. Set research model: ${chalk.yellow('task-master models --set-research <model_id>')}` - ) + - '\n' + - chalk.cyan( - `3. Set fallback model: ${chalk.yellow('task-master models --set-fallback <model_id>')}` - ) + - '\n' + - chalk.cyan( - `4. Run interactive setup: ${chalk.yellow('task-master models --setup')}` - ) + - '\n' + - chalk.cyan( - `5. Use custom ollama/openrouter models: ${chalk.yellow('task-master models --openrouter|ollama --set-main|research|fallback <model_id>')}` - ), - { - padding: 1, - borderColor: 'yellow', - borderStyle: 'round', - margin: { top: 1 } - } - ) - ); + // --- Suggested Actions Section (moved here from models command) --- + console.log( + boxen( + chalk.white.bold("Next Steps:") + + "\n" + + chalk.cyan( + `1. Set main model: ${chalk.yellow("task-master models --set-main <model_id>")}` + ) + + "\n" + + chalk.cyan( + `2. Set research model: ${chalk.yellow("task-master models --set-research <model_id>")}` + ) + + "\n" + + chalk.cyan( + `3. Set fallback model: ${chalk.yellow("task-master models --set-fallback <model_id>")}` + ) + + "\n" + + chalk.cyan( + `4. Run interactive setup: ${chalk.yellow("task-master models --setup")}` + ) + + "\n" + + chalk.cyan( + `5. Use custom ollama/openrouter models: ${chalk.yellow("task-master models --openrouter|ollama --set-main|research|fallback <model_id>")}` + ), + { + padding: 1, + borderColor: "yellow", + borderStyle: "round", + margin: { top: 1 }, + } + ) + ); } /** @@ -2035,63 +2035,63 @@ function displayAvailableModels(availableModels) { * @param {object} telemetryData - The telemetry data object. * @param {string} outputType - 'cli' or 'mcp' (though typically only called for 'cli'). */ -function displayAiUsageSummary(telemetryData, outputType = 'cli') { - if ( - (outputType !== 'cli' && outputType !== 'text') || - !telemetryData || - isSilentMode() - ) { - return; // Only display for CLI and if data exists and not in silent mode - } +function displayAiUsageSummary(telemetryData, outputType = "cli") { + if ( + (outputType !== "cli" && outputType !== "text") || + !telemetryData || + isSilentMode() + ) { + return; // Only display for CLI and if data exists and not in silent mode + } - const { - modelUsed, - providerName, - inputTokens, - outputTokens, - totalTokens, - totalCost, - commandName - } = telemetryData; + const { + modelUsed, + providerName, + inputTokens, + outputTokens, + totalTokens, + totalCost, + commandName, + } = telemetryData; - let summary = chalk.bold.blue('AI Usage Summary:') + '\n'; - summary += chalk.gray(` Command: ${commandName}\n`); - summary += chalk.gray(` Provider: ${providerName}\n`); - summary += chalk.gray(` Model: ${modelUsed}\n`); - summary += chalk.gray( - ` Tokens: ${totalTokens} (Input: ${inputTokens}, Output: ${outputTokens})\n` - ); - summary += chalk.gray(` Est. Cost: $${totalCost.toFixed(6)}`); + let summary = chalk.bold.blue("AI Usage Summary:") + "\n"; + summary += chalk.gray(` Command: ${commandName}\n`); + summary += chalk.gray(` Provider: ${providerName}\n`); + summary += chalk.gray(` Model: ${modelUsed}\n`); + summary += chalk.gray( + ` Tokens: ${totalTokens} (Input: ${inputTokens}, Output: ${outputTokens})\n` + ); + summary += chalk.gray(` Est. Cost: $${totalCost.toFixed(6)}`); - console.log( - boxen(summary, { - padding: 1, - margin: { top: 1 }, - borderColor: 'blue', - borderStyle: 'round', - title: '๐Ÿ’ก Telemetry', - titleAlignment: 'center' - }) - ); + console.log( + boxen(summary, { + padding: 1, + margin: { top: 1 }, + borderColor: "blue", + borderStyle: "round", + title: "๐Ÿ’ก Telemetry", + titleAlignment: "center", + }) + ); } // Export UI functions export { - displayBanner, - startLoadingIndicator, - stopLoadingIndicator, - createProgressBar, - getStatusWithColor, - formatDependenciesWithStatus, - displayHelp, - getComplexityWithColor, - displayNextTask, - displayTaskById, - displayComplexityReport, - generateComplexityAnalysisPrompt, - confirmTaskOverwrite, - displayApiKeyStatus, - displayModelConfiguration, - displayAvailableModels, - displayAiUsageSummary + displayBanner, + startLoadingIndicator, + stopLoadingIndicator, + createProgressBar, + getStatusWithColor, + formatDependenciesWithStatus, + displayHelp, + getComplexityWithColor, + displayNextTask, + displayTaskById, + displayComplexityReport, + generateComplexityAnalysisPrompt, + confirmTaskOverwrite, + displayApiKeyStatus, + displayModelConfiguration, + displayAvailableModels, + displayAiUsageSummary, }; diff --git a/tests/unit/ui.test.js b/tests/unit/ui.test.js index 8be90e1d..dbab8ea8 100644 --- a/tests/unit/ui.test.js +++ b/tests/unit/ui.test.js @@ -2,245 +2,245 @@ * UI module tests */ -import { jest } from '@jest/globals'; +import { jest } from "@jest/globals"; import { - getStatusWithColor, - formatDependenciesWithStatus, - createProgressBar, - getComplexityWithColor -} from '../../scripts/modules/ui.js'; -import { sampleTasks } from '../fixtures/sample-tasks.js'; + getStatusWithColor, + formatDependenciesWithStatus, + createProgressBar, + getComplexityWithColor, +} from "../../scripts/modules/ui.js"; +import { sampleTasks } from "../fixtures/sample-tasks.js"; // Mock dependencies -jest.mock('chalk', () => { - const origChalkFn = (text) => text; - const chalk = origChalkFn; - chalk.green = (text) => text; // Return text as-is for status functions - chalk.yellow = (text) => text; - chalk.red = (text) => text; - chalk.cyan = (text) => text; - chalk.blue = (text) => text; - chalk.gray = (text) => text; - chalk.white = (text) => text; - chalk.bold = (text) => text; - chalk.dim = (text) => text; +jest.mock("chalk", () => { + const origChalkFn = (text) => text; + const chalk = origChalkFn; + chalk.green = (text) => text; // Return text as-is for status functions + chalk.yellow = (text) => text; + chalk.red = (text) => text; + chalk.cyan = (text) => text; + chalk.blue = (text) => text; + chalk.gray = (text) => text; + chalk.white = (text) => text; + chalk.bold = (text) => text; + chalk.dim = (text) => text; - // Add hex and other methods - chalk.hex = () => origChalkFn; - chalk.rgb = () => origChalkFn; + // Add hex and other methods + chalk.hex = () => origChalkFn; + chalk.rgb = () => origChalkFn; - return chalk; + return chalk; }); -jest.mock('figlet', () => ({ - textSync: jest.fn(() => 'Task Master Banner') +jest.mock("figlet", () => ({ + textSync: jest.fn(() => "Task Master Banner"), })); -jest.mock('boxen', () => jest.fn((text) => `[boxed: ${text}]`)); +jest.mock("boxen", () => jest.fn((text) => `[boxed: ${text}]`)); -jest.mock('ora', () => - jest.fn(() => ({ - start: jest.fn(), - succeed: jest.fn(), - fail: jest.fn(), - stop: jest.fn() - })) +jest.mock("ora", () => + jest.fn(() => ({ + start: jest.fn(), + succeed: jest.fn(), + fail: jest.fn(), + stop: jest.fn(), + })) ); -jest.mock('cli-table3', () => - jest.fn().mockImplementation(() => ({ - push: jest.fn(), - toString: jest.fn(() => 'Table Content') - })) +jest.mock("cli-table3", () => + jest.fn().mockImplementation(() => ({ + push: jest.fn(), + toString: jest.fn(() => "Table Content"), + })) ); -jest.mock('gradient-string', () => jest.fn(() => jest.fn((text) => text))); +jest.mock("gradient-string", () => jest.fn(() => jest.fn((text) => text))); -jest.mock('../../scripts/modules/utils.js', () => ({ - CONFIG: { - projectName: 'Test Project', - projectVersion: '1.0.0' - }, - log: jest.fn(), - findTaskById: jest.fn(), - readJSON: jest.fn(), - readComplexityReport: jest.fn(), - truncate: jest.fn((text) => text) +jest.mock("../../scripts/modules/utils.js", () => ({ + CONFIG: { + projectName: "Test Project", + projectVersion: "1.0.0", + }, + log: jest.fn(), + findTaskById: jest.fn(), + readJSON: jest.fn(), + readComplexityReport: jest.fn(), + truncate: jest.fn((text) => text), })); -jest.mock('../../scripts/modules/task-manager.js', () => ({ - findNextTask: jest.fn(), - analyzeTaskComplexity: jest.fn() +jest.mock("../../scripts/modules/task-manager.js", () => ({ + findNextTask: jest.fn(), + analyzeTaskComplexity: jest.fn(), })); -describe('UI Module', () => { - beforeEach(() => { - jest.clearAllMocks(); - }); +describe("UI Module", () => { + beforeEach(() => { + jest.clearAllMocks(); + }); - describe('getStatusWithColor function', () => { - test('should return done status with emoji for console output', () => { - const result = getStatusWithColor('done'); - expect(result).toMatch(/done/); - expect(result).toContain('โœ…'); - }); + describe("getStatusWithColor function", () => { + test("should return done status with emoji for console output", () => { + const result = getStatusWithColor("done"); + expect(result).toMatch(/done/); + expect(result).toContain("โœ…"); + }); - test('should return pending status with emoji for console output', () => { - const result = getStatusWithColor('pending'); - expect(result).toMatch(/pending/); - expect(result).toContain('โฑ๏ธ'); - }); + test("should return pending status with emoji for console output", () => { + const result = getStatusWithColor("pending"); + expect(result).toMatch(/pending/); + expect(result).toContain("โฑ๏ธ"); + }); - test('should return deferred status with emoji for console output', () => { - const result = getStatusWithColor('deferred'); - expect(result).toMatch(/deferred/); - expect(result).toContain('โฑ๏ธ'); - }); + test("should return deferred status with emoji for console output", () => { + const result = getStatusWithColor("deferred"); + expect(result).toMatch(/deferred/); + expect(result).toContain("โฑ๏ธ"); + }); - test('should return in-progress status with emoji for console output', () => { - const result = getStatusWithColor('in-progress'); - expect(result).toMatch(/in-progress/); - expect(result).toContain('๐Ÿ”„'); - }); + test("should return in-progress status with emoji for console output", () => { + const result = getStatusWithColor("in-progress"); + expect(result).toMatch(/in-progress/); + expect(result).toContain("๐Ÿ”„"); + }); - test('should return unknown status with emoji for console output', () => { - const result = getStatusWithColor('unknown'); - expect(result).toMatch(/unknown/); - expect(result).toContain('โŒ'); - }); + test("should return unknown status with emoji for console output", () => { + const result = getStatusWithColor("unknown"); + expect(result).toMatch(/unknown/); + expect(result).toContain("โŒ"); + }); - test('should use simple icons when forTable is true', () => { - const doneResult = getStatusWithColor('done', true); - expect(doneResult).toMatch(/done/); - expect(doneResult).toContain('โœ“'); + test("should use simple icons when forTable is true", () => { + const doneResult = getStatusWithColor("done", true); + expect(doneResult).toMatch(/done/); + expect(doneResult).toContain("โœ“"); - const pendingResult = getStatusWithColor('pending', true); - expect(pendingResult).toMatch(/pending/); - expect(pendingResult).toContain('โ—‹'); + const pendingResult = getStatusWithColor("pending", true); + expect(pendingResult).toMatch(/pending/); + expect(pendingResult).toContain("โ—‹"); - const inProgressResult = getStatusWithColor('in-progress', true); - expect(inProgressResult).toMatch(/in-progress/); - expect(inProgressResult).toContain('โ–บ'); + const inProgressResult = getStatusWithColor("in-progress", true); + expect(inProgressResult).toMatch(/in-progress/); + expect(inProgressResult).toContain("โ–บ"); - const deferredResult = getStatusWithColor('deferred', true); - expect(deferredResult).toMatch(/deferred/); - expect(deferredResult).toContain('x'); - }); - }); + const deferredResult = getStatusWithColor("deferred", true); + expect(deferredResult).toMatch(/deferred/); + expect(deferredResult).toContain("x"); + }); + }); - describe('formatDependenciesWithStatus function', () => { - test('should format dependencies as plain IDs when forConsole is false (default)', () => { - const dependencies = [1, 2, 3]; - const allTasks = [ - { id: 1, status: 'done' }, - { id: 2, status: 'pending' }, - { id: 3, status: 'deferred' } - ]; + describe("formatDependenciesWithStatus function", () => { + test("should format dependencies as plain IDs when forConsole is false (default)", () => { + const dependencies = [1, 2, 3]; + const allTasks = [ + { id: 1, status: "done" }, + { id: 2, status: "pending" }, + { id: 3, status: "deferred" }, + ]; - const result = formatDependenciesWithStatus(dependencies, allTasks); + const result = formatDependenciesWithStatus(dependencies, allTasks); - // With recent changes, we expect just plain IDs when forConsole is false - expect(result).toBe('1, 2, 3'); - }); + // With recent changes, we expect just plain IDs when forConsole is false + expect(result).toBe("1, 2, 3"); + }); - test('should format dependencies with status indicators when forConsole is true', () => { - const dependencies = [1, 2, 3]; - const allTasks = [ - { id: 1, status: 'done' }, - { id: 2, status: 'pending' }, - { id: 3, status: 'deferred' } - ]; + test("should format dependencies with status indicators when forConsole is true", () => { + const dependencies = [1, 2, 3]; + const allTasks = [ + { id: 1, status: "done" }, + { id: 2, status: "pending" }, + { id: 3, status: "deferred" }, + ]; - const result = formatDependenciesWithStatus(dependencies, allTasks, true); + const result = formatDependenciesWithStatus(dependencies, allTasks, true); - // We can't test for exact color formatting due to our chalk mocks - // Instead, test that the result contains all the expected IDs - expect(result).toContain('1'); - expect(result).toContain('2'); - expect(result).toContain('3'); + // We can't test for exact color formatting due to our chalk mocks + // Instead, test that the result contains all the expected IDs + expect(result).toContain("1"); + expect(result).toContain("2"); + expect(result).toContain("3"); - // Test that it's a comma-separated list - expect(result.split(', ').length).toBe(3); - }); + // Test that it's a comma-separated list + expect(result.split(", ").length).toBe(3); + }); - test('should return "None" for empty dependencies', () => { - const result = formatDependenciesWithStatus([], []); - expect(result).toBe('None'); - }); + test('should return "None" for empty dependencies', () => { + const result = formatDependenciesWithStatus([], []); + expect(result).toBe("None"); + }); - test('should handle missing tasks in the task list', () => { - const dependencies = [1, 999]; - const allTasks = [{ id: 1, status: 'done' }]; + test("should handle missing tasks in the task list", () => { + const dependencies = [1, 999]; + const allTasks = [{ id: 1, status: "done" }]; - const result = formatDependenciesWithStatus(dependencies, allTasks); - expect(result).toBe('1, 999 (Not found)'); - }); - }); + const result = formatDependenciesWithStatus(dependencies, allTasks); + expect(result).toBe("1, 999 (Not found)"); + }); + }); - describe('createProgressBar function', () => { - test('should create a progress bar with the correct percentage', () => { - const result = createProgressBar(50, 10, { - pending: 20, - 'in-progress': 15, - blocked: 5 - }); - expect(result).toContain('50%'); - }); + describe("createProgressBar function", () => { + test("should create a progress bar with the correct percentage", () => { + const result = createProgressBar(50, 10, { + pending: 20, + "in-progress": 15, + blocked: 5, + }); + expect(result).toContain("50%"); + }); - test('should handle 0% progress', () => { - const result = createProgressBar(0, 10); - expect(result).toContain('0%'); - }); + test("should handle 0% progress", () => { + const result = createProgressBar(0, 10); + expect(result).toContain("0%"); + }); - test('should handle 100% progress', () => { - const result = createProgressBar(100, 10); - expect(result).toContain('100%'); - }); + test("should handle 100% progress", () => { + const result = createProgressBar(100, 10); + expect(result).toContain("100%"); + }); - test('should handle invalid percentages by clamping', () => { - const result1 = createProgressBar(0, 10); - expect(result1).toContain('0%'); + test("should handle invalid percentages by clamping", () => { + const result1 = createProgressBar(0, 10); + expect(result1).toContain("0%"); - const result2 = createProgressBar(100, 10); - expect(result2).toContain('100%'); - }); + const result2 = createProgressBar(100, 10); + expect(result2).toContain("100%"); + }); - test('should support status breakdown in the progress bar', () => { - const result = createProgressBar(30, 10, { - pending: 30, - 'in-progress': 20, - blocked: 10, - deferred: 5, - cancelled: 5 - }); + test("should support status breakdown in the progress bar", () => { + const result = createProgressBar(30, 10, { + pending: 30, + "in-progress": 20, + blocked: 10, + deferred: 5, + cancelled: 5, + }); - expect(result).toContain('40%'); - }); - }); + expect(result).toContain("40%"); + }); + }); - describe('getComplexityWithColor function', () => { - test('should return high complexity in red', () => { - const result = getComplexityWithColor(8); - expect(result).toMatch(/8/); - expect(result).toContain('๐Ÿ”ด'); - }); + describe("getComplexityWithColor function", () => { + test("should return high complexity in red", () => { + const result = getComplexityWithColor(8); + expect(result).toMatch(/8/); + expect(result).toContain("โ—"); + }); - test('should return medium complexity in yellow', () => { - const result = getComplexityWithColor(5); - expect(result).toMatch(/5/); - expect(result).toContain('๐ŸŸก'); - }); + test("should return medium complexity in yellow", () => { + const result = getComplexityWithColor(5); + expect(result).toMatch(/5/); + expect(result).toContain("โ—"); + }); - test('should return low complexity in green', () => { - const result = getComplexityWithColor(3); - expect(result).toMatch(/3/); - expect(result).toContain('๐ŸŸข'); - }); + test("should return low complexity in green", () => { + const result = getComplexityWithColor(3); + expect(result).toMatch(/3/); + expect(result).toContain("โ—"); + }); - test('should handle non-numeric inputs', () => { - const result = getComplexityWithColor('high'); - expect(result).toMatch(/high/); - expect(result).toContain('๐Ÿ”ด'); - }); - }); + test("should handle non-numeric inputs", () => { + const result = getComplexityWithColor("high"); + expect(result).toMatch(/high/); + expect(result).toContain("โ—"); + }); + }); }); From 9eb3842f0401e2d411c363a34d4915f452dd9d88 Mon Sep 17 00:00:00 2001 From: Eyal Toledano <eyal@microangel.so> Date: Sat, 7 Jun 2025 15:02:48 -0400 Subject: [PATCH 02/10] fix(ai-providers): change generateObject mode from 'tool' to 'auto' for better provider compatibility Fixes Perplexity research role failing with 'tool-mode object generation' error The hardcoded 'tool' mode was incompatible with providers like Perplexity that support structured JSON output but not function calling/tool use Using 'auto' mode allows the AI SDK to choose the best approach for each provider --- .taskmaster/config.json | 64 +- .taskmaster/tasks/task_092.txt | 18 +- .taskmaster/tasks/task_096.txt | 37 + .taskmaster/tasks/tasks.json | 191 +++-- package-lock.json | 4 +- scripts/modules/ai-services-unified.js | 1063 ++++++++++++------------ scripts/modules/supported-models.json | 850 +++++++++---------- src/ai-providers/base-provider.js | 364 ++++---- 8 files changed, 1346 insertions(+), 1245 deletions(-) create mode 100644 .taskmaster/tasks/task_096.txt diff --git a/.taskmaster/config.json b/.taskmaster/config.json index a61d10d5..442dfc1c 100644 --- a/.taskmaster/config.json +++ b/.taskmaster/config.json @@ -1,33 +1,33 @@ { - "models": { - "main": { - "provider": "anthropic", - "modelId": "claude-sonnet-4-20250514", - "maxTokens": 50000, - "temperature": 0.2 - }, - "research": { - "provider": "perplexity", - "modelId": "sonar-pro", - "maxTokens": 8700, - "temperature": 0.1 - }, - "fallback": { - "provider": "anthropic", - "modelId": "claude-3-7-sonnet-20250219", - "maxTokens": 128000, - "temperature": 0.2 - } - }, - "global": { - "logLevel": "info", - "debug": false, - "defaultSubtasks": 5, - "defaultPriority": "medium", - "projectName": "Taskmaster", - "ollamaBaseURL": "http://localhost:11434/api", - "bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com", - "userId": "1234567890", - "azureBaseURL": "https://your-endpoint.azure.com/" - } -} + "models": { + "main": { + "provider": "anthropic", + "modelId": "claude-sonnet-4-20250514", + "maxTokens": 50000, + "temperature": 0.2 + }, + "research": { + "provider": "perplexity", + "modelId": "sonar-pro", + "maxTokens": 8700, + "temperature": 0.1 + }, + "fallback": { + "provider": "anthropic", + "modelId": "claude-3-7-sonnet-20250219", + "maxTokens": 128000, + "temperature": 0.2 + } + }, + "global": { + "logLevel": "info", + "debug": false, + "defaultSubtasks": 5, + "defaultPriority": "medium", + "projectName": "Taskmaster", + "ollamaBaseURL": "http://localhost:11434/api", + "bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com", + "userId": "1234567890", + "azureBaseURL": "https://your-endpoint.azure.com/" + } +} \ No newline at end of file diff --git a/.taskmaster/tasks/task_092.txt b/.taskmaster/tasks/task_092.txt index 6c7ce26f..28ca7555 100644 --- a/.taskmaster/tasks/task_092.txt +++ b/.taskmaster/tasks/task_092.txt @@ -1,6 +1,6 @@ # Task ID: 92 # Title: Implement Project Root Environment Variable Support in MCP Configuration -# Status: in-progress +# Status: review # Dependencies: 1, 3, 17 # Priority: medium # Description: Add support for a 'TASK_MASTER_PROJECT_ROOT' environment variable in MCP configuration, allowing it to be set in both mcp.json and .env, with precedence over other methods. This will define the root directory for the MCP server and take precedence over all other project root resolution methods. The implementation should be backward compatible with existing workflows that don't use this variable. @@ -44,49 +44,49 @@ Implementation steps: - Test with invalid or non-existent directories to verify error handling # Subtasks: -## 92.1. Update configuration loader to check for TASK_MASTER_PROJECT_ROOT environment variable [pending] +## 1. Update configuration loader to check for TASK_MASTER_PROJECT_ROOT environment variable [pending] ### Dependencies: None ### Description: Modify the configuration loading system to check for the TASK_MASTER_PROJECT_ROOT environment variable as the primary source for project root directory. Ensure proper error handling if the variable is set but points to a non-existent or inaccessible directory. ### Details: -## 92.2. Add support for 'projectRoot' in configuration files [pending] +## 2. Add support for 'projectRoot' in configuration files [pending] ### Dependencies: None ### Description: Implement support for a 'projectRoot' key in mcp_config.toml and mcp.json configuration files as a fallback when the environment variable is not set. Update the configuration parser to recognize and validate this field. ### Details: -## 92.3. Refactor project root resolution logic with clear precedence rules [pending] +## 3. Refactor project root resolution logic with clear precedence rules [pending] ### Dependencies: None ### Description: Create a unified project root resolution function that follows the precedence order: 1) TASK_MASTER_PROJECT_ROOT environment variable, 2) 'projectRoot' in config files, 3) existing resolution methods. Ensure this function is used consistently throughout the codebase. ### Details: -## 92.4. Update all MCP tools to use the new project root resolution [pending] +## 4. Update all MCP tools to use the new project root resolution [pending] ### Dependencies: None ### Description: Identify all MCP tools and components that need to access the project root and update them to use the new resolution logic. Ensure consistent behavior across all parts of the system. ### Details: -## 92.5. Add comprehensive tests for the new project root resolution [pending] +## 5. Add comprehensive tests for the new project root resolution [pending] ### Dependencies: None ### Description: Create unit and integration tests to verify the correct behavior of the project root resolution logic under various configurations and edge cases. ### Details: -## 92.6. Update documentation with new configuration options [pending] +## 6. Update documentation with new configuration options [pending] ### Dependencies: None ### Description: Update the project documentation to clearly explain the new TASK_MASTER_PROJECT_ROOT environment variable, the 'projectRoot' configuration option, and the precedence rules. Include examples of different configuration scenarios. ### Details: -## 92.7. Implement validation for project root directory [pending] +## 7. Implement validation for project root directory [pending] ### Dependencies: None ### Description: Add validation to ensure the specified project root directory exists and has the necessary permissions. Provide clear error messages when validation fails. ### Details: -## 92.8. Implement support for loading environment variables from .env files [pending] +## 8. Implement support for loading environment variables from .env files [pending] ### Dependencies: None ### Description: Add functionality to load the TASK_MASTER_PROJECT_ROOT variable from .env files in the workspace, following best practices for environment variable management in MCP servers. ### Details: diff --git a/.taskmaster/tasks/task_096.txt b/.taskmaster/tasks/task_096.txt new file mode 100644 index 00000000..3468bfa5 --- /dev/null +++ b/.taskmaster/tasks/task_096.txt @@ -0,0 +1,37 @@ +# Task ID: 96 +# Title: Create Export Command for On-Demand Task File and PDF Generation +# Status: pending +# Dependencies: 2, 4, 95 +# Priority: medium +# Description: Develop an 'export' CLI command that generates task files and comprehensive PDF exports on-demand, replacing automatic file generation and providing users with flexible export options. +# Details: +Implement a new 'export' command in the CLI that supports two primary modes: (1) generating individual task files on-demand (superseding the current automatic generation system), and (2) producing a comprehensive PDF export. The PDF should include: a first page with the output of 'tm list --with-subtasks', followed by individual pages for each task (using 'tm show <task_id>') and each subtask (using 'tm show <subtask_id>'). Integrate PDF generation using a robust library (e.g., pdfkit, Puppeteer, or jsPDF) to ensure high-quality output and proper pagination. Refactor or disable any existing automatic file generation logic to avoid performance overhead. Ensure the command supports flexible output paths and options for exporting only files, only PDF, or both. Update documentation and help output to reflect the new export capabilities. Consider concurrency and error handling for large projects. Ensure the export process is efficient and does not block the main CLI thread unnecessarily. + +# Test Strategy: +1. Run the 'export' command with various options and verify that task files are generated only on-demand, not automatically. 2. Generate a PDF export and confirm that the first page contains the correct 'tm list --with-subtasks' output, and that each subsequent page accurately reflects the output of 'tm show <task_id>' and 'tm show <subtask_id>' for all tasks and subtasks. 3. Test exporting in projects with large numbers of tasks and subtasks to ensure performance and correctness. 4. Attempt exports with invalid paths or missing data to verify robust error handling. 5. Confirm that no automatic file generation occurs during normal task operations. 6. Review CLI help output and documentation for accuracy regarding the new export functionality. + +# Subtasks: +## 1. Remove Automatic Task File Generation from Task Operations [pending] +### Dependencies: None +### Description: Eliminate all calls to generateTaskFiles() from task operations such as add-task, remove-task, set-status, and similar commands to prevent unnecessary performance overhead. +### Details: +Audit the codebase for any automatic invocations of generateTaskFiles() and remove or refactor them to ensure task files are not generated automatically during task operations. + +## 2. Implement Export Command Infrastructure with On-Demand Task File Generation [pending] +### Dependencies: 96.1 +### Description: Develop the CLI 'export' command infrastructure, enabling users to generate task files on-demand by invoking the preserved generateTaskFiles function only when requested. +### Details: +Create the export command with options for output paths and modes (files, PDF, or both). Ensure generateTaskFiles is only called within this command and not elsewhere. + +## 3. Implement Comprehensive PDF Export Functionality [pending] +### Dependencies: 96.2 +### Description: Add PDF export capability to the export command, generating a structured PDF with a first page listing all tasks and subtasks, followed by individual pages for each task and subtask, using a robust PDF library. +### Details: +Integrate a PDF generation library (e.g., pdfkit, Puppeteer, or jsPDF). Ensure the PDF includes the output of 'tm list --with-subtasks' on the first page, and uses 'tm show <task_id>' and 'tm show <subtask_id>' for subsequent pages. Handle pagination, concurrency, and error handling for large projects. + +## 4. Update Documentation, Tests, and CLI Help for Export Workflow [pending] +### Dependencies: 96.2, 96.3 +### Description: Revise all relevant documentation, automated tests, and CLI help output to reflect the new export-based workflow and available options. +### Details: +Update user guides, README files, and CLI help text. Add or modify tests to cover the new export command and its options. Ensure all documentation accurately describes the new workflow and usage. + diff --git a/.taskmaster/tasks/tasks.json b/.taskmaster/tasks/tasks.json index 797456da..c8cd9c33 100644 --- a/.taskmaster/tasks/tasks.json +++ b/.taskmaster/tasks/tasks.json @@ -5467,6 +5467,70 @@ } ] }, + { + "id": 92, + "title": "Implement Project Root Environment Variable Support in MCP Configuration", + "description": "Add support for a 'TASK_MASTER_PROJECT_ROOT' environment variable in MCP configuration, allowing it to be set in both mcp.json and .env, with precedence over other methods. This will define the root directory for the MCP server and take precedence over all other project root resolution methods. The implementation should be backward compatible with existing workflows that don't use this variable.", + "status": "review", + "dependencies": [ + 1, + 3, + 17 + ], + "priority": "medium", + "details": "Update the MCP server configuration system to support the TASK_MASTER_PROJECT_ROOT environment variable as the standard way to specify the project root directory. This provides better namespacing and avoids conflicts with other tools that might use a generic PROJECT_ROOT variable. Implement a clear precedence order for project root resolution:\n\n1. TASK_MASTER_PROJECT_ROOT environment variable (from shell or .env file)\n2. 'projectRoot' key in mcp_config.toml or mcp.json configuration files\n3. Existing resolution logic (CLI args, current working directory, etc.)\n\nModify the configuration loading logic to check for these sources in the specified order, ensuring backward compatibility. All MCP tools and components should use this standardized project root resolution logic. The TASK_MASTER_PROJECT_ROOT environment variable will be required because path resolution is delegated to the MCP client implementation, ensuring consistent behavior across different environments.\n\nImplementation steps:\n1. Identify all code locations where project root is determined (initialization, utility functions)\n2. Update configuration loaders to check for TASK_MASTER_PROJECT_ROOT in environment variables\n3. Add support for 'projectRoot' in configuration files as a fallback\n4. Refactor project root resolution logic to follow the new precedence rules\n5. Ensure all MCP tools and functions use the updated resolution logic\n6. Add comprehensive error handling for cases where TASK_MASTER_PROJECT_ROOT is not set or invalid\n7. Implement validation to ensure the specified directory exists and is accessible", + "testStrategy": "1. Write unit tests to verify that the config loader correctly reads project root from environment variables and configuration files with the expected precedence:\n - Test TASK_MASTER_PROJECT_ROOT environment variable takes precedence when set\n - Test 'projectRoot' in configuration files is used when environment variable is absent\n - Test fallback to existing resolution logic when neither is specified\n\n2. Add integration tests to ensure that the MCP server and all tools use the correct project root:\n - Test server startup with TASK_MASTER_PROJECT_ROOT set to various valid and invalid paths\n - Test configuration file loading from the specified project root\n - Test path resolution for resources relative to the project root\n\n3. Test backward compatibility:\n - Verify existing workflows function correctly without the new variables\n - Ensure no regression in projects not using the new configuration options\n\n4. Manual testing:\n - Set TASK_MASTER_PROJECT_ROOT in shell environment and verify correct behavior\n - Set TASK_MASTER_PROJECT_ROOT in .env file and verify it's properly loaded\n - Configure 'projectRoot' in configuration files and test precedence\n - Test with invalid or non-existent directories to verify error handling", + "subtasks": [ + { + "id": 1, + "title": "Update configuration loader to check for TASK_MASTER_PROJECT_ROOT environment variable", + "description": "Modify the configuration loading system to check for the TASK_MASTER_PROJECT_ROOT environment variable as the primary source for project root directory. Ensure proper error handling if the variable is set but points to a non-existent or inaccessible directory.", + "status": "pending" + }, + { + "id": 2, + "title": "Add support for 'projectRoot' in configuration files", + "description": "Implement support for a 'projectRoot' key in mcp_config.toml and mcp.json configuration files as a fallback when the environment variable is not set. Update the configuration parser to recognize and validate this field.", + "status": "pending" + }, + { + "id": 3, + "title": "Refactor project root resolution logic with clear precedence rules", + "description": "Create a unified project root resolution function that follows the precedence order: 1) TASK_MASTER_PROJECT_ROOT environment variable, 2) 'projectRoot' in config files, 3) existing resolution methods. Ensure this function is used consistently throughout the codebase.", + "status": "pending" + }, + { + "id": 4, + "title": "Update all MCP tools to use the new project root resolution", + "description": "Identify all MCP tools and components that need to access the project root and update them to use the new resolution logic. Ensure consistent behavior across all parts of the system.", + "status": "pending" + }, + { + "id": 5, + "title": "Add comprehensive tests for the new project root resolution", + "description": "Create unit and integration tests to verify the correct behavior of the project root resolution logic under various configurations and edge cases.", + "status": "pending" + }, + { + "id": 6, + "title": "Update documentation with new configuration options", + "description": "Update the project documentation to clearly explain the new TASK_MASTER_PROJECT_ROOT environment variable, the 'projectRoot' configuration option, and the precedence rules. Include examples of different configuration scenarios.", + "status": "pending" + }, + { + "id": 7, + "title": "Implement validation for project root directory", + "description": "Add validation to ensure the specified project root directory exists and has the necessary permissions. Provide clear error messages when validation fails.", + "status": "pending" + }, + { + "id": 8, + "title": "Implement support for loading environment variables from .env files", + "description": "Add functionality to load the TASK_MASTER_PROJECT_ROOT variable from .env files in the workspace, following best practices for environment variable management in MCP servers.", + "status": "pending" + } + ] + }, { "id": 93, "title": "Implement Google Vertex AI Provider Integration", @@ -5613,70 +5677,6 @@ } ] }, - { - "id": 92, - "title": "Implement Project Root Environment Variable Support in MCP Configuration", - "description": "Add support for a 'TASK_MASTER_PROJECT_ROOT' environment variable in MCP configuration, allowing it to be set in both mcp.json and .env, with precedence over other methods. This will define the root directory for the MCP server and take precedence over all other project root resolution methods. The implementation should be backward compatible with existing workflows that don't use this variable.", - "status": "in-progress", - "dependencies": [ - 1, - 3, - 17 - ], - "priority": "medium", - "details": "Update the MCP server configuration system to support the TASK_MASTER_PROJECT_ROOT environment variable as the standard way to specify the project root directory. This provides better namespacing and avoids conflicts with other tools that might use a generic PROJECT_ROOT variable. Implement a clear precedence order for project root resolution:\n\n1. TASK_MASTER_PROJECT_ROOT environment variable (from shell or .env file)\n2. 'projectRoot' key in mcp_config.toml or mcp.json configuration files\n3. Existing resolution logic (CLI args, current working directory, etc.)\n\nModify the configuration loading logic to check for these sources in the specified order, ensuring backward compatibility. All MCP tools and components should use this standardized project root resolution logic. The TASK_MASTER_PROJECT_ROOT environment variable will be required because path resolution is delegated to the MCP client implementation, ensuring consistent behavior across different environments.\n\nImplementation steps:\n1. Identify all code locations where project root is determined (initialization, utility functions)\n2. Update configuration loaders to check for TASK_MASTER_PROJECT_ROOT in environment variables\n3. Add support for 'projectRoot' in configuration files as a fallback\n4. Refactor project root resolution logic to follow the new precedence rules\n5. Ensure all MCP tools and functions use the updated resolution logic\n6. Add comprehensive error handling for cases where TASK_MASTER_PROJECT_ROOT is not set or invalid\n7. Implement validation to ensure the specified directory exists and is accessible", - "testStrategy": "1. Write unit tests to verify that the config loader correctly reads project root from environment variables and configuration files with the expected precedence:\n - Test TASK_MASTER_PROJECT_ROOT environment variable takes precedence when set\n - Test 'projectRoot' in configuration files is used when environment variable is absent\n - Test fallback to existing resolution logic when neither is specified\n\n2. Add integration tests to ensure that the MCP server and all tools use the correct project root:\n - Test server startup with TASK_MASTER_PROJECT_ROOT set to various valid and invalid paths\n - Test configuration file loading from the specified project root\n - Test path resolution for resources relative to the project root\n\n3. Test backward compatibility:\n - Verify existing workflows function correctly without the new variables\n - Ensure no regression in projects not using the new configuration options\n\n4. Manual testing:\n - Set TASK_MASTER_PROJECT_ROOT in shell environment and verify correct behavior\n - Set TASK_MASTER_PROJECT_ROOT in .env file and verify it's properly loaded\n - Configure 'projectRoot' in configuration files and test precedence\n - Test with invalid or non-existent directories to verify error handling", - "subtasks": [ - { - "id": 92.1, - "title": "Update configuration loader to check for TASK_MASTER_PROJECT_ROOT environment variable", - "description": "Modify the configuration loading system to check for the TASK_MASTER_PROJECT_ROOT environment variable as the primary source for project root directory. Ensure proper error handling if the variable is set but points to a non-existent or inaccessible directory.", - "status": "pending" - }, - { - "id": 92.2, - "title": "Add support for 'projectRoot' in configuration files", - "description": "Implement support for a 'projectRoot' key in mcp_config.toml and mcp.json configuration files as a fallback when the environment variable is not set. Update the configuration parser to recognize and validate this field.", - "status": "pending" - }, - { - "id": 92.3, - "title": "Refactor project root resolution logic with clear precedence rules", - "description": "Create a unified project root resolution function that follows the precedence order: 1) TASK_MASTER_PROJECT_ROOT environment variable, 2) 'projectRoot' in config files, 3) existing resolution methods. Ensure this function is used consistently throughout the codebase.", - "status": "pending" - }, - { - "id": 92.4, - "title": "Update all MCP tools to use the new project root resolution", - "description": "Identify all MCP tools and components that need to access the project root and update them to use the new resolution logic. Ensure consistent behavior across all parts of the system.", - "status": "pending" - }, - { - "id": 92.5, - "title": "Add comprehensive tests for the new project root resolution", - "description": "Create unit and integration tests to verify the correct behavior of the project root resolution logic under various configurations and edge cases.", - "status": "pending" - }, - { - "id": 92.6, - "title": "Update documentation with new configuration options", - "description": "Update the project documentation to clearly explain the new TASK_MASTER_PROJECT_ROOT environment variable, the 'projectRoot' configuration option, and the precedence rules. Include examples of different configuration scenarios.", - "status": "pending" - }, - { - "id": 92.7, - "title": "Implement validation for project root directory", - "description": "Add validation to ensure the specified project root directory exists and has the necessary permissions. Provide clear error messages when validation fails.", - "status": "pending" - }, - { - "id": 92.8, - "title": "Implement support for loading environment variables from .env files", - "description": "Add functionality to load the TASK_MASTER_PROJECT_ROOT variable from .env files in the workspace, following best practices for environment variable management in MCP servers.", - "status": "pending" - } - ] - }, { "id": 95, "title": "Implement .taskmaster Directory Structure", @@ -5808,6 +5808,69 @@ "testStrategy": "Test complete workflows and verify only .taskmaster/ directory is created in project root. Check that all Task Master operations respect the new file organization. Verify .gitignore compatibility." } ] + }, + { + "id": 96, + "title": "Create Export Command for On-Demand Task File and PDF Generation", + "description": "Develop an 'export' CLI command that generates task files and comprehensive PDF exports on-demand, replacing automatic file generation and providing users with flexible export options.", + "details": "Implement a new 'export' command in the CLI that supports two primary modes: (1) generating individual task files on-demand (superseding the current automatic generation system), and (2) producing a comprehensive PDF export. The PDF should include: a first page with the output of 'tm list --with-subtasks', followed by individual pages for each task (using 'tm show <task_id>') and each subtask (using 'tm show <subtask_id>'). Integrate PDF generation using a robust library (e.g., pdfkit, Puppeteer, or jsPDF) to ensure high-quality output and proper pagination. Refactor or disable any existing automatic file generation logic to avoid performance overhead. Ensure the command supports flexible output paths and options for exporting only files, only PDF, or both. Update documentation and help output to reflect the new export capabilities. Consider concurrency and error handling for large projects. Ensure the export process is efficient and does not block the main CLI thread unnecessarily.", + "testStrategy": "1. Run the 'export' command with various options and verify that task files are generated only on-demand, not automatically. 2. Generate a PDF export and confirm that the first page contains the correct 'tm list --with-subtasks' output, and that each subsequent page accurately reflects the output of 'tm show <task_id>' and 'tm show <subtask_id>' for all tasks and subtasks. 3. Test exporting in projects with large numbers of tasks and subtasks to ensure performance and correctness. 4. Attempt exports with invalid paths or missing data to verify robust error handling. 5. Confirm that no automatic file generation occurs during normal task operations. 6. Review CLI help output and documentation for accuracy regarding the new export functionality.", + "status": "pending", + "dependencies": [ + 2, + 4, + 95 + ], + "priority": "medium", + "subtasks": [ + { + "id": 1, + "title": "Remove Automatic Task File Generation from Task Operations", + "description": "Eliminate all calls to generateTaskFiles() from task operations such as add-task, remove-task, set-status, and similar commands to prevent unnecessary performance overhead.", + "dependencies": [], + "details": "Audit the codebase for any automatic invocations of generateTaskFiles() and remove or refactor them to ensure task files are not generated automatically during task operations.", + "status": "pending", + "testStrategy": "Verify that no task file generation occurs during any task operation by running the CLI and monitoring file system changes.", + "parentTaskId": 96 + }, + { + "id": 2, + "title": "Implement Export Command Infrastructure with On-Demand Task File Generation", + "description": "Develop the CLI 'export' command infrastructure, enabling users to generate task files on-demand by invoking the preserved generateTaskFiles function only when requested.", + "dependencies": [ + 1 + ], + "details": "Create the export command with options for output paths and modes (files, PDF, or both). Ensure generateTaskFiles is only called within this command and not elsewhere.", + "status": "pending", + "testStrategy": "Test the export command to confirm task files are generated only when explicitly requested and that output paths and options function as intended.", + "parentTaskId": 96 + }, + { + "id": 3, + "title": "Implement Comprehensive PDF Export Functionality", + "description": "Add PDF export capability to the export command, generating a structured PDF with a first page listing all tasks and subtasks, followed by individual pages for each task and subtask, using a robust PDF library.", + "dependencies": [ + 2 + ], + "details": "Integrate a PDF generation library (e.g., pdfkit, Puppeteer, or jsPDF). Ensure the PDF includes the output of 'tm list --with-subtasks' on the first page, and uses 'tm show <task_id>' and 'tm show <subtask_id>' for subsequent pages. Handle pagination, concurrency, and error handling for large projects.", + "status": "pending", + "testStrategy": "Generate PDFs for projects of varying sizes and verify layout, content accuracy, and performance. Test error handling and concurrency under load.", + "parentTaskId": 96 + }, + { + "id": 4, + "title": "Update Documentation, Tests, and CLI Help for Export Workflow", + "description": "Revise all relevant documentation, automated tests, and CLI help output to reflect the new export-based workflow and available options.", + "dependencies": [ + 2, + 3 + ], + "details": "Update user guides, README files, and CLI help text. Add or modify tests to cover the new export command and its options. Ensure all documentation accurately describes the new workflow and usage.", + "status": "pending", + "testStrategy": "Review documentation for completeness and accuracy. Run all tests to ensure coverage of the new export command and verify CLI help output.", + "parentTaskId": 96 + } + ] } ] } \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index a29d6fc5..73fd6d55 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "task-master-ai", - "version": "0.16.1", + "version": "0.16.2-rc.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "task-master-ai", - "version": "0.16.1", + "version": "0.16.2-rc.0", "license": "MIT WITH Commons-Clause", "dependencies": { "@ai-sdk/amazon-bedrock": "^2.2.9", diff --git a/scripts/modules/ai-services-unified.js b/scripts/modules/ai-services-unified.js index ca702701..adcad88d 100644 --- a/scripts/modules/ai-services-unified.js +++ b/scripts/modules/ai-services-unified.js @@ -8,82 +8,82 @@ // --- Core Dependencies --- import { - getMainProvider, - getMainModelId, - getResearchProvider, - getResearchModelId, - getFallbackProvider, - getFallbackModelId, - getParametersForRole, - getUserId, - MODEL_MAP, - getDebugFlag, - getBaseUrlForRole, - isApiKeySet, - getOllamaBaseURL, - getAzureBaseURL, - getBedrockBaseURL, - getVertexProjectId, - getVertexLocation -} from './config-manager.js'; -import { log, findProjectRoot, resolveEnvVariable } from './utils.js'; + getMainProvider, + getMainModelId, + getResearchProvider, + getResearchModelId, + getFallbackProvider, + getFallbackModelId, + getParametersForRole, + getUserId, + MODEL_MAP, + getDebugFlag, + getBaseUrlForRole, + isApiKeySet, + getOllamaBaseURL, + getAzureBaseURL, + getBedrockBaseURL, + getVertexProjectId, + getVertexLocation, +} from "./config-manager.js"; +import { log, findProjectRoot, resolveEnvVariable } from "./utils.js"; // Import provider classes import { - AnthropicAIProvider, - PerplexityAIProvider, - GoogleAIProvider, - OpenAIProvider, - XAIProvider, - OpenRouterAIProvider, - OllamaAIProvider, - BedrockAIProvider, - AzureProvider, - VertexAIProvider -} from '../../src/ai-providers/index.js'; + AnthropicAIProvider, + PerplexityAIProvider, + GoogleAIProvider, + OpenAIProvider, + XAIProvider, + OpenRouterAIProvider, + OllamaAIProvider, + BedrockAIProvider, + AzureProvider, + VertexAIProvider, +} from "../../src/ai-providers/index.js"; // Create provider instances const PROVIDERS = { - anthropic: new AnthropicAIProvider(), - perplexity: new PerplexityAIProvider(), - google: new GoogleAIProvider(), - openai: new OpenAIProvider(), - xai: new XAIProvider(), - openrouter: new OpenRouterAIProvider(), - ollama: new OllamaAIProvider(), - bedrock: new BedrockAIProvider(), - azure: new AzureProvider(), - vertex: new VertexAIProvider() + anthropic: new AnthropicAIProvider(), + perplexity: new PerplexityAIProvider(), + google: new GoogleAIProvider(), + openai: new OpenAIProvider(), + xai: new XAIProvider(), + openrouter: new OpenRouterAIProvider(), + ollama: new OllamaAIProvider(), + bedrock: new BedrockAIProvider(), + azure: new AzureProvider(), + vertex: new VertexAIProvider(), }; // Helper function to get cost for a specific model function _getCostForModel(providerName, modelId) { - if (!MODEL_MAP || !MODEL_MAP[providerName]) { - log( - 'warn', - `Provider "${providerName}" not found in MODEL_MAP. Cannot determine cost for model ${modelId}.` - ); - return { inputCost: 0, outputCost: 0, currency: 'USD' }; // Default to zero cost - } + if (!MODEL_MAP || !MODEL_MAP[providerName]) { + log( + "warn", + `Provider "${providerName}" not found in MODEL_MAP. Cannot determine cost for model ${modelId}.` + ); + return { inputCost: 0, outputCost: 0, currency: "USD" }; // Default to zero cost + } - const modelData = MODEL_MAP[providerName].find((m) => m.id === modelId); + const modelData = MODEL_MAP[providerName].find((m) => m.id === modelId); - if (!modelData || !modelData.cost_per_1m_tokens) { - log( - 'debug', - `Cost data not found for model "${modelId}" under provider "${providerName}". Assuming zero cost.` - ); - return { inputCost: 0, outputCost: 0, currency: 'USD' }; // Default to zero cost - } + if (!modelData || !modelData.cost_per_1m_tokens) { + log( + "debug", + `Cost data not found for model "${modelId}" under provider "${providerName}". Assuming zero cost.` + ); + return { inputCost: 0, outputCost: 0, currency: "USD" }; // Default to zero cost + } - // Ensure currency is part of the returned object, defaulting if not present - const currency = modelData.cost_per_1m_tokens.currency || 'USD'; + // Ensure currency is part of the returned object, defaulting if not present + const currency = modelData.cost_per_1m_tokens.currency || "USD"; - return { - inputCost: modelData.cost_per_1m_tokens.input || 0, - outputCost: modelData.cost_per_1m_tokens.output || 0, - currency: currency - }; + return { + inputCost: modelData.cost_per_1m_tokens.input || 0, + outputCost: modelData.cost_per_1m_tokens.output || 0, + currency: currency, + }; } // --- Configuration for Retries --- @@ -92,16 +92,16 @@ const INITIAL_RETRY_DELAY_MS = 1000; // Helper function to check if an error is retryable function isRetryableError(error) { - const errorMessage = error.message?.toLowerCase() || ''; - return ( - errorMessage.includes('rate limit') || - errorMessage.includes('overloaded') || - errorMessage.includes('service temporarily unavailable') || - errorMessage.includes('timeout') || - errorMessage.includes('network error') || - error.status === 429 || - error.status >= 500 - ); + const errorMessage = error.message?.toLowerCase() || ""; + return ( + errorMessage.includes("rate limit") || + errorMessage.includes("overloaded") || + errorMessage.includes("service temporarily unavailable") || + errorMessage.includes("timeout") || + errorMessage.includes("network error") || + error.status === 429 || + error.status >= 500 + ); } /** @@ -111,45 +111,45 @@ function isRetryableError(error) { * @returns {string} A concise error message. */ function _extractErrorMessage(error) { - try { - // Attempt 1: Look for Vercel SDK specific nested structure (common) - if (error?.data?.error?.message) { - return error.data.error.message; - } + try { + // Attempt 1: Look for Vercel SDK specific nested structure (common) + if (error?.data?.error?.message) { + return error.data.error.message; + } - // Attempt 2: Look for nested error message directly in the error object - if (error?.error?.message) { - return error.error.message; - } + // Attempt 2: Look for nested error message directly in the error object + if (error?.error?.message) { + return error.error.message; + } - // Attempt 3: Look for nested error message in response body if it's JSON string - if (typeof error?.responseBody === 'string') { - try { - const body = JSON.parse(error.responseBody); - if (body?.error?.message) { - return body.error.message; - } - } catch (parseError) { - // Ignore if responseBody is not valid JSON - } - } + // Attempt 3: Look for nested error message in response body if it's JSON string + if (typeof error?.responseBody === "string") { + try { + const body = JSON.parse(error.responseBody); + if (body?.error?.message) { + return body.error.message; + } + } catch (parseError) { + // Ignore if responseBody is not valid JSON + } + } - // Attempt 4: Use the top-level message if it exists - if (typeof error?.message === 'string' && error.message) { - return error.message; - } + // Attempt 4: Use the top-level message if it exists + if (typeof error?.message === "string" && error.message) { + return error.message; + } - // Attempt 5: Handle simple string errors - if (typeof error === 'string') { - return error; - } + // Attempt 5: Handle simple string errors + if (typeof error === "string") { + return error; + } - // Fallback - return 'An unknown AI service error occurred.'; - } catch (e) { - // Safety net - return 'Failed to extract error message.'; - } + // Fallback + return "An unknown AI service error occurred."; + } catch (e) { + // Safety net + return "Failed to extract error message."; + } } /** @@ -161,40 +161,40 @@ function _extractErrorMessage(error) { * @throws {Error} If a required API key is missing. */ function _resolveApiKey(providerName, session, projectRoot = null) { - const keyMap = { - openai: 'OPENAI_API_KEY', - anthropic: 'ANTHROPIC_API_KEY', - google: 'GOOGLE_API_KEY', - perplexity: 'PERPLEXITY_API_KEY', - mistral: 'MISTRAL_API_KEY', - azure: 'AZURE_OPENAI_API_KEY', - openrouter: 'OPENROUTER_API_KEY', - xai: 'XAI_API_KEY', - ollama: 'OLLAMA_API_KEY', - bedrock: 'AWS_ACCESS_KEY_ID', - vertex: 'GOOGLE_API_KEY' - }; + const keyMap = { + openai: "OPENAI_API_KEY", + anthropic: "ANTHROPIC_API_KEY", + google: "GOOGLE_API_KEY", + perplexity: "PERPLEXITY_API_KEY", + mistral: "MISTRAL_API_KEY", + azure: "AZURE_OPENAI_API_KEY", + openrouter: "OPENROUTER_API_KEY", + xai: "XAI_API_KEY", + ollama: "OLLAMA_API_KEY", + bedrock: "AWS_ACCESS_KEY_ID", + vertex: "GOOGLE_API_KEY", + }; - const envVarName = keyMap[providerName]; - if (!envVarName) { - throw new Error( - `Unknown provider '${providerName}' for API key resolution.` - ); - } + const envVarName = keyMap[providerName]; + if (!envVarName) { + throw new Error( + `Unknown provider '${providerName}' for API key resolution.` + ); + } - const apiKey = resolveEnvVariable(envVarName, session, projectRoot); + const apiKey = resolveEnvVariable(envVarName, session, projectRoot); - // Special handling for providers that can use alternative auth - if (providerName === 'ollama' || providerName === 'bedrock') { - return apiKey || null; - } + // Special handling for providers that can use alternative auth + if (providerName === "ollama" || providerName === "bedrock") { + return apiKey || null; + } - if (!apiKey) { - throw new Error( - `Required API key ${envVarName} for provider '${providerName}' is not set in environment, session, or .env file.` - ); - } - return apiKey; + if (!apiKey) { + throw new Error( + `Required API key ${envVarName} for provider '${providerName}' is not set in environment, session, or .env file.` + ); + } + return apiKey; } /** @@ -209,62 +209,62 @@ function _resolveApiKey(providerName, session, projectRoot = null) { * @throws {Error} If the call fails after all retries. */ async function _attemptProviderCallWithRetries( - provider, - serviceType, - callParams, - providerName, - modelId, - attemptRole + provider, + serviceType, + callParams, + providerName, + modelId, + attemptRole ) { - let retries = 0; - const fnName = serviceType; + let retries = 0; + const fnName = serviceType; - while (retries <= MAX_RETRIES) { - try { - if (getDebugFlag()) { - log( - 'info', - `Attempt ${retries + 1}/${MAX_RETRIES + 1} calling ${fnName} (Provider: ${providerName}, Model: ${modelId}, Role: ${attemptRole})` - ); - } + while (retries <= MAX_RETRIES) { + try { + if (getDebugFlag()) { + log( + "info", + `Attempt ${retries + 1}/${MAX_RETRIES + 1} calling ${fnName} (Provider: ${providerName}, Model: ${modelId}, Role: ${attemptRole})` + ); + } - // Call the appropriate method on the provider instance - const result = await provider[serviceType](callParams); + // Call the appropriate method on the provider instance + const result = await provider[serviceType](callParams); - if (getDebugFlag()) { - log( - 'info', - `${fnName} succeeded for role ${attemptRole} (Provider: ${providerName}) on attempt ${retries + 1}` - ); - } - return result; - } catch (error) { - log( - 'warn', - `Attempt ${retries + 1} failed for role ${attemptRole} (${fnName} / ${providerName}): ${error.message}` - ); + if (getDebugFlag()) { + log( + "info", + `${fnName} succeeded for role ${attemptRole} (Provider: ${providerName}) on attempt ${retries + 1}` + ); + } + return result; + } catch (error) { + log( + "warn", + `Attempt ${retries + 1} failed for role ${attemptRole} (${fnName} / ${providerName}): ${error.message}` + ); - if (isRetryableError(error) && retries < MAX_RETRIES) { - retries++; - const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retries - 1); - log( - 'info', - `Something went wrong on the provider side. Retrying in ${delay / 1000}s...` - ); - await new Promise((resolve) => setTimeout(resolve, delay)); - } else { - log( - 'error', - `Something went wrong on the provider side. Max retries reached for role ${attemptRole} (${fnName} / ${providerName}).` - ); - throw error; - } - } - } - // Should not be reached due to throw in the else block - throw new Error( - `Exhausted all retries for role ${attemptRole} (${fnName} / ${providerName})` - ); + if (isRetryableError(error) && retries < MAX_RETRIES) { + retries++; + const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retries - 1); + log( + "info", + `Something went wrong on the provider side. Retrying in ${delay / 1000}s...` + ); + await new Promise((resolve) => setTimeout(resolve, delay)); + } else { + log( + "error", + `Something went wrong on the provider side. Max retries reached for role ${attemptRole} (${fnName} / ${providerName}).` + ); + throw error; + } + } + } + // Should not be reached due to throw in the else block + throw new Error( + `Exhausted all retries for role ${attemptRole} (${fnName} / ${providerName})` + ); } /** @@ -283,312 +283,313 @@ async function _attemptProviderCallWithRetries( * @returns {Promise<any>} Result from the underlying provider call. */ async function _unifiedServiceRunner(serviceType, params) { - const { - role: initialRole, - session, - projectRoot, - systemPrompt, - prompt, - schema, - objectName, - commandName, - outputType, - ...restApiParams - } = params; - if (getDebugFlag()) { - log('info', `${serviceType}Service called`, { - role: initialRole, - commandName, - outputType, - projectRoot - }); - } + const { + role: initialRole, + session, + projectRoot, + systemPrompt, + prompt, + schema, + objectName, + commandName, + outputType, + ...restApiParams + } = params; + if (getDebugFlag()) { + log("info", `${serviceType}Service called`, { + role: initialRole, + commandName, + outputType, + projectRoot, + }); + } - const effectiveProjectRoot = projectRoot || findProjectRoot(); - const userId = getUserId(effectiveProjectRoot); + const effectiveProjectRoot = projectRoot || findProjectRoot(); + const userId = getUserId(effectiveProjectRoot); - let sequence; - if (initialRole === 'main') { - sequence = ['main', 'fallback', 'research']; - } else if (initialRole === 'research') { - sequence = ['research', 'fallback', 'main']; - } else if (initialRole === 'fallback') { - sequence = ['fallback', 'main', 'research']; - } else { - log( - 'warn', - `Unknown initial role: ${initialRole}. Defaulting to main -> fallback -> research sequence.` - ); - sequence = ['main', 'fallback', 'research']; - } + let sequence; + if (initialRole === "main") { + sequence = ["main", "fallback", "research"]; + } else if (initialRole === "research") { + sequence = ["research", "fallback", "main"]; + } else if (initialRole === "fallback") { + sequence = ["fallback", "main", "research"]; + } else { + log( + "warn", + `Unknown initial role: ${initialRole}. Defaulting to main -> fallback -> research sequence.` + ); + sequence = ["main", "fallback", "research"]; + } - let lastError = null; - let lastCleanErrorMessage = - 'AI service call failed for all configured roles.'; + let lastError = null; + let lastCleanErrorMessage = + "AI service call failed for all configured roles."; - for (const currentRole of sequence) { - let providerName, - modelId, - apiKey, - roleParams, - provider, - baseURL, - providerResponse, - telemetryData = null; + for (const currentRole of sequence) { + let providerName, + modelId, + apiKey, + roleParams, + provider, + baseURL, + providerResponse, + telemetryData = null; - try { - log('info', `New AI service call with role: ${currentRole}`); + try { + log("info", `New AI service call with role: ${currentRole}`); - if (currentRole === 'main') { - providerName = getMainProvider(effectiveProjectRoot); - modelId = getMainModelId(effectiveProjectRoot); - } else if (currentRole === 'research') { - providerName = getResearchProvider(effectiveProjectRoot); - modelId = getResearchModelId(effectiveProjectRoot); - } else if (currentRole === 'fallback') { - providerName = getFallbackProvider(effectiveProjectRoot); - modelId = getFallbackModelId(effectiveProjectRoot); - } else { - log( - 'error', - `Unknown role encountered in _unifiedServiceRunner: ${currentRole}` - ); - lastError = - lastError || new Error(`Unknown AI role specified: ${currentRole}`); - continue; - } + if (currentRole === "main") { + providerName = getMainProvider(effectiveProjectRoot); + modelId = getMainModelId(effectiveProjectRoot); + } else if (currentRole === "research") { + providerName = getResearchProvider(effectiveProjectRoot); + modelId = getResearchModelId(effectiveProjectRoot); + } else if (currentRole === "fallback") { + providerName = getFallbackProvider(effectiveProjectRoot); + modelId = getFallbackModelId(effectiveProjectRoot); + } else { + log( + "error", + `Unknown role encountered in _unifiedServiceRunner: ${currentRole}` + ); + lastError = + lastError || new Error(`Unknown AI role specified: ${currentRole}`); + continue; + } - if (!providerName || !modelId) { - log( - 'warn', - `Skipping role '${currentRole}': Provider or Model ID not configured.` - ); - lastError = - lastError || - new Error( - `Configuration missing for role '${currentRole}'. Provider: ${providerName}, Model: ${modelId}` - ); - continue; - } + if (!providerName || !modelId) { + log( + "warn", + `Skipping role '${currentRole}': Provider or Model ID not configured.` + ); + lastError = + lastError || + new Error( + `Configuration missing for role '${currentRole}'. Provider: ${providerName}, Model: ${modelId}` + ); + continue; + } - // Get provider instance - provider = PROVIDERS[providerName?.toLowerCase()]; - if (!provider) { - log( - 'warn', - `Skipping role '${currentRole}': Provider '${providerName}' not supported.` - ); - lastError = - lastError || - new Error(`Unsupported provider configured: ${providerName}`); - continue; - } + // Get provider instance + provider = PROVIDERS[providerName?.toLowerCase()]; + if (!provider) { + log( + "warn", + `Skipping role '${currentRole}': Provider '${providerName}' not supported.` + ); + lastError = + lastError || + new Error(`Unsupported provider configured: ${providerName}`); + continue; + } - // Check API key if needed - if (providerName?.toLowerCase() !== 'ollama') { - if (!isApiKeySet(providerName, session, effectiveProjectRoot)) { - log( - 'warn', - `Skipping role '${currentRole}' (Provider: ${providerName}): API key not set or invalid.` - ); - lastError = - lastError || - new Error( - `API key for provider '${providerName}' (role: ${currentRole}) is not set.` - ); - continue; // Skip to the next role in the sequence - } - } + // Check API key if needed + if (providerName?.toLowerCase() !== "ollama") { + if (!isApiKeySet(providerName, session, effectiveProjectRoot)) { + log( + "warn", + `Skipping role '${currentRole}' (Provider: ${providerName}): API key not set or invalid.` + ); + lastError = + lastError || + new Error( + `API key for provider '${providerName}' (role: ${currentRole}) is not set.` + ); + continue; // Skip to the next role in the sequence + } + } - // Get base URL if configured (optional for most providers) - baseURL = getBaseUrlForRole(currentRole, effectiveProjectRoot); + // Get base URL if configured (optional for most providers) + baseURL = getBaseUrlForRole(currentRole, effectiveProjectRoot); - // For Azure, use the global Azure base URL if role-specific URL is not configured - if (providerName?.toLowerCase() === 'azure' && !baseURL) { - baseURL = getAzureBaseURL(effectiveProjectRoot); - log('debug', `Using global Azure base URL: ${baseURL}`); - } else if (providerName?.toLowerCase() === 'ollama' && !baseURL) { - // For Ollama, use the global Ollama base URL if role-specific URL is not configured - baseURL = getOllamaBaseURL(effectiveProjectRoot); - log('debug', `Using global Ollama base URL: ${baseURL}`); - } else if (providerName?.toLowerCase() === 'bedrock' && !baseURL) { - // For Bedrock, use the global Bedrock base URL if role-specific URL is not configured - baseURL = getBedrockBaseURL(effectiveProjectRoot); - log('debug', `Using global Bedrock base URL: ${baseURL}`); - } + // For Azure, use the global Azure base URL if role-specific URL is not configured + if (providerName?.toLowerCase() === "azure" && !baseURL) { + baseURL = getAzureBaseURL(effectiveProjectRoot); + log("debug", `Using global Azure base URL: ${baseURL}`); + } else if (providerName?.toLowerCase() === "ollama" && !baseURL) { + // For Ollama, use the global Ollama base URL if role-specific URL is not configured + baseURL = getOllamaBaseURL(effectiveProjectRoot); + log("debug", `Using global Ollama base URL: ${baseURL}`); + } else if (providerName?.toLowerCase() === "bedrock" && !baseURL) { + // For Bedrock, use the global Bedrock base URL if role-specific URL is not configured + baseURL = getBedrockBaseURL(effectiveProjectRoot); + log("debug", `Using global Bedrock base URL: ${baseURL}`); + } - // Get AI parameters for the current role - roleParams = getParametersForRole(currentRole, effectiveProjectRoot); - apiKey = _resolveApiKey( - providerName?.toLowerCase(), - session, - effectiveProjectRoot - ); + // Get AI parameters for the current role + roleParams = getParametersForRole(currentRole, effectiveProjectRoot); + apiKey = _resolveApiKey( + providerName?.toLowerCase(), + session, + effectiveProjectRoot + ); - // Prepare provider-specific configuration - let providerSpecificParams = {}; + // Prepare provider-specific configuration + let providerSpecificParams = {}; - // Handle Vertex AI specific configuration - if (providerName?.toLowerCase() === 'vertex') { - // Get Vertex project ID and location - const projectId = - getVertexProjectId(effectiveProjectRoot) || - resolveEnvVariable( - 'VERTEX_PROJECT_ID', - session, - effectiveProjectRoot - ); + // Handle Vertex AI specific configuration + if (providerName?.toLowerCase() === "vertex") { + // Get Vertex project ID and location + const projectId = + getVertexProjectId(effectiveProjectRoot) || + resolveEnvVariable( + "VERTEX_PROJECT_ID", + session, + effectiveProjectRoot + ); - const location = - getVertexLocation(effectiveProjectRoot) || - resolveEnvVariable( - 'VERTEX_LOCATION', - session, - effectiveProjectRoot - ) || - 'us-central1'; + const location = + getVertexLocation(effectiveProjectRoot) || + resolveEnvVariable( + "VERTEX_LOCATION", + session, + effectiveProjectRoot + ) || + "us-central1"; - // Get credentials path if available - const credentialsPath = resolveEnvVariable( - 'GOOGLE_APPLICATION_CREDENTIALS', - session, - effectiveProjectRoot - ); + // Get credentials path if available + const credentialsPath = resolveEnvVariable( + "GOOGLE_APPLICATION_CREDENTIALS", + session, + effectiveProjectRoot + ); - // Add Vertex-specific parameters - providerSpecificParams = { - projectId, - location, - ...(credentialsPath && { credentials: { credentialsFromEnv: true } }) - }; + // Add Vertex-specific parameters + providerSpecificParams = { + projectId, + location, + ...(credentialsPath && { credentials: { credentialsFromEnv: true } }), + }; - log( - 'debug', - `Using Vertex AI configuration: Project ID=${projectId}, Location=${location}` - ); - } + log( + "debug", + `Using Vertex AI configuration: Project ID=${projectId}, Location=${location}` + ); + } - const messages = []; - if (systemPrompt) { - messages.push({ role: 'system', content: systemPrompt }); - } + const messages = []; + if (systemPrompt) { + messages.push({ role: "system", content: systemPrompt }); + } - // IN THE FUTURE WHEN DOING CONTEXT IMPROVEMENTS - // { - // type: 'text', - // text: 'Large cached context here like a tasks json', - // providerOptions: { - // anthropic: { cacheControl: { type: 'ephemeral' } } - // } - // } + // IN THE FUTURE WHEN DOING CONTEXT IMPROVEMENTS + // { + // type: 'text', + // text: 'Large cached context here like a tasks json', + // providerOptions: { + // anthropic: { cacheControl: { type: 'ephemeral' } } + // } + // } - // Example - // if (params.context) { // context is a json string of a tasks object or some other stu - // messages.push({ - // type: 'text', - // text: params.context, - // providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } } } - // }); - // } + // Example + // if (params.context) { // context is a json string of a tasks object or some other stu + // messages.push({ + // type: 'text', + // text: params.context, + // providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } } } + // }); + // } - if (prompt) { - messages.push({ role: 'user', content: prompt }); - } else { - throw new Error('User prompt content is missing.'); - } + if (prompt) { + messages.push({ role: "user", content: prompt }); + } else { + throw new Error("User prompt content is missing."); + } - const callParams = { - apiKey, - modelId, - maxTokens: roleParams.maxTokens, - temperature: roleParams.temperature, - messages, - ...(baseURL && { baseURL }), - ...(serviceType === 'generateObject' && { schema, objectName }), - ...providerSpecificParams, - ...restApiParams - }; + const callParams = { + apiKey, + modelId, + maxTokens: roleParams.maxTokens, + temperature: roleParams.temperature, + messages, + ...(baseURL && { baseURL }), + ...(serviceType === "generateObject" && { schema, objectName }), + ...providerSpecificParams, + ...restApiParams, + }; - providerResponse = await _attemptProviderCallWithRetries( - provider, - serviceType, - callParams, - providerName, - modelId, - currentRole - ); + providerResponse = await _attemptProviderCallWithRetries( + provider, + serviceType, + callParams, + providerName, + modelId, + currentRole + ); - if (userId && providerResponse && providerResponse.usage) { - try { - telemetryData = await logAiUsage({ - userId, - commandName, - providerName, - modelId, - inputTokens: providerResponse.usage.inputTokens, - outputTokens: providerResponse.usage.outputTokens, - outputType - }); - } catch (telemetryError) { - // logAiUsage already logs its own errors and returns null on failure - // No need to log again here, telemetryData will remain null - } - } else if (userId && providerResponse && !providerResponse.usage) { - log( - 'warn', - `Cannot log telemetry for ${commandName} (${providerName}/${modelId}): AI result missing 'usage' data. (May be expected for streams)` - ); - } + if (userId && providerResponse && providerResponse.usage) { + try { + telemetryData = await logAiUsage({ + userId, + commandName, + providerName, + modelId, + inputTokens: providerResponse.usage.inputTokens, + outputTokens: providerResponse.usage.outputTokens, + outputType, + }); + } catch (telemetryError) { + // logAiUsage already logs its own errors and returns null on failure + // No need to log again here, telemetryData will remain null + } + } else if (userId && providerResponse && !providerResponse.usage) { + log( + "warn", + `Cannot log telemetry for ${commandName} (${providerName}/${modelId}): AI result missing 'usage' data. (May be expected for streams)` + ); + } - let finalMainResult; - if (serviceType === 'generateText') { - finalMainResult = providerResponse.text; - } else if (serviceType === 'generateObject') { - finalMainResult = providerResponse.object; - } else if (serviceType === 'streamText') { - finalMainResult = providerResponse; - } else { - log( - 'error', - `Unknown serviceType in _unifiedServiceRunner: ${serviceType}` - ); - finalMainResult = providerResponse; - } + let finalMainResult; + if (serviceType === "generateText") { + finalMainResult = providerResponse.text; + } else if (serviceType === "generateObject") { + finalMainResult = providerResponse.object; + } else if (serviceType === "streamText") { + finalMainResult = providerResponse; + } else { + log( + "error", + `Unknown serviceType in _unifiedServiceRunner: ${serviceType}` + ); + finalMainResult = providerResponse; + } - return { - mainResult: finalMainResult, - telemetryData: telemetryData - }; - } catch (error) { - const cleanMessage = _extractErrorMessage(error); - log( - 'error', - `Service call failed for role ${currentRole} (Provider: ${providerName || 'unknown'}, Model: ${modelId || 'unknown'}): ${cleanMessage}` - ); - lastError = error; - lastCleanErrorMessage = cleanMessage; + return { + mainResult: finalMainResult, + telemetryData: telemetryData, + }; + } catch (error) { + const cleanMessage = _extractErrorMessage(error); + log( + "error", + `Service call failed for role ${currentRole} (Provider: ${providerName || "unknown"}, Model: ${modelId || "unknown"}): ${cleanMessage}` + ); + lastError = error; + lastCleanErrorMessage = cleanMessage; - if (serviceType === 'generateObject') { - const lowerCaseMessage = cleanMessage.toLowerCase(); - if ( - lowerCaseMessage.includes( - 'no endpoints found that support tool use' - ) || - lowerCaseMessage.includes('does not support tool_use') || - lowerCaseMessage.includes('tool use is not supported') || - lowerCaseMessage.includes('tools are not supported') || - lowerCaseMessage.includes('function calling is not supported') - ) { - const specificErrorMsg = `Model '${modelId || 'unknown'}' via provider '${providerName || 'unknown'}' does not support the 'tool use' required by generateObjectService. Please configure a model that supports tool/function calling for the '${currentRole}' role, or use generateTextService if structured output is not strictly required.`; - log('error', `[Tool Support Error] ${specificErrorMsg}`); - throw new Error(specificErrorMsg); - } - } - } - } + if (serviceType === "generateObject") { + const lowerCaseMessage = cleanMessage.toLowerCase(); + if ( + lowerCaseMessage.includes( + "no endpoints found that support tool use" + ) || + lowerCaseMessage.includes("does not support tool_use") || + lowerCaseMessage.includes("tool use is not supported") || + lowerCaseMessage.includes("tools are not supported") || + lowerCaseMessage.includes("function calling is not supported") || + lowerCaseMessage.includes("tool use is not supported") + ) { + const specificErrorMsg = `Model '${modelId || "unknown"}' via provider '${providerName || "unknown"}' does not support the 'tool use' required by generateObjectService. Please configure a model that supports tool/function calling for the '${currentRole}' role, or use generateTextService if structured output is not strictly required.`; + log("error", `[Tool Support Error] ${specificErrorMsg}`); + throw new Error(specificErrorMsg); + } + } + } + } - log('error', `All roles in the sequence [${sequence.join(', ')}] failed.`); - throw new Error(lastCleanErrorMessage); + log("error", `All roles in the sequence [${sequence.join(", ")}] failed.`); + throw new Error(lastCleanErrorMessage); } /** @@ -606,11 +607,11 @@ async function _unifiedServiceRunner(serviceType, params) { * @returns {Promise<object>} Result object containing generated text and usage data. */ async function generateTextService(params) { - // Ensure default outputType if not provided - const defaults = { outputType: 'cli' }; - const combinedParams = { ...defaults, ...params }; - // TODO: Validate commandName exists? - return _unifiedServiceRunner('generateText', combinedParams); + // Ensure default outputType if not provided + const defaults = { outputType: "cli" }; + const combinedParams = { ...defaults, ...params }; + // TODO: Validate commandName exists? + return _unifiedServiceRunner("generateText", combinedParams); } /** @@ -628,13 +629,13 @@ async function generateTextService(params) { * @returns {Promise<object>} Result object containing the stream and usage data. */ async function streamTextService(params) { - const defaults = { outputType: 'cli' }; - const combinedParams = { ...defaults, ...params }; - // TODO: Validate commandName exists? - // NOTE: Telemetry for streaming might be tricky as usage data often comes at the end. - // The current implementation logs *after* the stream is returned. - // We might need to adjust how usage is captured/logged for streams. - return _unifiedServiceRunner('streamText', combinedParams); + const defaults = { outputType: "cli" }; + const combinedParams = { ...defaults, ...params }; + // TODO: Validate commandName exists? + // NOTE: Telemetry for streaming might be tricky as usage data often comes at the end. + // The current implementation logs *after* the stream is returned. + // We might need to adjust how usage is captured/logged for streams. + return _unifiedServiceRunner("streamText", combinedParams); } /** @@ -655,14 +656,14 @@ async function streamTextService(params) { * @returns {Promise<object>} Result object containing the generated object and usage data. */ async function generateObjectService(params) { - const defaults = { - objectName: 'generated_object', - maxRetries: 3, - outputType: 'cli' - }; - const combinedParams = { ...defaults, ...params }; - // TODO: Validate commandName exists? - return _unifiedServiceRunner('generateObject', combinedParams); + const defaults = { + objectName: "generated_object", + maxRetries: 3, + outputType: "cli", + }; + const combinedParams = { ...defaults, ...params }; + // TODO: Validate commandName exists? + return _unifiedServiceRunner("generateObject", combinedParams); } // --- Telemetry Function --- @@ -678,61 +679,61 @@ async function generateObjectService(params) { * @param {number} params.outputTokens - Number of output tokens. */ async function logAiUsage({ - userId, - commandName, - providerName, - modelId, - inputTokens, - outputTokens, - outputType + userId, + commandName, + providerName, + modelId, + inputTokens, + outputTokens, + outputType, }) { - try { - const isMCP = outputType === 'mcp'; - const timestamp = new Date().toISOString(); - const totalTokens = (inputTokens || 0) + (outputTokens || 0); + try { + const isMCP = outputType === "mcp"; + const timestamp = new Date().toISOString(); + const totalTokens = (inputTokens || 0) + (outputTokens || 0); - // Destructure currency along with costs - const { inputCost, outputCost, currency } = _getCostForModel( - providerName, - modelId - ); + // Destructure currency along with costs + const { inputCost, outputCost, currency } = _getCostForModel( + providerName, + modelId + ); - const totalCost = - ((inputTokens || 0) / 1_000_000) * inputCost + - ((outputTokens || 0) / 1_000_000) * outputCost; + const totalCost = + ((inputTokens || 0) / 1_000_000) * inputCost + + ((outputTokens || 0) / 1_000_000) * outputCost; - const telemetryData = { - timestamp, - userId, - commandName, - modelUsed: modelId, // Consistent field name from requirements - providerName, // Keep provider name for context - inputTokens: inputTokens || 0, - outputTokens: outputTokens || 0, - totalTokens, - totalCost: parseFloat(totalCost.toFixed(6)), - currency // Add currency to the telemetry data - }; + const telemetryData = { + timestamp, + userId, + commandName, + modelUsed: modelId, // Consistent field name from requirements + providerName, // Keep provider name for context + inputTokens: inputTokens || 0, + outputTokens: outputTokens || 0, + totalTokens, + totalCost: parseFloat(totalCost.toFixed(6)), + currency, // Add currency to the telemetry data + }; - if (getDebugFlag()) { - log('info', 'AI Usage Telemetry:', telemetryData); - } + if (getDebugFlag()) { + log("info", "AI Usage Telemetry:", telemetryData); + } - // TODO (Subtask 77.2): Send telemetryData securely to the external endpoint. + // TODO (Subtask 77.2): Send telemetryData securely to the external endpoint. - return telemetryData; - } catch (error) { - log('error', `Failed to log AI usage telemetry: ${error.message}`, { - error - }); - // Don't re-throw; telemetry failure shouldn't block core functionality. - return null; - } + return telemetryData; + } catch (error) { + log("error", `Failed to log AI usage telemetry: ${error.message}`, { + error, + }); + // Don't re-throw; telemetry failure shouldn't block core functionality. + return null; + } } export { - generateTextService, - streamTextService, - generateObjectService, - logAiUsage + generateTextService, + streamTextService, + generateObjectService, + logAiUsage, }; diff --git a/scripts/modules/supported-models.json b/scripts/modules/supported-models.json index 944abca3..fac3da0d 100644 --- a/scripts/modules/supported-models.json +++ b/scripts/modules/supported-models.json @@ -1,427 +1,427 @@ { - "anthropic": [ - { - "id": "claude-sonnet-4-20250514", - "swe_score": 0.727, - "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 64000 - }, - { - "id": "claude-opus-4-20250514", - "swe_score": 0.725, - "cost_per_1m_tokens": { "input": 15.0, "output": 75.0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 32000 - }, - { - "id": "claude-3-7-sonnet-20250219", - "swe_score": 0.623, - "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 120000 - }, - { - "id": "claude-3-5-sonnet-20241022", - "swe_score": 0.49, - "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 64000 - } - ], - "openai": [ - { - "id": "gpt-4o", - "swe_score": 0.332, - "cost_per_1m_tokens": { "input": 2.5, "output": 10.0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 16384 - }, - { - "id": "o1", - "swe_score": 0.489, - "cost_per_1m_tokens": { "input": 15.0, "output": 60.0 }, - "allowed_roles": ["main"] - }, - { - "id": "o3", - "swe_score": 0.5, - "cost_per_1m_tokens": { "input": 10.0, "output": 40.0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "o3-mini", - "swe_score": 0.493, - "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, - "allowed_roles": ["main"], - "max_tokens": 100000 - }, - { - "id": "o4-mini", - "swe_score": 0.45, - "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "o1-mini", - "swe_score": 0.4, - "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, - "allowed_roles": ["main"] - }, - { - "id": "o1-pro", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 150.0, "output": 600.0 }, - "allowed_roles": ["main"] - }, - { - "id": "gpt-4-5-preview", - "swe_score": 0.38, - "cost_per_1m_tokens": { "input": 75.0, "output": 150.0 }, - "allowed_roles": ["main"] - }, - { - "id": "gpt-4-1-mini", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.4, "output": 1.6 }, - "allowed_roles": ["main"] - }, - { - "id": "gpt-4-1-nano", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.1, "output": 0.4 }, - "allowed_roles": ["main"] - }, - { - "id": "gpt-4o-mini", - "swe_score": 0.3, - "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, - "allowed_roles": ["main"] - }, - { - "id": "gpt-4o-search-preview", - "swe_score": 0.33, - "cost_per_1m_tokens": { "input": 2.5, "output": 10.0 }, - "allowed_roles": ["research"] - }, - { - "id": "gpt-4o-mini-search-preview", - "swe_score": 0.3, - "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, - "allowed_roles": ["research"] - } - ], - "google": [ - { - "id": "gemini-2.5-pro-preview-05-06", - "swe_score": 0.638, - "cost_per_1m_tokens": null, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048000 - }, - { - "id": "gemini-2.5-pro-preview-03-25", - "swe_score": 0.638, - "cost_per_1m_tokens": null, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048000 - }, - { - "id": "gemini-2.5-flash-preview-04-17", - "swe_score": 0, - "cost_per_1m_tokens": null, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048000 - }, - { - "id": "gemini-2.0-flash", - "swe_score": 0.754, - "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048000 - }, - { - "id": "gemini-2.0-flash-lite", - "swe_score": 0, - "cost_per_1m_tokens": null, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048000 - } - ], - "perplexity": [ - { - "id": "sonar-pro", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 3, "output": 15 }, - "allowed_roles": ["research"], - "max_tokens": 8700 - }, - { - "id": "sonar", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 1, "output": 1 }, - "allowed_roles": ["research"], - "max_tokens": 8700 - }, - { - "id": "deep-research", - "swe_score": 0.211, - "cost_per_1m_tokens": { "input": 2, "output": 8 }, - "allowed_roles": ["research"], - "max_tokens": 8700 - }, - { - "id": "sonar-reasoning-pro", - "swe_score": 0.211, - "cost_per_1m_tokens": { "input": 2, "output": 8 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 8700 - }, - { - "id": "sonar-reasoning", - "swe_score": 0.211, - "cost_per_1m_tokens": { "input": 1, "output": 5 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 8700 - } - ], - "xai": [ - { - "id": "grok-3", - "name": "Grok 3", - "swe_score": null, - "cost_per_1m_tokens": { "input": 3, "output": 15 }, - "allowed_roles": ["main", "fallback", "research"], - "max_tokens": 131072 - }, - { - "id": "grok-3-fast", - "name": "Grok 3 Fast", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 5, "output": 25 }, - "allowed_roles": ["main", "fallback", "research"], - "max_tokens": 131072 - } - ], - "ollama": [ - { - "id": "devstral:latest", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "qwen3:latest", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "qwen3:14b", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "qwen3:32b", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "mistral-small3.1:latest", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "llama3.3:latest", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "phi4:latest", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - } - ], - "openrouter": [ - { - "id": "google/gemini-2.5-flash-preview-05-20", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048576 - }, - { - "id": "google/gemini-2.5-flash-preview-05-20:thinking", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.15, "output": 3.5 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048576 - }, - { - "id": "google/gemini-2.5-pro-exp-03-25", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "deepseek/deepseek-chat-v3-0324:free", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 163840 - }, - { - "id": "deepseek/deepseek-chat-v3-0324", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.27, "output": 1.1 }, - "allowed_roles": ["main"], - "max_tokens": 64000 - }, - { - "id": "openai/gpt-4.1", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 2, "output": 8 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "openai/gpt-4.1-mini", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.4, "output": 1.6 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "openai/gpt-4.1-nano", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.1, "output": 0.4 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "openai/o3", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 10, "output": 40 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 200000 - }, - { - "id": "openai/codex-mini", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 1.5, "output": 6 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 100000 - }, - { - "id": "openai/gpt-4o-mini", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 100000 - }, - { - "id": "openai/o4-mini", - "swe_score": 0.45, - "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 100000 - }, - { - "id": "openai/o4-mini-high", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 100000 - }, - { - "id": "openai/o1-pro", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 150, "output": 600 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 100000 - }, - { - "id": "meta-llama/llama-3.3-70b-instruct", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 120, "output": 600 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048576 - }, - { - "id": "meta-llama/llama-4-maverick", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.18, "output": 0.6 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "meta-llama/llama-4-scout", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.08, "output": 0.3 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "qwen/qwen-max", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 1.6, "output": 6.4 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 32768 - }, - { - "id": "qwen/qwen-turbo", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.05, "output": 0.2 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "qwen/qwen3-235b-a22b", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.14, "output": 2 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 24000 - }, - { - "id": "mistralai/mistral-small-3.1-24b-instruct:free", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 96000 - }, - { - "id": "mistralai/mistral-small-3.1-24b-instruct", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.1, "output": 0.3 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 128000 - }, - { - "id": "mistralai/devstral-small", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.1, "output": 0.3 }, - "allowed_roles": ["main"], - "max_tokens": 110000 - }, - { - "id": "mistralai/mistral-nemo", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.03, "output": 0.07 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 100000 - }, - { - "id": "thudm/glm-4-32b:free", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 32768 - } - ] + "anthropic": [ + { + "id": "claude-sonnet-4-20250514", + "swe_score": 0.727, + "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 64000 + }, + { + "id": "claude-opus-4-20250514", + "swe_score": 0.725, + "cost_per_1m_tokens": { "input": 15.0, "output": 75.0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 32000 + }, + { + "id": "claude-3-7-sonnet-20250219", + "swe_score": 0.623, + "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 120000 + }, + { + "id": "claude-3-5-sonnet-20241022", + "swe_score": 0.49, + "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 64000 + } + ], + "openai": [ + { + "id": "gpt-4o", + "swe_score": 0.332, + "cost_per_1m_tokens": { "input": 2.5, "output": 10.0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 16384 + }, + { + "id": "o1", + "swe_score": 0.489, + "cost_per_1m_tokens": { "input": 15.0, "output": 60.0 }, + "allowed_roles": ["main"] + }, + { + "id": "o3", + "swe_score": 0.5, + "cost_per_1m_tokens": { "input": 10.0, "output": 40.0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "o3-mini", + "swe_score": 0.493, + "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, + "allowed_roles": ["main"], + "max_tokens": 100000 + }, + { + "id": "o4-mini", + "swe_score": 0.45, + "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "o1-mini", + "swe_score": 0.4, + "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, + "allowed_roles": ["main"] + }, + { + "id": "o1-pro", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 150.0, "output": 600.0 }, + "allowed_roles": ["main"] + }, + { + "id": "gpt-4-5-preview", + "swe_score": 0.38, + "cost_per_1m_tokens": { "input": 75.0, "output": 150.0 }, + "allowed_roles": ["main"] + }, + { + "id": "gpt-4-1-mini", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.4, "output": 1.6 }, + "allowed_roles": ["main"] + }, + { + "id": "gpt-4-1-nano", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.1, "output": 0.4 }, + "allowed_roles": ["main"] + }, + { + "id": "gpt-4o-mini", + "swe_score": 0.3, + "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, + "allowed_roles": ["main"] + }, + { + "id": "gpt-4o-search-preview", + "swe_score": 0.33, + "cost_per_1m_tokens": { "input": 2.5, "output": 10.0 }, + "allowed_roles": ["research"] + }, + { + "id": "gpt-4o-mini-search-preview", + "swe_score": 0.3, + "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, + "allowed_roles": ["research"] + } + ], + "google": [ + { + "id": "gemini-2.5-pro-preview-05-06", + "swe_score": 0.638, + "cost_per_1m_tokens": null, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048000 + }, + { + "id": "gemini-2.5-pro-preview-03-25", + "swe_score": 0.638, + "cost_per_1m_tokens": null, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048000 + }, + { + "id": "gemini-2.5-flash-preview-04-17", + "swe_score": 0, + "cost_per_1m_tokens": null, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048000 + }, + { + "id": "gemini-2.0-flash", + "swe_score": 0.754, + "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048000 + }, + { + "id": "gemini-2.0-flash-lite", + "swe_score": 0, + "cost_per_1m_tokens": null, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048000 + } + ], + "perplexity": [ + { + "id": "sonar-pro", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 3, "output": 15 }, + "allowed_roles": ["main", "research"], + "max_tokens": 8700 + }, + { + "id": "sonar", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 1, "output": 1 }, + "allowed_roles": ["research"], + "max_tokens": 8700 + }, + { + "id": "deep-research", + "swe_score": 0.211, + "cost_per_1m_tokens": { "input": 2, "output": 8 }, + "allowed_roles": ["research"], + "max_tokens": 8700 + }, + { + "id": "sonar-reasoning-pro", + "swe_score": 0.211, + "cost_per_1m_tokens": { "input": 2, "output": 8 }, + "allowed_roles": ["main", "research", "fallback"], + "max_tokens": 8700 + }, + { + "id": "sonar-reasoning", + "swe_score": 0.211, + "cost_per_1m_tokens": { "input": 1, "output": 5 }, + "allowed_roles": ["main", "research", "fallback"], + "max_tokens": 8700 + } + ], + "xai": [ + { + "id": "grok-3", + "name": "Grok 3", + "swe_score": null, + "cost_per_1m_tokens": { "input": 3, "output": 15 }, + "allowed_roles": ["main", "fallback", "research"], + "max_tokens": 131072 + }, + { + "id": "grok-3-fast", + "name": "Grok 3 Fast", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 5, "output": 25 }, + "allowed_roles": ["main", "fallback", "research"], + "max_tokens": 131072 + } + ], + "ollama": [ + { + "id": "devstral:latest", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "qwen3:latest", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "qwen3:14b", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "qwen3:32b", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "mistral-small3.1:latest", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "llama3.3:latest", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "phi4:latest", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + } + ], + "openrouter": [ + { + "id": "google/gemini-2.5-flash-preview-05-20", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048576 + }, + { + "id": "google/gemini-2.5-flash-preview-05-20:thinking", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.15, "output": 3.5 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048576 + }, + { + "id": "google/gemini-2.5-pro-exp-03-25", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "deepseek/deepseek-chat-v3-0324:free", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 163840 + }, + { + "id": "deepseek/deepseek-chat-v3-0324", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.27, "output": 1.1 }, + "allowed_roles": ["main"], + "max_tokens": 64000 + }, + { + "id": "openai/gpt-4.1", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 2, "output": 8 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "openai/gpt-4.1-mini", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.4, "output": 1.6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "openai/gpt-4.1-nano", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.1, "output": 0.4 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "openai/o3", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 10, "output": 40 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 200000 + }, + { + "id": "openai/codex-mini", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 1.5, "output": 6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "openai/gpt-4o-mini", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "openai/o4-mini", + "swe_score": 0.45, + "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "openai/o4-mini-high", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "openai/o1-pro", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 150, "output": 600 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "meta-llama/llama-3.3-70b-instruct", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 120, "output": 600 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048576 + }, + { + "id": "meta-llama/llama-4-maverick", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.18, "output": 0.6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "meta-llama/llama-4-scout", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.08, "output": 0.3 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "qwen/qwen-max", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 1.6, "output": 6.4 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 32768 + }, + { + "id": "qwen/qwen-turbo", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.05, "output": 0.2 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "qwen/qwen3-235b-a22b", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.14, "output": 2 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 24000 + }, + { + "id": "mistralai/mistral-small-3.1-24b-instruct:free", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 96000 + }, + { + "id": "mistralai/mistral-small-3.1-24b-instruct", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.1, "output": 0.3 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 128000 + }, + { + "id": "mistralai/devstral-small", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.1, "output": 0.3 }, + "allowed_roles": ["main"], + "max_tokens": 110000 + }, + { + "id": "mistralai/mistral-nemo", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.03, "output": 0.07 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "thudm/glm-4-32b:free", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 32768 + } + ] } diff --git a/src/ai-providers/base-provider.js b/src/ai-providers/base-provider.js index 46732502..b96561d7 100644 --- a/src/ai-providers/base-provider.js +++ b/src/ai-providers/base-provider.js @@ -1,214 +1,214 @@ -import { generateText, streamText, generateObject } from 'ai'; -import { log } from '../../scripts/modules/index.js'; +import { generateText, streamText, generateObject } from "ai"; +import { log } from "../../scripts/modules/index.js"; /** * Base class for all AI providers */ export class BaseAIProvider { - constructor() { - if (this.constructor === BaseAIProvider) { - throw new Error('BaseAIProvider cannot be instantiated directly'); - } + constructor() { + if (this.constructor === BaseAIProvider) { + throw new Error("BaseAIProvider cannot be instantiated directly"); + } - // Each provider must set their name - this.name = this.constructor.name; - } + // Each provider must set their name + this.name = this.constructor.name; + } - /** - * Validates authentication parameters - can be overridden by providers - * @param {object} params - Parameters to validate - */ - validateAuth(params) { - // Default: require API key (most providers need this) - if (!params.apiKey) { - throw new Error(`${this.name} API key is required`); - } - } + /** + * Validates authentication parameters - can be overridden by providers + * @param {object} params - Parameters to validate + */ + validateAuth(params) { + // Default: require API key (most providers need this) + if (!params.apiKey) { + throw new Error(`${this.name} API key is required`); + } + } - /** - * Validates common parameters across all methods - * @param {object} params - Parameters to validate - */ - validateParams(params) { - // Validate authentication (can be overridden by providers) - this.validateAuth(params); + /** + * Validates common parameters across all methods + * @param {object} params - Parameters to validate + */ + validateParams(params) { + // Validate authentication (can be overridden by providers) + this.validateAuth(params); - // Validate required model ID - if (!params.modelId) { - throw new Error(`${this.name} Model ID is required`); - } + // Validate required model ID + if (!params.modelId) { + throw new Error(`${this.name} Model ID is required`); + } - // Validate optional parameters - this.validateOptionalParams(params); - } + // Validate optional parameters + this.validateOptionalParams(params); + } - /** - * Validates optional parameters like temperature and maxTokens - * @param {object} params - Parameters to validate - */ - validateOptionalParams(params) { - if ( - params.temperature !== undefined && - (params.temperature < 0 || params.temperature > 1) - ) { - throw new Error('Temperature must be between 0 and 1'); - } - if (params.maxTokens !== undefined && params.maxTokens <= 0) { - throw new Error('maxTokens must be greater than 0'); - } - } + /** + * Validates optional parameters like temperature and maxTokens + * @param {object} params - Parameters to validate + */ + validateOptionalParams(params) { + if ( + params.temperature !== undefined && + (params.temperature < 0 || params.temperature > 1) + ) { + throw new Error("Temperature must be between 0 and 1"); + } + if (params.maxTokens !== undefined && params.maxTokens <= 0) { + throw new Error("maxTokens must be greater than 0"); + } + } - /** - * Validates message array structure - */ - validateMessages(messages) { - if (!messages || !Array.isArray(messages) || messages.length === 0) { - throw new Error('Invalid or empty messages array provided'); - } + /** + * Validates message array structure + */ + validateMessages(messages) { + if (!messages || !Array.isArray(messages) || messages.length === 0) { + throw new Error("Invalid or empty messages array provided"); + } - for (const msg of messages) { - if (!msg.role || !msg.content) { - throw new Error( - 'Invalid message format. Each message must have role and content' - ); - } - } - } + for (const msg of messages) { + if (!msg.role || !msg.content) { + throw new Error( + "Invalid message format. Each message must have role and content" + ); + } + } + } - /** - * Common error handler - */ - handleError(operation, error) { - const errorMessage = error.message || 'Unknown error occurred'; - log('error', `${this.name} ${operation} failed: ${errorMessage}`, { - error - }); - throw new Error( - `${this.name} API error during ${operation}: ${errorMessage}` - ); - } + /** + * Common error handler + */ + handleError(operation, error) { + const errorMessage = error.message || "Unknown error occurred"; + log("error", `${this.name} ${operation} failed: ${errorMessage}`, { + error, + }); + throw new Error( + `${this.name} API error during ${operation}: ${errorMessage}` + ); + } - /** - * Creates and returns a client instance for the provider - * @abstract - */ - getClient(params) { - throw new Error('getClient must be implemented by provider'); - } + /** + * Creates and returns a client instance for the provider + * @abstract + */ + getClient(params) { + throw new Error("getClient must be implemented by provider"); + } - /** - * Generates text using the provider's model - */ - async generateText(params) { - try { - this.validateParams(params); - this.validateMessages(params.messages); + /** + * Generates text using the provider's model + */ + async generateText(params) { + try { + this.validateParams(params); + this.validateMessages(params.messages); - log( - 'debug', - `Generating ${this.name} text with model: ${params.modelId}` - ); + log( + "debug", + `Generating ${this.name} text with model: ${params.modelId}` + ); - const client = this.getClient(params); - const result = await generateText({ - model: client(params.modelId), - messages: params.messages, - maxTokens: params.maxTokens, - temperature: params.temperature - }); + const client = this.getClient(params); + const result = await generateText({ + model: client(params.modelId), + messages: params.messages, + maxTokens: params.maxTokens, + temperature: params.temperature, + }); - log( - 'debug', - `${this.name} generateText completed successfully for model: ${params.modelId}` - ); + log( + "debug", + `${this.name} generateText completed successfully for model: ${params.modelId}` + ); - return { - text: result.text, - usage: { - inputTokens: result.usage?.promptTokens, - outputTokens: result.usage?.completionTokens, - totalTokens: result.usage?.totalTokens - } - }; - } catch (error) { - this.handleError('text generation', error); - } - } + return { + text: result.text, + usage: { + inputTokens: result.usage?.promptTokens, + outputTokens: result.usage?.completionTokens, + totalTokens: result.usage?.totalTokens, + }, + }; + } catch (error) { + this.handleError("text generation", error); + } + } - /** - * Streams text using the provider's model - */ - async streamText(params) { - try { - this.validateParams(params); - this.validateMessages(params.messages); + /** + * Streams text using the provider's model + */ + async streamText(params) { + try { + this.validateParams(params); + this.validateMessages(params.messages); - log('debug', `Streaming ${this.name} text with model: ${params.modelId}`); + log("debug", `Streaming ${this.name} text with model: ${params.modelId}`); - const client = this.getClient(params); - const stream = await streamText({ - model: client(params.modelId), - messages: params.messages, - maxTokens: params.maxTokens, - temperature: params.temperature - }); + const client = this.getClient(params); + const stream = await streamText({ + model: client(params.modelId), + messages: params.messages, + maxTokens: params.maxTokens, + temperature: params.temperature, + }); - log( - 'debug', - `${this.name} streamText initiated successfully for model: ${params.modelId}` - ); + log( + "debug", + `${this.name} streamText initiated successfully for model: ${params.modelId}` + ); - return stream; - } catch (error) { - this.handleError('text streaming', error); - } - } + return stream; + } catch (error) { + this.handleError("text streaming", error); + } + } - /** - * Generates a structured object using the provider's model - */ - async generateObject(params) { - try { - this.validateParams(params); - this.validateMessages(params.messages); + /** + * Generates a structured object using the provider's model + */ + async generateObject(params) { + try { + this.validateParams(params); + this.validateMessages(params.messages); - if (!params.schema) { - throw new Error('Schema is required for object generation'); - } - if (!params.objectName) { - throw new Error('Object name is required for object generation'); - } + if (!params.schema) { + throw new Error("Schema is required for object generation"); + } + if (!params.objectName) { + throw new Error("Object name is required for object generation"); + } - log( - 'debug', - `Generating ${this.name} object ('${params.objectName}') with model: ${params.modelId}` - ); + log( + "debug", + `Generating ${this.name} object ('${params.objectName}') with model: ${params.modelId}` + ); - const client = this.getClient(params); - const result = await generateObject({ - model: client(params.modelId), - messages: params.messages, - schema: params.schema, - mode: 'tool', - maxTokens: params.maxTokens, - temperature: params.temperature - }); + const client = this.getClient(params); + const result = await generateObject({ + model: client(params.modelId), + messages: params.messages, + schema: params.schema, + mode: "auto", + maxTokens: params.maxTokens, + temperature: params.temperature, + }); - log( - 'debug', - `${this.name} generateObject completed successfully for model: ${params.modelId}` - ); + log( + "debug", + `${this.name} generateObject completed successfully for model: ${params.modelId}` + ); - return { - object: result.object, - usage: { - inputTokens: result.usage?.promptTokens, - outputTokens: result.usage?.completionTokens, - totalTokens: result.usage?.totalTokens - } - }; - } catch (error) { - this.handleError('object generation', error); - } - } + return { + object: result.object, + usage: { + inputTokens: result.usage?.promptTokens, + outputTokens: result.usage?.completionTokens, + totalTokens: result.usage?.totalTokens, + }, + }; + } catch (error) { + this.handleError("object generation", error); + } + } } From bfd86eb9cc85527c2f21856c0851ef276205331e Mon Sep 17 00:00:00 2001 From: Eyal Toledano <eyal@microangel.so> Date: Sat, 7 Jun 2025 18:42:11 -0400 Subject: [PATCH 03/10] Adds qwen3-235n-a22b:free to supported models. Closes #687) --- scripts/modules/supported-models.json | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/scripts/modules/supported-models.json b/scripts/modules/supported-models.json index fac3da0d..3dbb4109 100644 --- a/scripts/modules/supported-models.json +++ b/scripts/modules/supported-models.json @@ -388,6 +388,13 @@ "allowed_roles": ["main", "fallback"], "max_tokens": 24000 }, + { + "id": "qwen/qwen3-235b-a22b:free", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.14, "output": 2 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 24000 + }, { "id": "mistralai/mistral-small-3.1-24b-instruct:free", "swe_score": 0, From cc04d53720317ffc4a147162564e0e2e19ac2397 Mon Sep 17 00:00:00 2001 From: Eyal Toledano <eyal@microangel.so> Date: Sat, 7 Jun 2025 18:54:11 -0400 Subject: [PATCH 04/10] chore: adds a warning when custom openrouter model is a free model which suffers from lower rate limits, restricted context, and, worst of all, no access to tool_use. --- .taskmaster/config.json | 4 +- .taskmaster/tasks/task_097.txt | 35 + .taskmaster/tasks/tasks.json | 16 + scripts/modules/supported-models.json | 7 - scripts/modules/task-manager/models.js | 1035 ++++++++++++------------ 5 files changed, 574 insertions(+), 523 deletions(-) create mode 100644 .taskmaster/tasks/task_097.txt diff --git a/.taskmaster/config.json b/.taskmaster/config.json index 442dfc1c..20cdb49c 100644 --- a/.taskmaster/config.json +++ b/.taskmaster/config.json @@ -1,8 +1,8 @@ { "models": { "main": { - "provider": "anthropic", - "modelId": "claude-sonnet-4-20250514", + "provider": "openrouter", + "modelId": "qwen/qwen3-235b-a22b:free", "maxTokens": 50000, "temperature": 0.2 }, diff --git a/.taskmaster/tasks/task_097.txt b/.taskmaster/tasks/task_097.txt new file mode 100644 index 00000000..2ef43dd6 --- /dev/null +++ b/.taskmaster/tasks/task_097.txt @@ -0,0 +1,35 @@ +# Task ID: 97 +# Title: Create Taskmaster Jingle Implementation +# Status: pending +# Dependencies: 95, 57, 3, 2 +# Priority: medium +# Description: Develop a musical jingle system for Taskmaster that plays sound effects during key CLI interactions to enhance user experience. +# Details: +This task involves implementing a sound system that plays audio cues during Taskmaster CLI operations. Key implementation steps include: + +1. Audio System Integration: + - Research and select appropriate audio library compatible with Node.js CLI applications + - Implement cross-platform audio playback (Windows, macOS, Linux) + - Create sound configuration options in .taskmasterconfig + +2. Jingle Design: + - Define sound triggers for key events (task creation, completion, errors, etc.) + - Create or source appropriate sound files (WAV/MP3 format) + - Implement volume control and mute option in settings + +3. CLI Integration: + - Add sound playback to core CLI commands (init, create, update, delete) + - Implement optional sound effects toggle via command line flags + - Ensure audio playback doesn't interfere with CLI performance + +4. Documentation: + - Update user guide with sound configuration instructions + - Add troubleshooting section for audio playback issues + +# Test Strategy: +1. Verify audio plays correctly during each supported CLI operation +2. Test sound configuration options across different platforms +3. Confirm volume control and mute functionality works as expected +4. Validate that audio playback doesn't affect CLI performance +5. Test edge cases (no audio hardware, invalid sound files, etc.) +6. Ensure sound effects can be disabled via configuration and CLI flags diff --git a/.taskmaster/tasks/tasks.json b/.taskmaster/tasks/tasks.json index c8cd9c33..8cc174d5 100644 --- a/.taskmaster/tasks/tasks.json +++ b/.taskmaster/tasks/tasks.json @@ -5871,6 +5871,22 @@ "parentTaskId": 96 } ] + }, + { + "id": 97, + "title": "Create Taskmaster Jingle Implementation", + "description": "Develop a musical jingle system for Taskmaster that plays sound effects during key CLI interactions to enhance user experience.", + "details": "This task involves implementing a sound system that plays audio cues during Taskmaster CLI operations. Key implementation steps include:\n\n1. Audio System Integration:\n - Research and select appropriate audio library compatible with Node.js CLI applications\n - Implement cross-platform audio playback (Windows, macOS, Linux)\n - Create sound configuration options in .taskmasterconfig\n\n2. Jingle Design:\n - Define sound triggers for key events (task creation, completion, errors, etc.)\n - Create or source appropriate sound files (WAV/MP3 format)\n - Implement volume control and mute option in settings\n\n3. CLI Integration:\n - Add sound playback to core CLI commands (init, create, update, delete)\n - Implement optional sound effects toggle via command line flags\n - Ensure audio playback doesn't interfere with CLI performance\n\n4. Documentation:\n - Update user guide with sound configuration instructions\n - Add troubleshooting section for audio playback issues", + "testStrategy": "1. Verify audio plays correctly during each supported CLI operation\n2. Test sound configuration options across different platforms\n3. Confirm volume control and mute functionality works as expected\n4. Validate that audio playback doesn't affect CLI performance\n5. Test edge cases (no audio hardware, invalid sound files, etc.)\n6. Ensure sound effects can be disabled via configuration and CLI flags", + "status": "pending", + "dependencies": [ + 95, + 57, + 3, + 2 + ], + "priority": "medium", + "subtasks": [] } ] } \ No newline at end of file diff --git a/scripts/modules/supported-models.json b/scripts/modules/supported-models.json index 3dbb4109..fac3da0d 100644 --- a/scripts/modules/supported-models.json +++ b/scripts/modules/supported-models.json @@ -388,13 +388,6 @@ "allowed_roles": ["main", "fallback"], "max_tokens": 24000 }, - { - "id": "qwen/qwen3-235b-a22b:free", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.14, "output": 2 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 24000 - }, { "id": "mistralai/mistral-small-3.1-24b-instruct:free", "swe_score": 0, diff --git a/scripts/modules/task-manager/models.js b/scripts/modules/task-manager/models.js index 8416c616..5fe02d8a 100644 --- a/scripts/modules/task-manager/models.js +++ b/scripts/modules/task-manager/models.js @@ -3,71 +3,71 @@ * Core functionality for managing AI model configurations */ -import https from 'https'; -import http from 'http'; +import https from "https"; +import http from "http"; import { - getMainModelId, - getResearchModelId, - getFallbackModelId, - getAvailableModels, - getMainProvider, - getResearchProvider, - getFallbackProvider, - isApiKeySet, - getMcpApiKeyStatus, - getConfig, - writeConfig, - isConfigFilePresent, - getAllProviders, - getBaseUrlForRole -} from '../config-manager.js'; -import { findConfigPath } from '../../../src/utils/path-utils.js'; -import { log } from '../utils.js'; + getMainModelId, + getResearchModelId, + getFallbackModelId, + getAvailableModels, + getMainProvider, + getResearchProvider, + getFallbackProvider, + isApiKeySet, + getMcpApiKeyStatus, + getConfig, + writeConfig, + isConfigFilePresent, + getAllProviders, + getBaseUrlForRole, +} from "../config-manager.js"; +import { findConfigPath } from "../../../src/utils/path-utils.js"; +import { log } from "../utils.js"; /** * Fetches the list of models from OpenRouter API. * @returns {Promise<Array|null>} A promise that resolves with the list of model IDs or null if fetch fails. */ function fetchOpenRouterModels() { - return new Promise((resolve) => { - const options = { - hostname: 'openrouter.ai', - path: '/api/v1/models', - method: 'GET', - headers: { - Accept: 'application/json' - } - }; + return new Promise((resolve) => { + const options = { + hostname: "openrouter.ai", + path: "/api/v1/models", + method: "GET", + headers: { + Accept: "application/json", + }, + }; - const req = https.request(options, (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - if (res.statusCode === 200) { - try { - const parsedData = JSON.parse(data); - resolve(parsedData.data || []); // Return the array of models - } catch (e) { - console.error('Error parsing OpenRouter response:', e); - resolve(null); // Indicate failure - } - } else { - console.error( - `OpenRouter API request failed with status code: ${res.statusCode}` - ); - resolve(null); // Indicate failure - } - }); - }); + const req = https.request(options, (res) => { + let data = ""; + res.on("data", (chunk) => { + data += chunk; + }); + res.on("end", () => { + if (res.statusCode === 200) { + try { + const parsedData = JSON.parse(data); + resolve(parsedData.data || []); // Return the array of models + } catch (e) { + console.error("Error parsing OpenRouter response:", e); + resolve(null); // Indicate failure + } + } else { + console.error( + `OpenRouter API request failed with status code: ${res.statusCode}` + ); + resolve(null); // Indicate failure + } + }); + }); - req.on('error', (e) => { - console.error('Error fetching OpenRouter models:', e); - resolve(null); // Indicate failure - }); - req.end(); - }); + req.on("error", (e) => { + console.error("Error fetching OpenRouter models:", e); + resolve(null); // Indicate failure + }); + req.end(); + }); } /** @@ -75,61 +75,61 @@ function fetchOpenRouterModels() { * @param {string} baseURL - The base URL for the Ollama API (e.g., "http://localhost:11434/api") * @returns {Promise<Array|null>} A promise that resolves with the list of model objects or null if fetch fails. */ -function fetchOllamaModels(baseURL = 'http://localhost:11434/api') { - return new Promise((resolve) => { - try { - // Parse the base URL to extract hostname, port, and base path - const url = new URL(baseURL); - const isHttps = url.protocol === 'https:'; - const port = url.port || (isHttps ? 443 : 80); - const basePath = url.pathname.endsWith('/') - ? url.pathname.slice(0, -1) - : url.pathname; +function fetchOllamaModels(baseURL = "http://localhost:11434/api") { + return new Promise((resolve) => { + try { + // Parse the base URL to extract hostname, port, and base path + const url = new URL(baseURL); + const isHttps = url.protocol === "https:"; + const port = url.port || (isHttps ? 443 : 80); + const basePath = url.pathname.endsWith("/") + ? url.pathname.slice(0, -1) + : url.pathname; - const options = { - hostname: url.hostname, - port: parseInt(port, 10), - path: `${basePath}/tags`, - method: 'GET', - headers: { - Accept: 'application/json' - } - }; + const options = { + hostname: url.hostname, + port: parseInt(port, 10), + path: `${basePath}/tags`, + method: "GET", + headers: { + Accept: "application/json", + }, + }; - const requestLib = isHttps ? https : http; - const req = requestLib.request(options, (res) => { - let data = ''; - res.on('data', (chunk) => { - data += chunk; - }); - res.on('end', () => { - if (res.statusCode === 200) { - try { - const parsedData = JSON.parse(data); - resolve(parsedData.models || []); // Return the array of models - } catch (e) { - console.error('Error parsing Ollama response:', e); - resolve(null); // Indicate failure - } - } else { - console.error( - `Ollama API request failed with status code: ${res.statusCode}` - ); - resolve(null); // Indicate failure - } - }); - }); + const requestLib = isHttps ? https : http; + const req = requestLib.request(options, (res) => { + let data = ""; + res.on("data", (chunk) => { + data += chunk; + }); + res.on("end", () => { + if (res.statusCode === 200) { + try { + const parsedData = JSON.parse(data); + resolve(parsedData.models || []); // Return the array of models + } catch (e) { + console.error("Error parsing Ollama response:", e); + resolve(null); // Indicate failure + } + } else { + console.error( + `Ollama API request failed with status code: ${res.statusCode}` + ); + resolve(null); // Indicate failure + } + }); + }); - req.on('error', (e) => { - console.error('Error fetching Ollama models:', e); - resolve(null); // Indicate failure - }); - req.end(); - } catch (e) { - console.error('Error parsing Ollama base URL:', e); - resolve(null); // Indicate failure - } - }); + req.on("error", (e) => { + console.error("Error fetching Ollama models:", e); + resolve(null); // Indicate failure + }); + req.end(); + } catch (e) { + console.error("Error parsing Ollama base URL:", e); + resolve(null); // Indicate failure + } + }); } /** @@ -141,125 +141,125 @@ function fetchOllamaModels(baseURL = 'http://localhost:11434/api') { * @returns {Object} RESTful response with current model configuration */ async function getModelConfiguration(options = {}) { - const { mcpLog, projectRoot, session } = options; + const { mcpLog, projectRoot, session } = options; - const report = (level, ...args) => { - if (mcpLog && typeof mcpLog[level] === 'function') { - mcpLog[level](...args); - } - }; + const report = (level, ...args) => { + if (mcpLog && typeof mcpLog[level] === "function") { + mcpLog[level](...args); + } + }; - if (!projectRoot) { - throw new Error('Project root is required but not found.'); - } + if (!projectRoot) { + throw new Error("Project root is required but not found."); + } - // Use centralized config path finding instead of hardcoded path - const configPath = findConfigPath(null, { projectRoot }); - const configExists = isConfigFilePresent(projectRoot); + // Use centralized config path finding instead of hardcoded path + const configPath = findConfigPath(null, { projectRoot }); + const configExists = isConfigFilePresent(projectRoot); - log( - 'debug', - `Checking for config file using findConfigPath, found: ${configPath}` - ); - log( - 'debug', - `Checking config file using isConfigFilePresent(), exists: ${configExists}` - ); + log( + "debug", + `Checking for config file using findConfigPath, found: ${configPath}` + ); + log( + "debug", + `Checking config file using isConfigFilePresent(), exists: ${configExists}` + ); - if (!configExists) { - throw new Error( - 'The configuration file is missing. Run "task-master models --setup" to create it.' - ); - } + if (!configExists) { + throw new Error( + 'The configuration file is missing. Run "task-master models --setup" to create it.' + ); + } - try { - // Get current settings - these should use the config from the found path automatically - const mainProvider = getMainProvider(projectRoot); - const mainModelId = getMainModelId(projectRoot); - const researchProvider = getResearchProvider(projectRoot); - const researchModelId = getResearchModelId(projectRoot); - const fallbackProvider = getFallbackProvider(projectRoot); - const fallbackModelId = getFallbackModelId(projectRoot); + try { + // Get current settings - these should use the config from the found path automatically + const mainProvider = getMainProvider(projectRoot); + const mainModelId = getMainModelId(projectRoot); + const researchProvider = getResearchProvider(projectRoot); + const researchModelId = getResearchModelId(projectRoot); + const fallbackProvider = getFallbackProvider(projectRoot); + const fallbackModelId = getFallbackModelId(projectRoot); - // Check API keys - const mainCliKeyOk = isApiKeySet(mainProvider, session, projectRoot); - const mainMcpKeyOk = getMcpApiKeyStatus(mainProvider, projectRoot); - const researchCliKeyOk = isApiKeySet( - researchProvider, - session, - projectRoot - ); - const researchMcpKeyOk = getMcpApiKeyStatus(researchProvider, projectRoot); - const fallbackCliKeyOk = fallbackProvider - ? isApiKeySet(fallbackProvider, session, projectRoot) - : true; - const fallbackMcpKeyOk = fallbackProvider - ? getMcpApiKeyStatus(fallbackProvider, projectRoot) - : true; + // Check API keys + const mainCliKeyOk = isApiKeySet(mainProvider, session, projectRoot); + const mainMcpKeyOk = getMcpApiKeyStatus(mainProvider, projectRoot); + const researchCliKeyOk = isApiKeySet( + researchProvider, + session, + projectRoot + ); + const researchMcpKeyOk = getMcpApiKeyStatus(researchProvider, projectRoot); + const fallbackCliKeyOk = fallbackProvider + ? isApiKeySet(fallbackProvider, session, projectRoot) + : true; + const fallbackMcpKeyOk = fallbackProvider + ? getMcpApiKeyStatus(fallbackProvider, projectRoot) + : true; - // Get available models to find detailed info - const availableModels = getAvailableModels(projectRoot); + // Get available models to find detailed info + const availableModels = getAvailableModels(projectRoot); - // Find model details - const mainModelData = availableModels.find((m) => m.id === mainModelId); - const researchModelData = availableModels.find( - (m) => m.id === researchModelId - ); - const fallbackModelData = fallbackModelId - ? availableModels.find((m) => m.id === fallbackModelId) - : null; + // Find model details + const mainModelData = availableModels.find((m) => m.id === mainModelId); + const researchModelData = availableModels.find( + (m) => m.id === researchModelId + ); + const fallbackModelData = fallbackModelId + ? availableModels.find((m) => m.id === fallbackModelId) + : null; - // Return structured configuration data - return { - success: true, - data: { - activeModels: { - main: { - provider: mainProvider, - modelId: mainModelId, - sweScore: mainModelData?.swe_score || null, - cost: mainModelData?.cost_per_1m_tokens || null, - keyStatus: { - cli: mainCliKeyOk, - mcp: mainMcpKeyOk - } - }, - research: { - provider: researchProvider, - modelId: researchModelId, - sweScore: researchModelData?.swe_score || null, - cost: researchModelData?.cost_per_1m_tokens || null, - keyStatus: { - cli: researchCliKeyOk, - mcp: researchMcpKeyOk - } - }, - fallback: fallbackProvider - ? { - provider: fallbackProvider, - modelId: fallbackModelId, - sweScore: fallbackModelData?.swe_score || null, - cost: fallbackModelData?.cost_per_1m_tokens || null, - keyStatus: { - cli: fallbackCliKeyOk, - mcp: fallbackMcpKeyOk - } - } - : null - }, - message: 'Successfully retrieved current model configuration' - } - }; - } catch (error) { - report('error', `Error getting model configuration: ${error.message}`); - return { - success: false, - error: { - code: 'CONFIG_ERROR', - message: error.message - } - }; - } + // Return structured configuration data + return { + success: true, + data: { + activeModels: { + main: { + provider: mainProvider, + modelId: mainModelId, + sweScore: mainModelData?.swe_score || null, + cost: mainModelData?.cost_per_1m_tokens || null, + keyStatus: { + cli: mainCliKeyOk, + mcp: mainMcpKeyOk, + }, + }, + research: { + provider: researchProvider, + modelId: researchModelId, + sweScore: researchModelData?.swe_score || null, + cost: researchModelData?.cost_per_1m_tokens || null, + keyStatus: { + cli: researchCliKeyOk, + mcp: researchMcpKeyOk, + }, + }, + fallback: fallbackProvider + ? { + provider: fallbackProvider, + modelId: fallbackModelId, + sweScore: fallbackModelData?.swe_score || null, + cost: fallbackModelData?.cost_per_1m_tokens || null, + keyStatus: { + cli: fallbackCliKeyOk, + mcp: fallbackMcpKeyOk, + }, + } + : null, + }, + message: "Successfully retrieved current model configuration", + }, + }; + } catch (error) { + report("error", `Error getting model configuration: ${error.message}`); + return { + success: false, + error: { + code: "CONFIG_ERROR", + message: error.message, + }, + }; + } } /** @@ -271,85 +271,85 @@ async function getModelConfiguration(options = {}) { * @returns {Object} RESTful response with available models */ async function getAvailableModelsList(options = {}) { - const { mcpLog, projectRoot } = options; + const { mcpLog, projectRoot } = options; - const report = (level, ...args) => { - if (mcpLog && typeof mcpLog[level] === 'function') { - mcpLog[level](...args); - } - }; + const report = (level, ...args) => { + if (mcpLog && typeof mcpLog[level] === "function") { + mcpLog[level](...args); + } + }; - if (!projectRoot) { - throw new Error('Project root is required but not found.'); - } + if (!projectRoot) { + throw new Error("Project root is required but not found."); + } - // Use centralized config path finding instead of hardcoded path - const configPath = findConfigPath(null, { projectRoot }); - const configExists = isConfigFilePresent(projectRoot); + // Use centralized config path finding instead of hardcoded path + const configPath = findConfigPath(null, { projectRoot }); + const configExists = isConfigFilePresent(projectRoot); - log( - 'debug', - `Checking for config file using findConfigPath, found: ${configPath}` - ); - log( - 'debug', - `Checking config file using isConfigFilePresent(), exists: ${configExists}` - ); + log( + "debug", + `Checking for config file using findConfigPath, found: ${configPath}` + ); + log( + "debug", + `Checking config file using isConfigFilePresent(), exists: ${configExists}` + ); - if (!configExists) { - throw new Error( - 'The configuration file is missing. Run "task-master models --setup" to create it.' - ); - } + if (!configExists) { + throw new Error( + 'The configuration file is missing. Run "task-master models --setup" to create it.' + ); + } - try { - // Get all available models - const allAvailableModels = getAvailableModels(projectRoot); + try { + // Get all available models + const allAvailableModels = getAvailableModels(projectRoot); - if (!allAvailableModels || allAvailableModels.length === 0) { - return { - success: true, - data: { - models: [], - message: 'No available models found' - } - }; - } + if (!allAvailableModels || allAvailableModels.length === 0) { + return { + success: true, + data: { + models: [], + message: "No available models found", + }, + }; + } - // Get currently used model IDs - const mainModelId = getMainModelId(projectRoot); - const researchModelId = getResearchModelId(projectRoot); - const fallbackModelId = getFallbackModelId(projectRoot); + // Get currently used model IDs + const mainModelId = getMainModelId(projectRoot); + const researchModelId = getResearchModelId(projectRoot); + const fallbackModelId = getFallbackModelId(projectRoot); - // Filter out placeholder models and active models - const activeIds = [mainModelId, researchModelId, fallbackModelId].filter( - Boolean - ); - const otherAvailableModels = allAvailableModels.map((model) => ({ - provider: model.provider || 'N/A', - modelId: model.id, - sweScore: model.swe_score || null, - cost: model.cost_per_1m_tokens || null, - allowedRoles: model.allowed_roles || [] - })); + // Filter out placeholder models and active models + const activeIds = [mainModelId, researchModelId, fallbackModelId].filter( + Boolean + ); + const otherAvailableModels = allAvailableModels.map((model) => ({ + provider: model.provider || "N/A", + modelId: model.id, + sweScore: model.swe_score || null, + cost: model.cost_per_1m_tokens || null, + allowedRoles: model.allowed_roles || [], + })); - return { - success: true, - data: { - models: otherAvailableModels, - message: `Successfully retrieved ${otherAvailableModels.length} available models` - } - }; - } catch (error) { - report('error', `Error getting available models: ${error.message}`); - return { - success: false, - error: { - code: 'MODELS_LIST_ERROR', - message: error.message - } - }; - } + return { + success: true, + data: { + models: otherAvailableModels, + message: `Successfully retrieved ${otherAvailableModels.length} available models`, + }, + }; + } catch (error) { + report("error", `Error getting available models: ${error.message}`); + return { + success: false, + error: { + code: "MODELS_LIST_ERROR", + message: error.message, + }, + }; + } } /** @@ -364,211 +364,218 @@ async function getAvailableModelsList(options = {}) { * @returns {Object} RESTful response with result of update operation */ async function setModel(role, modelId, options = {}) { - const { mcpLog, projectRoot, providerHint } = options; + const { mcpLog, projectRoot, providerHint } = options; - const report = (level, ...args) => { - if (mcpLog && typeof mcpLog[level] === 'function') { - mcpLog[level](...args); - } - }; + const report = (level, ...args) => { + if (mcpLog && typeof mcpLog[level] === "function") { + mcpLog[level](...args); + } + }; - if (!projectRoot) { - throw new Error('Project root is required but not found.'); - } + if (!projectRoot) { + throw new Error("Project root is required but not found."); + } - // Use centralized config path finding instead of hardcoded path - const configPath = findConfigPath(null, { projectRoot }); - const configExists = isConfigFilePresent(projectRoot); + // Use centralized config path finding instead of hardcoded path + const configPath = findConfigPath(null, { projectRoot }); + const configExists = isConfigFilePresent(projectRoot); - log( - 'debug', - `Checking for config file using findConfigPath, found: ${configPath}` - ); - log( - 'debug', - `Checking config file using isConfigFilePresent(), exists: ${configExists}` - ); + log( + "debug", + `Checking for config file using findConfigPath, found: ${configPath}` + ); + log( + "debug", + `Checking config file using isConfigFilePresent(), exists: ${configExists}` + ); - if (!configExists) { - throw new Error( - 'The configuration file is missing. Run "task-master models --setup" to create it.' - ); - } + if (!configExists) { + throw new Error( + 'The configuration file is missing. Run "task-master models --setup" to create it.' + ); + } - // Validate role - if (!['main', 'research', 'fallback'].includes(role)) { - return { - success: false, - error: { - code: 'INVALID_ROLE', - message: `Invalid role: ${role}. Must be one of: main, research, fallback.` - } - }; - } + // Validate role + if (!["main", "research", "fallback"].includes(role)) { + return { + success: false, + error: { + code: "INVALID_ROLE", + message: `Invalid role: ${role}. Must be one of: main, research, fallback.`, + }, + }; + } - // Validate model ID - if (typeof modelId !== 'string' || modelId.trim() === '') { - return { - success: false, - error: { - code: 'INVALID_MODEL_ID', - message: `Invalid model ID: ${modelId}. Must be a non-empty string.` - } - }; - } + // Validate model ID + if (typeof modelId !== "string" || modelId.trim() === "") { + return { + success: false, + error: { + code: "INVALID_MODEL_ID", + message: `Invalid model ID: ${modelId}. Must be a non-empty string.`, + }, + }; + } - try { - const availableModels = getAvailableModels(projectRoot); - const currentConfig = getConfig(projectRoot); - let determinedProvider = null; // Initialize provider - let warningMessage = null; + try { + const availableModels = getAvailableModels(projectRoot); + const currentConfig = getConfig(projectRoot); + let determinedProvider = null; // Initialize provider + let warningMessage = null; - // Find the model data in internal list initially to see if it exists at all - const modelData = availableModels.find((m) => m.id === modelId); + // Find the model data in internal list initially to see if it exists at all + const modelData = availableModels.find((m) => m.id === modelId); - // --- Revised Logic: Prioritize providerHint --- // + // --- Revised Logic: Prioritize providerHint --- // - if (providerHint) { - // Hint provided (--ollama or --openrouter flag used) - if (modelData && modelData.provider === providerHint) { - // Found internally AND provider matches the hint - determinedProvider = providerHint; - report( - 'info', - `Model ${modelId} found internally with matching provider hint ${determinedProvider}.` - ); - } else { - // Either not found internally, OR found but under a DIFFERENT provider than hinted. - // Proceed with custom logic based ONLY on the hint. - if (providerHint === 'openrouter') { - // Check OpenRouter ONLY because hint was openrouter - report('info', `Checking OpenRouter for ${modelId} (as hinted)...`); - const openRouterModels = await fetchOpenRouterModels(); + if (providerHint) { + // Hint provided (--ollama or --openrouter flag used) + if (modelData && modelData.provider === providerHint) { + // Found internally AND provider matches the hint + determinedProvider = providerHint; + report( + "info", + `Model ${modelId} found internally with matching provider hint ${determinedProvider}.` + ); + } else { + // Either not found internally, OR found but under a DIFFERENT provider than hinted. + // Proceed with custom logic based ONLY on the hint. + if (providerHint === "openrouter") { + // Check OpenRouter ONLY because hint was openrouter + report("info", `Checking OpenRouter for ${modelId} (as hinted)...`); + const openRouterModels = await fetchOpenRouterModels(); - if ( - openRouterModels && - openRouterModels.some((m) => m.id === modelId) - ) { - determinedProvider = 'openrouter'; - warningMessage = `Warning: Custom OpenRouter model '${modelId}' set. This model is not officially validated by Taskmaster and may not function as expected.`; - report('warn', warningMessage); - } else { - // Hinted as OpenRouter but not found in live check - throw new Error( - `Model ID "${modelId}" not found in the live OpenRouter model list. Please verify the ID and ensure it's available on OpenRouter.` - ); - } - } else if (providerHint === 'ollama') { - // Check Ollama ONLY because hint was ollama - report('info', `Checking Ollama for ${modelId} (as hinted)...`); + if ( + openRouterModels && + openRouterModels.some((m) => m.id === modelId) + ) { + determinedProvider = "openrouter"; - // Get the Ollama base URL from config - const ollamaBaseURL = getBaseUrlForRole(role, projectRoot); - const ollamaModels = await fetchOllamaModels(ollamaBaseURL); + // Check if this is a free model (ends with :free) + if (modelId.endsWith(":free")) { + warningMessage = `Warning: OpenRouter free model '${modelId}' selected. Free models have significant limitations including lower context windows, reduced rate limits, and may not support advanced features like tool_use. Consider using the paid version '${modelId.replace(":free", "")}' for full functionality.`; + } else { + warningMessage = `Warning: Custom OpenRouter model '${modelId}' set. This model is not officially validated by Taskmaster and may not function as expected.`; + } - if (ollamaModels === null) { - // Connection failed - server probably not running - throw new Error( - `Unable to connect to Ollama server at ${ollamaBaseURL}. Please ensure Ollama is running and try again.` - ); - } else if (ollamaModels.some((m) => m.model === modelId)) { - determinedProvider = 'ollama'; - warningMessage = `Warning: Custom Ollama model '${modelId}' set. Ensure your Ollama server is running and has pulled this model. Taskmaster cannot guarantee compatibility.`; - report('warn', warningMessage); - } else { - // Server is running but model not found - const tagsUrl = `${ollamaBaseURL}/tags`; - throw new Error( - `Model ID "${modelId}" not found in the Ollama instance. Please verify the model is pulled and available. You can check available models with: curl ${tagsUrl}` - ); - } - } else if (providerHint === 'bedrock') { - // Set provider without model validation since Bedrock models are managed by AWS - determinedProvider = 'bedrock'; - warningMessage = `Warning: Custom Bedrock model '${modelId}' set. Please ensure the model ID is valid and accessible in your AWS account.`; - report('warn', warningMessage); - } else { - // Invalid provider hint - should not happen - throw new Error(`Invalid provider hint received: ${providerHint}`); - } - } - } else { - // No hint provided (flags not used) - if (modelData) { - // Found internally, use the provider from the internal list - determinedProvider = modelData.provider; - report( - 'info', - `Model ${modelId} found internally with provider ${determinedProvider}.` - ); - } else { - // Model not found and no provider hint was given - return { - success: false, - error: { - code: 'MODEL_NOT_FOUND_NO_HINT', - message: `Model ID "${modelId}" not found in Taskmaster's supported models. If this is a custom model, please specify the provider using --openrouter or --ollama.` - } - }; - } - } + report("warn", warningMessage); + } else { + // Hinted as OpenRouter but not found in live check + throw new Error( + `Model ID "${modelId}" not found in the live OpenRouter model list. Please verify the ID and ensure it's available on OpenRouter.` + ); + } + } else if (providerHint === "ollama") { + // Check Ollama ONLY because hint was ollama + report("info", `Checking Ollama for ${modelId} (as hinted)...`); - // --- End of Revised Logic --- // + // Get the Ollama base URL from config + const ollamaBaseURL = getBaseUrlForRole(role, projectRoot); + const ollamaModels = await fetchOllamaModels(ollamaBaseURL); - // At this point, we should have a determinedProvider if the model is valid (internally or custom) - if (!determinedProvider) { - // This case acts as a safeguard - return { - success: false, - error: { - code: 'PROVIDER_UNDETERMINED', - message: `Could not determine the provider for model ID "${modelId}".` - } - }; - } + if (ollamaModels === null) { + // Connection failed - server probably not running + throw new Error( + `Unable to connect to Ollama server at ${ollamaBaseURL}. Please ensure Ollama is running and try again.` + ); + } else if (ollamaModels.some((m) => m.model === modelId)) { + determinedProvider = "ollama"; + warningMessage = `Warning: Custom Ollama model '${modelId}' set. Ensure your Ollama server is running and has pulled this model. Taskmaster cannot guarantee compatibility.`; + report("warn", warningMessage); + } else { + // Server is running but model not found + const tagsUrl = `${ollamaBaseURL}/tags`; + throw new Error( + `Model ID "${modelId}" not found in the Ollama instance. Please verify the model is pulled and available. You can check available models with: curl ${tagsUrl}` + ); + } + } else if (providerHint === "bedrock") { + // Set provider without model validation since Bedrock models are managed by AWS + determinedProvider = "bedrock"; + warningMessage = `Warning: Custom Bedrock model '${modelId}' set. Please ensure the model ID is valid and accessible in your AWS account.`; + report("warn", warningMessage); + } else { + // Invalid provider hint - should not happen + throw new Error(`Invalid provider hint received: ${providerHint}`); + } + } + } else { + // No hint provided (flags not used) + if (modelData) { + // Found internally, use the provider from the internal list + determinedProvider = modelData.provider; + report( + "info", + `Model ${modelId} found internally with provider ${determinedProvider}.` + ); + } else { + // Model not found and no provider hint was given + return { + success: false, + error: { + code: "MODEL_NOT_FOUND_NO_HINT", + message: `Model ID "${modelId}" not found in Taskmaster's supported models. If this is a custom model, please specify the provider using --openrouter or --ollama.`, + }, + }; + } + } - // Update configuration - currentConfig.models[role] = { - ...currentConfig.models[role], // Keep existing params like maxTokens - provider: determinedProvider, - modelId: modelId - }; + // --- End of Revised Logic --- // - // Write updated configuration - const writeResult = writeConfig(currentConfig, projectRoot); - if (!writeResult) { - return { - success: false, - error: { - code: 'CONFIG_WRITE_ERROR', - message: 'Error writing updated configuration to configuration file' - } - }; - } + // At this point, we should have a determinedProvider if the model is valid (internally or custom) + if (!determinedProvider) { + // This case acts as a safeguard + return { + success: false, + error: { + code: "PROVIDER_UNDETERMINED", + message: `Could not determine the provider for model ID "${modelId}".`, + }, + }; + } - const successMessage = `Successfully set ${role} model to ${modelId} (Provider: ${determinedProvider})`; - report('info', successMessage); + // Update configuration + currentConfig.models[role] = { + ...currentConfig.models[role], // Keep existing params like maxTokens + provider: determinedProvider, + modelId: modelId, + }; - return { - success: true, - data: { - role, - provider: determinedProvider, - modelId, - message: successMessage, - warning: warningMessage // Include warning in the response data - } - }; - } catch (error) { - report('error', `Error setting ${role} model: ${error.message}`); - return { - success: false, - error: { - code: 'SET_MODEL_ERROR', - message: error.message - } - }; - } + // Write updated configuration + const writeResult = writeConfig(currentConfig, projectRoot); + if (!writeResult) { + return { + success: false, + error: { + code: "CONFIG_WRITE_ERROR", + message: "Error writing updated configuration to configuration file", + }, + }; + } + + const successMessage = `Successfully set ${role} model to ${modelId} (Provider: ${determinedProvider})`; + report("info", successMessage); + + return { + success: true, + data: { + role, + provider: determinedProvider, + modelId, + message: successMessage, + warning: warningMessage, // Include warning in the response data + }, + }; + } catch (error) { + report("error", `Error setting ${role} model: ${error.message}`); + return { + success: false, + error: { + code: "SET_MODEL_ERROR", + message: error.message, + }, + }; + } } /** @@ -580,52 +587,52 @@ async function setModel(role, modelId, options = {}) { * @returns {Object} RESTful response with API key status report */ async function getApiKeyStatusReport(options = {}) { - const { mcpLog, projectRoot, session } = options; - const report = (level, ...args) => { - if (mcpLog && typeof mcpLog[level] === 'function') { - mcpLog[level](...args); - } - }; + const { mcpLog, projectRoot, session } = options; + const report = (level, ...args) => { + if (mcpLog && typeof mcpLog[level] === "function") { + mcpLog[level](...args); + } + }; - try { - const providers = getAllProviders(); - const providersToCheck = providers.filter( - (p) => p.toLowerCase() !== 'ollama' - ); // Ollama is not a provider, it's a service, doesn't need an api key usually - const statusReport = providersToCheck.map((provider) => { - // Use provided projectRoot for MCP status check - const cliOk = isApiKeySet(provider, session, projectRoot); // Pass session and projectRoot for CLI check - const mcpOk = getMcpApiKeyStatus(provider, projectRoot); - return { - provider, - cli: cliOk, - mcp: mcpOk - }; - }); + try { + const providers = getAllProviders(); + const providersToCheck = providers.filter( + (p) => p.toLowerCase() !== "ollama" + ); // Ollama is not a provider, it's a service, doesn't need an api key usually + const statusReport = providersToCheck.map((provider) => { + // Use provided projectRoot for MCP status check + const cliOk = isApiKeySet(provider, session, projectRoot); // Pass session and projectRoot for CLI check + const mcpOk = getMcpApiKeyStatus(provider, projectRoot); + return { + provider, + cli: cliOk, + mcp: mcpOk, + }; + }); - report('info', 'Successfully generated API key status report.'); - return { - success: true, - data: { - report: statusReport, - message: 'API key status report generated.' - } - }; - } catch (error) { - report('error', `Error generating API key status report: ${error.message}`); - return { - success: false, - error: { - code: 'API_KEY_STATUS_ERROR', - message: error.message - } - }; - } + report("info", "Successfully generated API key status report."); + return { + success: true, + data: { + report: statusReport, + message: "API key status report generated.", + }, + }; + } catch (error) { + report("error", `Error generating API key status report: ${error.message}`); + return { + success: false, + error: { + code: "API_KEY_STATUS_ERROR", + message: error.message, + }, + }; + } } export { - getModelConfiguration, - getAvailableModelsList, - setModel, - getApiKeyStatusReport + getModelConfiguration, + getAvailableModelsList, + setModel, + getApiKeyStatusReport, }; From b1390e4ddf92324104c7fa3c9c0f26b9c227e792 Mon Sep 17 00:00:00 2001 From: Eyal Toledano <eyal@microangel.so> Date: Sat, 7 Jun 2025 20:23:55 -0400 Subject: [PATCH 05/10] refactor: enhance add-task fuzzy search and fix duplicate banner display MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - **Remove hardcoded category system** in add-task that always matched 'Task management' - **Eliminate arbitrary limits** in fuzzy search results (5โ†’25 high relevance, 3โ†’10 medium relevance, 8โ†’20 detailed tasks) - **Improve semantic weighting** in Fuse.js search (details=3, description=2, title=1.5) for better relevance - **Fix duplicate banner issue** by removing console.clear() and redundant displayBanner() calls from UI functions - **Enhance context generation** to rely on semantic similarity rather than rigid pattern matching - **Preserve terminal history** to address GitHub issue #553 about eating terminal lines - **Remove displayBanner() calls** from: displayHelp, displayNextTask, displayTaskById, displayComplexityReport, set-task-status, clear-subtasks, dependency-manager functions The add-task system now provides truly relevant task context based on semantic similarity rather than arbitrary categories and limits, while maintaining a cleaner terminal experience. Changes span: add-task.js, ui.js, set-task-status.js, clear-subtasks.js, list-tasks.js, dependency-manager.js Closes #553 --- .taskmaster/config.json | 4 +- .taskmaster/tasks/task_097.txt | 35 - .taskmaster/tasks/tasks.json | 16 - scripts/modules/dependency-manager.js | 1986 ++++++++------- scripts/modules/task-manager/add-task.js | 2227 ++++++++--------- .../modules/task-manager/clear-subtasks.js | 254 +- scripts/modules/task-manager/list-tasks.js | 1337 +++++----- .../modules/task-manager/set-task-status.js | 188 +- scripts/modules/ui.js | 94 +- 9 files changed, 3047 insertions(+), 3094 deletions(-) delete mode 100644 .taskmaster/tasks/task_097.txt diff --git a/.taskmaster/config.json b/.taskmaster/config.json index 20cdb49c..442dfc1c 100644 --- a/.taskmaster/config.json +++ b/.taskmaster/config.json @@ -1,8 +1,8 @@ { "models": { "main": { - "provider": "openrouter", - "modelId": "qwen/qwen3-235b-a22b:free", + "provider": "anthropic", + "modelId": "claude-sonnet-4-20250514", "maxTokens": 50000, "temperature": 0.2 }, diff --git a/.taskmaster/tasks/task_097.txt b/.taskmaster/tasks/task_097.txt deleted file mode 100644 index 2ef43dd6..00000000 --- a/.taskmaster/tasks/task_097.txt +++ /dev/null @@ -1,35 +0,0 @@ -# Task ID: 97 -# Title: Create Taskmaster Jingle Implementation -# Status: pending -# Dependencies: 95, 57, 3, 2 -# Priority: medium -# Description: Develop a musical jingle system for Taskmaster that plays sound effects during key CLI interactions to enhance user experience. -# Details: -This task involves implementing a sound system that plays audio cues during Taskmaster CLI operations. Key implementation steps include: - -1. Audio System Integration: - - Research and select appropriate audio library compatible with Node.js CLI applications - - Implement cross-platform audio playback (Windows, macOS, Linux) - - Create sound configuration options in .taskmasterconfig - -2. Jingle Design: - - Define sound triggers for key events (task creation, completion, errors, etc.) - - Create or source appropriate sound files (WAV/MP3 format) - - Implement volume control and mute option in settings - -3. CLI Integration: - - Add sound playback to core CLI commands (init, create, update, delete) - - Implement optional sound effects toggle via command line flags - - Ensure audio playback doesn't interfere with CLI performance - -4. Documentation: - - Update user guide with sound configuration instructions - - Add troubleshooting section for audio playback issues - -# Test Strategy: -1. Verify audio plays correctly during each supported CLI operation -2. Test sound configuration options across different platforms -3. Confirm volume control and mute functionality works as expected -4. Validate that audio playback doesn't affect CLI performance -5. Test edge cases (no audio hardware, invalid sound files, etc.) -6. Ensure sound effects can be disabled via configuration and CLI flags diff --git a/.taskmaster/tasks/tasks.json b/.taskmaster/tasks/tasks.json index 8cc174d5..c8cd9c33 100644 --- a/.taskmaster/tasks/tasks.json +++ b/.taskmaster/tasks/tasks.json @@ -5871,22 +5871,6 @@ "parentTaskId": 96 } ] - }, - { - "id": 97, - "title": "Create Taskmaster Jingle Implementation", - "description": "Develop a musical jingle system for Taskmaster that plays sound effects during key CLI interactions to enhance user experience.", - "details": "This task involves implementing a sound system that plays audio cues during Taskmaster CLI operations. Key implementation steps include:\n\n1. Audio System Integration:\n - Research and select appropriate audio library compatible with Node.js CLI applications\n - Implement cross-platform audio playback (Windows, macOS, Linux)\n - Create sound configuration options in .taskmasterconfig\n\n2. Jingle Design:\n - Define sound triggers for key events (task creation, completion, errors, etc.)\n - Create or source appropriate sound files (WAV/MP3 format)\n - Implement volume control and mute option in settings\n\n3. CLI Integration:\n - Add sound playback to core CLI commands (init, create, update, delete)\n - Implement optional sound effects toggle via command line flags\n - Ensure audio playback doesn't interfere with CLI performance\n\n4. Documentation:\n - Update user guide with sound configuration instructions\n - Add troubleshooting section for audio playback issues", - "testStrategy": "1. Verify audio plays correctly during each supported CLI operation\n2. Test sound configuration options across different platforms\n3. Confirm volume control and mute functionality works as expected\n4. Validate that audio playback doesn't affect CLI performance\n5. Test edge cases (no audio hardware, invalid sound files, etc.)\n6. Ensure sound effects can be disabled via configuration and CLI flags", - "status": "pending", - "dependencies": [ - 95, - 57, - 3, - 2 - ], - "priority": "medium", - "subtasks": [] } ] } \ No newline at end of file diff --git a/scripts/modules/dependency-manager.js b/scripts/modules/dependency-manager.js index 73745276..a6417780 100644 --- a/scripts/modules/dependency-manager.js +++ b/scripts/modules/dependency-manager.js @@ -3,23 +3,23 @@ * Manages task dependencies and relationships */ -import path from 'path'; -import chalk from 'chalk'; -import boxen from 'boxen'; +import path from "path"; +import chalk from "chalk"; +import boxen from "boxen"; import { - log, - readJSON, - writeJSON, - taskExists, - formatTaskId, - findCycles, - isSilentMode -} from './utils.js'; + log, + readJSON, + writeJSON, + taskExists, + formatTaskId, + findCycles, + isSilentMode, +} from "./utils.js"; -import { displayBanner } from './ui.js'; +import { displayBanner } from "./ui.js"; -import { generateTaskFiles } from './task-manager.js'; +import { generateTaskFiles } from "./task-manager.js"; /** * Add a dependency to a task @@ -28,183 +28,183 @@ import { generateTaskFiles } from './task-manager.js'; * @param {number|string} dependencyId - ID of the task to add as dependency */ async function addDependency(tasksPath, taskId, dependencyId) { - log('info', `Adding dependency ${dependencyId} to task ${taskId}...`); + log("info", `Adding dependency ${dependencyId} to task ${taskId}...`); - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', 'No valid tasks found in tasks.json'); - process.exit(1); - } + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log("error", "No valid tasks found in tasks.json"); + process.exit(1); + } - // Format the task and dependency IDs correctly - const formattedTaskId = - typeof taskId === 'string' && taskId.includes('.') - ? taskId - : parseInt(taskId, 10); + // Format the task and dependency IDs correctly + const formattedTaskId = + typeof taskId === "string" && taskId.includes(".") + ? taskId + : parseInt(taskId, 10); - const formattedDependencyId = formatTaskId(dependencyId); + const formattedDependencyId = formatTaskId(dependencyId); - // Check if the dependency task or subtask actually exists - if (!taskExists(data.tasks, formattedDependencyId)) { - log( - 'error', - `Dependency target ${formattedDependencyId} does not exist in tasks.json` - ); - process.exit(1); - } + // Check if the dependency task or subtask actually exists + if (!taskExists(data.tasks, formattedDependencyId)) { + log( + "error", + `Dependency target ${formattedDependencyId} does not exist in tasks.json` + ); + process.exit(1); + } - // Find the task to update - let targetTask = null; - let isSubtask = false; + // Find the task to update + let targetTask = null; + let isSubtask = false; - if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) { - // Handle dot notation for subtasks (e.g., "1.2") - const [parentId, subtaskId] = formattedTaskId - .split('.') - .map((id) => parseInt(id, 10)); - const parentTask = data.tasks.find((t) => t.id === parentId); + if (typeof formattedTaskId === "string" && formattedTaskId.includes(".")) { + // Handle dot notation for subtasks (e.g., "1.2") + const [parentId, subtaskId] = formattedTaskId + .split(".") + .map((id) => parseInt(id, 10)); + const parentTask = data.tasks.find((t) => t.id === parentId); - if (!parentTask) { - log('error', `Parent task ${parentId} not found.`); - process.exit(1); - } + if (!parentTask) { + log("error", `Parent task ${parentId} not found.`); + process.exit(1); + } - if (!parentTask.subtasks) { - log('error', `Parent task ${parentId} has no subtasks.`); - process.exit(1); - } + if (!parentTask.subtasks) { + log("error", `Parent task ${parentId} has no subtasks.`); + process.exit(1); + } - targetTask = parentTask.subtasks.find((s) => s.id === subtaskId); - isSubtask = true; + targetTask = parentTask.subtasks.find((s) => s.id === subtaskId); + isSubtask = true; - if (!targetTask) { - log('error', `Subtask ${formattedTaskId} not found.`); - process.exit(1); - } - } else { - // Regular task (not a subtask) - targetTask = data.tasks.find((t) => t.id === formattedTaskId); + if (!targetTask) { + log("error", `Subtask ${formattedTaskId} not found.`); + process.exit(1); + } + } else { + // Regular task (not a subtask) + targetTask = data.tasks.find((t) => t.id === formattedTaskId); - if (!targetTask) { - log('error', `Task ${formattedTaskId} not found.`); - process.exit(1); - } - } + if (!targetTask) { + log("error", `Task ${formattedTaskId} not found.`); + process.exit(1); + } + } - // Initialize dependencies array if it doesn't exist - if (!targetTask.dependencies) { - targetTask.dependencies = []; - } + // Initialize dependencies array if it doesn't exist + if (!targetTask.dependencies) { + targetTask.dependencies = []; + } - // Check if dependency already exists - if ( - targetTask.dependencies.some((d) => { - // Convert both to strings for comparison to handle both numeric and string IDs - return String(d) === String(formattedDependencyId); - }) - ) { - log( - 'warn', - `Dependency ${formattedDependencyId} already exists in task ${formattedTaskId}.` - ); - return; - } + // Check if dependency already exists + if ( + targetTask.dependencies.some((d) => { + // Convert both to strings for comparison to handle both numeric and string IDs + return String(d) === String(formattedDependencyId); + }) + ) { + log( + "warn", + `Dependency ${formattedDependencyId} already exists in task ${formattedTaskId}.` + ); + return; + } - // Check if the task is trying to depend on itself - compare full IDs (including subtask parts) - if (String(formattedTaskId) === String(formattedDependencyId)) { - log('error', `Task ${formattedTaskId} cannot depend on itself.`); - process.exit(1); - } + // Check if the task is trying to depend on itself - compare full IDs (including subtask parts) + if (String(formattedTaskId) === String(formattedDependencyId)) { + log("error", `Task ${formattedTaskId} cannot depend on itself.`); + process.exit(1); + } - // For subtasks of the same parent, we need to make sure we're not treating it as a self-dependency - // Check if we're dealing with subtasks with the same parent task - let isSelfDependency = false; + // For subtasks of the same parent, we need to make sure we're not treating it as a self-dependency + // Check if we're dealing with subtasks with the same parent task + let isSelfDependency = false; - if ( - typeof formattedTaskId === 'string' && - typeof formattedDependencyId === 'string' && - formattedTaskId.includes('.') && - formattedDependencyId.includes('.') - ) { - const [taskParentId] = formattedTaskId.split('.'); - const [depParentId] = formattedDependencyId.split('.'); + if ( + typeof formattedTaskId === "string" && + typeof formattedDependencyId === "string" && + formattedTaskId.includes(".") && + formattedDependencyId.includes(".") + ) { + const [taskParentId] = formattedTaskId.split("."); + const [depParentId] = formattedDependencyId.split("."); - // Only treat it as a self-dependency if both the parent ID and subtask ID are identical - isSelfDependency = formattedTaskId === formattedDependencyId; + // Only treat it as a self-dependency if both the parent ID and subtask ID are identical + isSelfDependency = formattedTaskId === formattedDependencyId; - // Log for debugging - log( - 'debug', - `Adding dependency between subtasks: ${formattedTaskId} depends on ${formattedDependencyId}` - ); - log( - 'debug', - `Parent IDs: ${taskParentId} and ${depParentId}, Self-dependency check: ${isSelfDependency}` - ); - } + // Log for debugging + log( + "debug", + `Adding dependency between subtasks: ${formattedTaskId} depends on ${formattedDependencyId}` + ); + log( + "debug", + `Parent IDs: ${taskParentId} and ${depParentId}, Self-dependency check: ${isSelfDependency}` + ); + } - if (isSelfDependency) { - log('error', `Subtask ${formattedTaskId} cannot depend on itself.`); - process.exit(1); - } + if (isSelfDependency) { + log("error", `Subtask ${formattedTaskId} cannot depend on itself.`); + process.exit(1); + } - // Check for circular dependencies - let dependencyChain = [formattedTaskId]; - if ( - !isCircularDependency(data.tasks, formattedDependencyId, dependencyChain) - ) { - // Add the dependency - targetTask.dependencies.push(formattedDependencyId); + // Check for circular dependencies + let dependencyChain = [formattedTaskId]; + if ( + !isCircularDependency(data.tasks, formattedDependencyId, dependencyChain) + ) { + // Add the dependency + targetTask.dependencies.push(formattedDependencyId); - // Sort dependencies numerically or by parent task ID first, then subtask ID - targetTask.dependencies.sort((a, b) => { - if (typeof a === 'number' && typeof b === 'number') { - return a - b; - } else if (typeof a === 'string' && typeof b === 'string') { - const [aParent, aChild] = a.split('.').map(Number); - const [bParent, bChild] = b.split('.').map(Number); - return aParent !== bParent ? aParent - bParent : aChild - bChild; - } else if (typeof a === 'number') { - return -1; // Numbers come before strings - } else { - return 1; // Strings come after numbers - } - }); + // Sort dependencies numerically or by parent task ID first, then subtask ID + targetTask.dependencies.sort((a, b) => { + if (typeof a === "number" && typeof b === "number") { + return a - b; + } else if (typeof a === "string" && typeof b === "string") { + const [aParent, aChild] = a.split(".").map(Number); + const [bParent, bChild] = b.split(".").map(Number); + return aParent !== bParent ? aParent - bParent : aChild - bChild; + } else if (typeof a === "number") { + return -1; // Numbers come before strings + } else { + return 1; // Strings come after numbers + } + }); - // Save changes - writeJSON(tasksPath, data); - log( - 'success', - `Added dependency ${formattedDependencyId} to task ${formattedTaskId}` - ); + // Save changes + writeJSON(tasksPath, data); + log( + "success", + `Added dependency ${formattedDependencyId} to task ${formattedTaskId}` + ); - // Display a more visually appealing success message - if (!isSilentMode()) { - console.log( - boxen( - chalk.green(`Successfully added dependency:\n\n`) + - `Task ${chalk.bold(formattedTaskId)} now depends on ${chalk.bold(formattedDependencyId)}`, - { - padding: 1, - borderColor: 'green', - borderStyle: 'round', - margin: { top: 1 } - } - ) - ); - } + // Display a more visually appealing success message + if (!isSilentMode()) { + console.log( + boxen( + chalk.green(`Successfully added dependency:\n\n`) + + `Task ${chalk.bold(formattedTaskId)} now depends on ${chalk.bold(formattedDependencyId)}`, + { + padding: 1, + borderColor: "green", + borderStyle: "round", + margin: { top: 1 }, + } + ) + ); + } - // Generate updated task files - await generateTaskFiles(tasksPath, path.dirname(tasksPath)); + // Generate updated task files + await generateTaskFiles(tasksPath, path.dirname(tasksPath)); - log('info', 'Task files regenerated with updated dependencies.'); - } else { - log( - 'error', - `Cannot add dependency ${formattedDependencyId} to task ${formattedTaskId} as it would create a circular dependency.` - ); - process.exit(1); - } + log("info", "Task files regenerated with updated dependencies."); + } else { + log( + "error", + `Cannot add dependency ${formattedDependencyId} to task ${formattedTaskId} as it would create a circular dependency.` + ); + process.exit(1); + } } /** @@ -214,127 +214,127 @@ async function addDependency(tasksPath, taskId, dependencyId) { * @param {number|string} dependencyId - ID of the task to remove as dependency */ async function removeDependency(tasksPath, taskId, dependencyId) { - log('info', `Removing dependency ${dependencyId} from task ${taskId}...`); + log("info", `Removing dependency ${dependencyId} from task ${taskId}...`); - // Read tasks file - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', 'No valid tasks found.'); - process.exit(1); - } + // Read tasks file + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log("error", "No valid tasks found."); + process.exit(1); + } - // Format the task and dependency IDs correctly - const formattedTaskId = - typeof taskId === 'string' && taskId.includes('.') - ? taskId - : parseInt(taskId, 10); + // Format the task and dependency IDs correctly + const formattedTaskId = + typeof taskId === "string" && taskId.includes(".") + ? taskId + : parseInt(taskId, 10); - const formattedDependencyId = formatTaskId(dependencyId); + const formattedDependencyId = formatTaskId(dependencyId); - // Find the task to update - let targetTask = null; - let isSubtask = false; + // Find the task to update + let targetTask = null; + let isSubtask = false; - if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) { - // Handle dot notation for subtasks (e.g., "1.2") - const [parentId, subtaskId] = formattedTaskId - .split('.') - .map((id) => parseInt(id, 10)); - const parentTask = data.tasks.find((t) => t.id === parentId); + if (typeof formattedTaskId === "string" && formattedTaskId.includes(".")) { + // Handle dot notation for subtasks (e.g., "1.2") + const [parentId, subtaskId] = formattedTaskId + .split(".") + .map((id) => parseInt(id, 10)); + const parentTask = data.tasks.find((t) => t.id === parentId); - if (!parentTask) { - log('error', `Parent task ${parentId} not found.`); - process.exit(1); - } + if (!parentTask) { + log("error", `Parent task ${parentId} not found.`); + process.exit(1); + } - if (!parentTask.subtasks) { - log('error', `Parent task ${parentId} has no subtasks.`); - process.exit(1); - } + if (!parentTask.subtasks) { + log("error", `Parent task ${parentId} has no subtasks.`); + process.exit(1); + } - targetTask = parentTask.subtasks.find((s) => s.id === subtaskId); - isSubtask = true; + targetTask = parentTask.subtasks.find((s) => s.id === subtaskId); + isSubtask = true; - if (!targetTask) { - log('error', `Subtask ${formattedTaskId} not found.`); - process.exit(1); - } - } else { - // Regular task (not a subtask) - targetTask = data.tasks.find((t) => t.id === formattedTaskId); + if (!targetTask) { + log("error", `Subtask ${formattedTaskId} not found.`); + process.exit(1); + } + } else { + // Regular task (not a subtask) + targetTask = data.tasks.find((t) => t.id === formattedTaskId); - if (!targetTask) { - log('error', `Task ${formattedTaskId} not found.`); - process.exit(1); - } - } + if (!targetTask) { + log("error", `Task ${formattedTaskId} not found.`); + process.exit(1); + } + } - // Check if the task has any dependencies - if (!targetTask.dependencies || targetTask.dependencies.length === 0) { - log( - 'info', - `Task ${formattedTaskId} has no dependencies, nothing to remove.` - ); - return; - } + // Check if the task has any dependencies + if (!targetTask.dependencies || targetTask.dependencies.length === 0) { + log( + "info", + `Task ${formattedTaskId} has no dependencies, nothing to remove.` + ); + return; + } - // Normalize the dependency ID for comparison to handle different formats - const normalizedDependencyId = String(formattedDependencyId); + // Normalize the dependency ID for comparison to handle different formats + const normalizedDependencyId = String(formattedDependencyId); - // Check if the dependency exists by comparing string representations - const dependencyIndex = targetTask.dependencies.findIndex((dep) => { - // Convert both to strings for comparison - let depStr = String(dep); + // Check if the dependency exists by comparing string representations + const dependencyIndex = targetTask.dependencies.findIndex((dep) => { + // Convert both to strings for comparison + let depStr = String(dep); - // Special handling for numeric IDs that might be subtask references - if (typeof dep === 'number' && dep < 100 && isSubtask) { - // It's likely a reference to another subtask in the same parent task - // Convert to full format for comparison (e.g., 2 -> "1.2" for a subtask in task 1) - const [parentId] = formattedTaskId.split('.'); - depStr = `${parentId}.${dep}`; - } + // Special handling for numeric IDs that might be subtask references + if (typeof dep === "number" && dep < 100 && isSubtask) { + // It's likely a reference to another subtask in the same parent task + // Convert to full format for comparison (e.g., 2 -> "1.2" for a subtask in task 1) + const [parentId] = formattedTaskId.split("."); + depStr = `${parentId}.${dep}`; + } - return depStr === normalizedDependencyId; - }); + return depStr === normalizedDependencyId; + }); - if (dependencyIndex === -1) { - log( - 'info', - `Task ${formattedTaskId} does not depend on ${formattedDependencyId}, no changes made.` - ); - return; - } + if (dependencyIndex === -1) { + log( + "info", + `Task ${formattedTaskId} does not depend on ${formattedDependencyId}, no changes made.` + ); + return; + } - // Remove the dependency - targetTask.dependencies.splice(dependencyIndex, 1); + // Remove the dependency + targetTask.dependencies.splice(dependencyIndex, 1); - // Save the updated tasks - writeJSON(tasksPath, data); + // Save the updated tasks + writeJSON(tasksPath, data); - // Success message - log( - 'success', - `Removed dependency: Task ${formattedTaskId} no longer depends on ${formattedDependencyId}` - ); + // Success message + log( + "success", + `Removed dependency: Task ${formattedTaskId} no longer depends on ${formattedDependencyId}` + ); - if (!isSilentMode()) { - // Display a more visually appealing success message - console.log( - boxen( - chalk.green(`Successfully removed dependency:\n\n`) + - `Task ${chalk.bold(formattedTaskId)} no longer depends on ${chalk.bold(formattedDependencyId)}`, - { - padding: 1, - borderColor: 'green', - borderStyle: 'round', - margin: { top: 1 } - } - ) - ); - } + if (!isSilentMode()) { + // Display a more visually appealing success message + console.log( + boxen( + chalk.green(`Successfully removed dependency:\n\n`) + + `Task ${chalk.bold(formattedTaskId)} no longer depends on ${chalk.bold(formattedDependencyId)}`, + { + padding: 1, + borderColor: "green", + borderStyle: "round", + margin: { top: 1 }, + } + ) + ); + } - // Regenerate task files - await generateTaskFiles(tasksPath, path.dirname(tasksPath)); + // Regenerate task files + await generateTaskFiles(tasksPath, path.dirname(tasksPath)); } /** @@ -345,54 +345,54 @@ async function removeDependency(tasksPath, taskId, dependencyId) { * @returns {boolean} True if circular dependency would be created */ function isCircularDependency(tasks, taskId, chain = []) { - // Convert taskId to string for comparison - const taskIdStr = String(taskId); + // Convert taskId to string for comparison + const taskIdStr = String(taskId); - // If we've seen this task before in the chain, we have a circular dependency - if (chain.some((id) => String(id) === taskIdStr)) { - return true; - } + // If we've seen this task before in the chain, we have a circular dependency + if (chain.some((id) => String(id) === taskIdStr)) { + return true; + } - // Find the task or subtask - let task = null; - let parentIdForSubtask = null; + // Find the task or subtask + let task = null; + let parentIdForSubtask = null; - // Check if this is a subtask reference (e.g., "1.2") - if (taskIdStr.includes('.')) { - const [parentId, subtaskId] = taskIdStr.split('.').map(Number); - const parentTask = tasks.find((t) => t.id === parentId); - parentIdForSubtask = parentId; // Store parent ID if it's a subtask + // Check if this is a subtask reference (e.g., "1.2") + if (taskIdStr.includes(".")) { + const [parentId, subtaskId] = taskIdStr.split(".").map(Number); + const parentTask = tasks.find((t) => t.id === parentId); + parentIdForSubtask = parentId; // Store parent ID if it's a subtask - if (parentTask && parentTask.subtasks) { - task = parentTask.subtasks.find((st) => st.id === subtaskId); - } - } else { - // Regular task - task = tasks.find((t) => String(t.id) === taskIdStr); - } + if (parentTask && parentTask.subtasks) { + task = parentTask.subtasks.find((st) => st.id === subtaskId); + } + } else { + // Regular task + task = tasks.find((t) => String(t.id) === taskIdStr); + } - if (!task) { - return false; // Task doesn't exist, can't create circular dependency - } + if (!task) { + return false; // Task doesn't exist, can't create circular dependency + } - // No dependencies, can't create circular dependency - if (!task.dependencies || task.dependencies.length === 0) { - return false; - } + // No dependencies, can't create circular dependency + if (!task.dependencies || task.dependencies.length === 0) { + return false; + } - // Check each dependency recursively - const newChain = [...chain, taskIdStr]; // Use taskIdStr for consistency - return task.dependencies.some((depId) => { - let normalizedDepId = String(depId); - // Normalize relative subtask dependencies - if (typeof depId === 'number' && parentIdForSubtask !== null) { - // If the current task is a subtask AND the dependency is a number, - // assume it refers to a sibling subtask. - normalizedDepId = `${parentIdForSubtask}.${depId}`; - } - // Pass the normalized ID to the recursive call - return isCircularDependency(tasks, normalizedDepId, newChain); - }); + // Check each dependency recursively + const newChain = [...chain, taskIdStr]; // Use taskIdStr for consistency + return task.dependencies.some((depId) => { + let normalizedDepId = String(depId); + // Normalize relative subtask dependencies + if (typeof depId === "number" && parentIdForSubtask !== null) { + // If the current task is a subtask AND the dependency is a number, + // assume it refers to a sibling subtask. + normalizedDepId = `${parentIdForSubtask}.${depId}`; + } + // Pass the normalized ID to the recursive call + return isCircularDependency(tasks, normalizedDepId, newChain); + }); } /** @@ -401,96 +401,96 @@ function isCircularDependency(tasks, taskId, chain = []) { * @returns {Object} Validation result with valid flag and issues array */ function validateTaskDependencies(tasks) { - const issues = []; + const issues = []; - // Check each task's dependencies - tasks.forEach((task) => { - if (!task.dependencies) { - return; // No dependencies to validate - } + // Check each task's dependencies + tasks.forEach((task) => { + if (!task.dependencies) { + return; // No dependencies to validate + } - task.dependencies.forEach((depId) => { - // Check for self-dependencies - if (String(depId) === String(task.id)) { - issues.push({ - type: 'self', - taskId: task.id, - message: `Task ${task.id} depends on itself` - }); - return; - } + task.dependencies.forEach((depId) => { + // Check for self-dependencies + if (String(depId) === String(task.id)) { + issues.push({ + type: "self", + taskId: task.id, + message: `Task ${task.id} depends on itself`, + }); + return; + } - // Check if dependency exists - if (!taskExists(tasks, depId)) { - issues.push({ - type: 'missing', - taskId: task.id, - dependencyId: depId, - message: `Task ${task.id} depends on non-existent task ${depId}` - }); - } - }); + // Check if dependency exists + if (!taskExists(tasks, depId)) { + issues.push({ + type: "missing", + taskId: task.id, + dependencyId: depId, + message: `Task ${task.id} depends on non-existent task ${depId}`, + }); + } + }); - // Check for circular dependencies - if (isCircularDependency(tasks, task.id)) { - issues.push({ - type: 'circular', - taskId: task.id, - message: `Task ${task.id} is part of a circular dependency chain` - }); - } + // Check for circular dependencies + if (isCircularDependency(tasks, task.id)) { + issues.push({ + type: "circular", + taskId: task.id, + message: `Task ${task.id} is part of a circular dependency chain`, + }); + } - // Check subtask dependencies if they exist - if (task.subtasks && task.subtasks.length > 0) { - task.subtasks.forEach((subtask) => { - if (!subtask.dependencies) { - return; // No dependencies to validate - } + // Check subtask dependencies if they exist + if (task.subtasks && task.subtasks.length > 0) { + task.subtasks.forEach((subtask) => { + if (!subtask.dependencies) { + return; // No dependencies to validate + } - // Create a full subtask ID for reference - const fullSubtaskId = `${task.id}.${subtask.id}`; + // Create a full subtask ID for reference + const fullSubtaskId = `${task.id}.${subtask.id}`; - subtask.dependencies.forEach((depId) => { - // Check for self-dependencies in subtasks - if ( - String(depId) === String(fullSubtaskId) || - (typeof depId === 'number' && depId === subtask.id) - ) { - issues.push({ - type: 'self', - taskId: fullSubtaskId, - message: `Subtask ${fullSubtaskId} depends on itself` - }); - return; - } + subtask.dependencies.forEach((depId) => { + // Check for self-dependencies in subtasks + if ( + String(depId) === String(fullSubtaskId) || + (typeof depId === "number" && depId === subtask.id) + ) { + issues.push({ + type: "self", + taskId: fullSubtaskId, + message: `Subtask ${fullSubtaskId} depends on itself`, + }); + return; + } - // Check if dependency exists - if (!taskExists(tasks, depId)) { - issues.push({ - type: 'missing', - taskId: fullSubtaskId, - dependencyId: depId, - message: `Subtask ${fullSubtaskId} depends on non-existent task/subtask ${depId}` - }); - } - }); + // Check if dependency exists + if (!taskExists(tasks, depId)) { + issues.push({ + type: "missing", + taskId: fullSubtaskId, + dependencyId: depId, + message: `Subtask ${fullSubtaskId} depends on non-existent task/subtask ${depId}`, + }); + } + }); - // Check for circular dependencies in subtasks - if (isCircularDependency(tasks, fullSubtaskId)) { - issues.push({ - type: 'circular', - taskId: fullSubtaskId, - message: `Subtask ${fullSubtaskId} is part of a circular dependency chain` - }); - } - }); - } - }); + // Check for circular dependencies in subtasks + if (isCircularDependency(tasks, fullSubtaskId)) { + issues.push({ + type: "circular", + taskId: fullSubtaskId, + message: `Subtask ${fullSubtaskId} is part of a circular dependency chain`, + }); + } + }); + } + }); - return { - valid: issues.length === 0, - issues - }; + return { + valid: issues.length === 0, + issues, + }; } /** @@ -499,23 +499,23 @@ function validateTaskDependencies(tasks) { * @returns {Object} Updated tasks data with duplicates removed */ function removeDuplicateDependencies(tasksData) { - const tasks = tasksData.tasks.map((task) => { - if (!task.dependencies) { - return task; - } + const tasks = tasksData.tasks.map((task) => { + if (!task.dependencies) { + return task; + } - // Convert to Set and back to array to remove duplicates - const uniqueDeps = [...new Set(task.dependencies)]; - return { - ...task, - dependencies: uniqueDeps - }; - }); + // Convert to Set and back to array to remove duplicates + const uniqueDeps = [...new Set(task.dependencies)]; + return { + ...task, + dependencies: uniqueDeps, + }; + }); - return { - ...tasksData, - tasks - }; + return { + ...tasksData, + tasks, + }; } /** @@ -524,38 +524,38 @@ function removeDuplicateDependencies(tasksData) { * @returns {Object} Updated tasks data with invalid subtask dependencies removed */ function cleanupSubtaskDependencies(tasksData) { - const tasks = tasksData.tasks.map((task) => { - // Handle task's own dependencies - if (task.dependencies) { - task.dependencies = task.dependencies.filter((depId) => { - // Keep only dependencies that exist - return taskExists(tasksData.tasks, depId); - }); - } + const tasks = tasksData.tasks.map((task) => { + // Handle task's own dependencies + if (task.dependencies) { + task.dependencies = task.dependencies.filter((depId) => { + // Keep only dependencies that exist + return taskExists(tasksData.tasks, depId); + }); + } - // Handle subtask dependencies - if (task.subtasks) { - task.subtasks = task.subtasks.map((subtask) => { - if (!subtask.dependencies) { - return subtask; - } + // Handle subtask dependencies + if (task.subtasks) { + task.subtasks = task.subtasks.map((subtask) => { + if (!subtask.dependencies) { + return subtask; + } - // Filter out dependencies to non-existent subtasks - subtask.dependencies = subtask.dependencies.filter((depId) => { - return taskExists(tasksData.tasks, depId); - }); + // Filter out dependencies to non-existent subtasks + subtask.dependencies = subtask.dependencies.filter((depId) => { + return taskExists(tasksData.tasks, depId); + }); - return subtask; - }); - } + return subtask; + }); + } - return task; - }); + return task; + }); - return { - ...tasksData, - tasks - }; + return { + ...tasksData, + tasks, + }; } /** @@ -563,99 +563,94 @@ function cleanupSubtaskDependencies(tasksData) { * @param {string} tasksPath - Path to tasks.json */ async function validateDependenciesCommand(tasksPath, options = {}) { - // Only display banner if not in silent mode - if (!isSilentMode()) { - displayBanner(); - } + log("info", "Checking for invalid dependencies in task files..."); - log('info', 'Checking for invalid dependencies in task files...'); + // Read tasks data + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log("error", "No valid tasks found in tasks.json"); + process.exit(1); + } - // Read tasks data - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', 'No valid tasks found in tasks.json'); - process.exit(1); - } + // Count of tasks and subtasks for reporting + const taskCount = data.tasks.length; + let subtaskCount = 0; + data.tasks.forEach((task) => { + if (task.subtasks && Array.isArray(task.subtasks)) { + subtaskCount += task.subtasks.length; + } + }); - // Count of tasks and subtasks for reporting - const taskCount = data.tasks.length; - let subtaskCount = 0; - data.tasks.forEach((task) => { - if (task.subtasks && Array.isArray(task.subtasks)) { - subtaskCount += task.subtasks.length; - } - }); + log( + "info", + `Analyzing dependencies for ${taskCount} tasks and ${subtaskCount} subtasks...` + ); - log( - 'info', - `Analyzing dependencies for ${taskCount} tasks and ${subtaskCount} subtasks...` - ); + try { + // Directly call the validation function + const validationResult = validateTaskDependencies(data.tasks); - try { - // Directly call the validation function - const validationResult = validateTaskDependencies(data.tasks); + if (!validationResult.valid) { + log( + "error", + `Dependency validation failed. Found ${validationResult.issues.length} issue(s):` + ); + validationResult.issues.forEach((issue) => { + let errorMsg = ` [${issue.type.toUpperCase()}] Task ${issue.taskId}: ${issue.message}`; + if (issue.dependencyId) { + errorMsg += ` (Dependency: ${issue.dependencyId})`; + } + log("error", errorMsg); // Log each issue as an error + }); - if (!validationResult.valid) { - log( - 'error', - `Dependency validation failed. Found ${validationResult.issues.length} issue(s):` - ); - validationResult.issues.forEach((issue) => { - let errorMsg = ` [${issue.type.toUpperCase()}] Task ${issue.taskId}: ${issue.message}`; - if (issue.dependencyId) { - errorMsg += ` (Dependency: ${issue.dependencyId})`; - } - log('error', errorMsg); // Log each issue as an error - }); + // Optionally exit if validation fails, depending on desired behavior + // process.exit(1); // Uncomment if validation failure should stop the process - // Optionally exit if validation fails, depending on desired behavior - // process.exit(1); // Uncomment if validation failure should stop the process + // Display summary box even on failure, showing issues found + if (!isSilentMode()) { + console.log( + boxen( + chalk.red(`Dependency Validation FAILED\n\n`) + + `${chalk.cyan("Tasks checked:")} ${taskCount}\n` + + `${chalk.cyan("Subtasks checked:")} ${subtaskCount}\n` + + `${chalk.red("Issues found:")} ${validationResult.issues.length}`, // Display count from result + { + padding: 1, + borderColor: "red", + borderStyle: "round", + margin: { top: 1, bottom: 1 }, + } + ) + ); + } + } else { + log( + "success", + "No invalid dependencies found - all dependencies are valid" + ); - // Display summary box even on failure, showing issues found - if (!isSilentMode()) { - console.log( - boxen( - chalk.red(`Dependency Validation FAILED\n\n`) + - `${chalk.cyan('Tasks checked:')} ${taskCount}\n` + - `${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` + - `${chalk.red('Issues found:')} ${validationResult.issues.length}`, // Display count from result - { - padding: 1, - borderColor: 'red', - borderStyle: 'round', - margin: { top: 1, bottom: 1 } - } - ) - ); - } - } else { - log( - 'success', - 'No invalid dependencies found - all dependencies are valid' - ); - - // Show validation summary - only if not in silent mode - if (!isSilentMode()) { - console.log( - boxen( - chalk.green(`All Dependencies Are Valid\n\n`) + - `${chalk.cyan('Tasks checked:')} ${taskCount}\n` + - `${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` + - `${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`, - { - padding: 1, - borderColor: 'green', - borderStyle: 'round', - margin: { top: 1, bottom: 1 } - } - ) - ); - } - } - } catch (error) { - log('error', 'Error validating dependencies:', error); - process.exit(1); - } + // Show validation summary - only if not in silent mode + if (!isSilentMode()) { + console.log( + boxen( + chalk.green(`All Dependencies Are Valid\n\n`) + + `${chalk.cyan("Tasks checked:")} ${taskCount}\n` + + `${chalk.cyan("Subtasks checked:")} ${subtaskCount}\n` + + `${chalk.cyan("Total dependencies verified:")} ${countAllDependencies(data.tasks)}`, + { + padding: 1, + borderColor: "green", + borderStyle: "round", + margin: { top: 1, bottom: 1 }, + } + ) + ); + } + } + } catch (error) { + log("error", "Error validating dependencies:", error); + process.exit(1); + } } /** @@ -664,25 +659,25 @@ async function validateDependenciesCommand(tasksPath, options = {}) { * @returns {number} - Total number of dependencies */ function countAllDependencies(tasks) { - let count = 0; + let count = 0; - tasks.forEach((task) => { - // Count main task dependencies - if (task.dependencies && Array.isArray(task.dependencies)) { - count += task.dependencies.length; - } + tasks.forEach((task) => { + // Count main task dependencies + if (task.dependencies && Array.isArray(task.dependencies)) { + count += task.dependencies.length; + } - // Count subtask dependencies - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach((subtask) => { - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - count += subtask.dependencies.length; - } - }); - } - }); + // Count subtask dependencies + if (task.subtasks && Array.isArray(task.subtasks)) { + task.subtasks.forEach((subtask) => { + if (subtask.dependencies && Array.isArray(subtask.dependencies)) { + count += subtask.dependencies.length; + } + }); + } + }); - return count; + return count; } /** @@ -691,392 +686,387 @@ function countAllDependencies(tasks) { * @param {Object} options - Options object */ async function fixDependenciesCommand(tasksPath, options = {}) { - // Only display banner if not in silent mode - if (!isSilentMode()) { - displayBanner(); - } + log("info", "Checking for and fixing invalid dependencies in tasks.json..."); - log('info', 'Checking for and fixing invalid dependencies in tasks.json...'); + try { + // Read tasks data + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log("error", "No valid tasks found in tasks.json"); + process.exit(1); + } - try { - // Read tasks data - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', 'No valid tasks found in tasks.json'); - process.exit(1); - } + // Create a deep copy of the original data for comparison + const originalData = JSON.parse(JSON.stringify(data)); - // Create a deep copy of the original data for comparison - const originalData = JSON.parse(JSON.stringify(data)); + // Track fixes for reporting + const stats = { + nonExistentDependenciesRemoved: 0, + selfDependenciesRemoved: 0, + duplicateDependenciesRemoved: 0, + circularDependenciesFixed: 0, + tasksFixed: 0, + subtasksFixed: 0, + }; - // Track fixes for reporting - const stats = { - nonExistentDependenciesRemoved: 0, - selfDependenciesRemoved: 0, - duplicateDependenciesRemoved: 0, - circularDependenciesFixed: 0, - tasksFixed: 0, - subtasksFixed: 0 - }; + // First phase: Remove duplicate dependencies in tasks + data.tasks.forEach((task) => { + if (task.dependencies && Array.isArray(task.dependencies)) { + const uniqueDeps = new Set(); + const originalLength = task.dependencies.length; + task.dependencies = task.dependencies.filter((depId) => { + const depIdStr = String(depId); + if (uniqueDeps.has(depIdStr)) { + log( + "info", + `Removing duplicate dependency from task ${task.id}: ${depId}` + ); + stats.duplicateDependenciesRemoved++; + return false; + } + uniqueDeps.add(depIdStr); + return true; + }); + if (task.dependencies.length < originalLength) { + stats.tasksFixed++; + } + } - // First phase: Remove duplicate dependencies in tasks - data.tasks.forEach((task) => { - if (task.dependencies && Array.isArray(task.dependencies)) { - const uniqueDeps = new Set(); - const originalLength = task.dependencies.length; - task.dependencies = task.dependencies.filter((depId) => { - const depIdStr = String(depId); - if (uniqueDeps.has(depIdStr)) { - log( - 'info', - `Removing duplicate dependency from task ${task.id}: ${depId}` - ); - stats.duplicateDependenciesRemoved++; - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - if (task.dependencies.length < originalLength) { - stats.tasksFixed++; - } - } + // Check for duplicates in subtasks + if (task.subtasks && Array.isArray(task.subtasks)) { + task.subtasks.forEach((subtask) => { + if (subtask.dependencies && Array.isArray(subtask.dependencies)) { + const uniqueDeps = new Set(); + const originalLength = subtask.dependencies.length; + subtask.dependencies = subtask.dependencies.filter((depId) => { + let depIdStr = String(depId); + if (typeof depId === "number" && depId < 100) { + depIdStr = `${task.id}.${depId}`; + } + if (uniqueDeps.has(depIdStr)) { + log( + "info", + `Removing duplicate dependency from subtask ${task.id}.${subtask.id}: ${depId}` + ); + stats.duplicateDependenciesRemoved++; + return false; + } + uniqueDeps.add(depIdStr); + return true; + }); + if (subtask.dependencies.length < originalLength) { + stats.subtasksFixed++; + } + } + }); + } + }); - // Check for duplicates in subtasks - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach((subtask) => { - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - const uniqueDeps = new Set(); - const originalLength = subtask.dependencies.length; - subtask.dependencies = subtask.dependencies.filter((depId) => { - let depIdStr = String(depId); - if (typeof depId === 'number' && depId < 100) { - depIdStr = `${task.id}.${depId}`; - } - if (uniqueDeps.has(depIdStr)) { - log( - 'info', - `Removing duplicate dependency from subtask ${task.id}.${subtask.id}: ${depId}` - ); - stats.duplicateDependenciesRemoved++; - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - if (subtask.dependencies.length < originalLength) { - stats.subtasksFixed++; - } - } - }); - } - }); + // Create validity maps for tasks and subtasks + const validTaskIds = new Set(data.tasks.map((t) => t.id)); + const validSubtaskIds = new Set(); + data.tasks.forEach((task) => { + if (task.subtasks && Array.isArray(task.subtasks)) { + task.subtasks.forEach((subtask) => { + validSubtaskIds.add(`${task.id}.${subtask.id}`); + }); + } + }); - // Create validity maps for tasks and subtasks - const validTaskIds = new Set(data.tasks.map((t) => t.id)); - const validSubtaskIds = new Set(); - data.tasks.forEach((task) => { - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach((subtask) => { - validSubtaskIds.add(`${task.id}.${subtask.id}`); - }); - } - }); + // Second phase: Remove invalid task dependencies (non-existent tasks) + data.tasks.forEach((task) => { + if (task.dependencies && Array.isArray(task.dependencies)) { + const originalLength = task.dependencies.length; + task.dependencies = task.dependencies.filter((depId) => { + const isSubtask = typeof depId === "string" && depId.includes("."); - // Second phase: Remove invalid task dependencies (non-existent tasks) - data.tasks.forEach((task) => { - if (task.dependencies && Array.isArray(task.dependencies)) { - const originalLength = task.dependencies.length; - task.dependencies = task.dependencies.filter((depId) => { - const isSubtask = typeof depId === 'string' && depId.includes('.'); + if (isSubtask) { + // Check if the subtask exists + if (!validSubtaskIds.has(depId)) { + log( + "info", + `Removing invalid subtask dependency from task ${task.id}: ${depId} (subtask does not exist)` + ); + stats.nonExistentDependenciesRemoved++; + return false; + } + return true; + } else { + // Check if the task exists + const numericId = + typeof depId === "string" ? parseInt(depId, 10) : depId; + if (!validTaskIds.has(numericId)) { + log( + "info", + `Removing invalid task dependency from task ${task.id}: ${depId} (task does not exist)` + ); + stats.nonExistentDependenciesRemoved++; + return false; + } + return true; + } + }); - if (isSubtask) { - // Check if the subtask exists - if (!validSubtaskIds.has(depId)) { - log( - 'info', - `Removing invalid subtask dependency from task ${task.id}: ${depId} (subtask does not exist)` - ); - stats.nonExistentDependenciesRemoved++; - return false; - } - return true; - } else { - // Check if the task exists - const numericId = - typeof depId === 'string' ? parseInt(depId, 10) : depId; - if (!validTaskIds.has(numericId)) { - log( - 'info', - `Removing invalid task dependency from task ${task.id}: ${depId} (task does not exist)` - ); - stats.nonExistentDependenciesRemoved++; - return false; - } - return true; - } - }); + if (task.dependencies.length < originalLength) { + stats.tasksFixed++; + } + } - if (task.dependencies.length < originalLength) { - stats.tasksFixed++; - } - } + // Check subtask dependencies for invalid references + if (task.subtasks && Array.isArray(task.subtasks)) { + task.subtasks.forEach((subtask) => { + if (subtask.dependencies && Array.isArray(subtask.dependencies)) { + const originalLength = subtask.dependencies.length; + const subtaskId = `${task.id}.${subtask.id}`; - // Check subtask dependencies for invalid references - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach((subtask) => { - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - const originalLength = subtask.dependencies.length; - const subtaskId = `${task.id}.${subtask.id}`; + // First check for self-dependencies + const hasSelfDependency = subtask.dependencies.some((depId) => { + if (typeof depId === "string" && depId.includes(".")) { + return depId === subtaskId; + } else if (typeof depId === "number" && depId < 100) { + return depId === subtask.id; + } + return false; + }); - // First check for self-dependencies - const hasSelfDependency = subtask.dependencies.some((depId) => { - if (typeof depId === 'string' && depId.includes('.')) { - return depId === subtaskId; - } else if (typeof depId === 'number' && depId < 100) { - return depId === subtask.id; - } - return false; - }); + if (hasSelfDependency) { + subtask.dependencies = subtask.dependencies.filter((depId) => { + const normalizedDepId = + typeof depId === "number" && depId < 100 + ? `${task.id}.${depId}` + : String(depId); - if (hasSelfDependency) { - subtask.dependencies = subtask.dependencies.filter((depId) => { - const normalizedDepId = - typeof depId === 'number' && depId < 100 - ? `${task.id}.${depId}` - : String(depId); + if (normalizedDepId === subtaskId) { + log( + "info", + `Removing self-dependency from subtask ${subtaskId}` + ); + stats.selfDependenciesRemoved++; + return false; + } + return true; + }); + } - if (normalizedDepId === subtaskId) { - log( - 'info', - `Removing self-dependency from subtask ${subtaskId}` - ); - stats.selfDependenciesRemoved++; - return false; - } - return true; - }); - } + // Then check for non-existent dependencies + subtask.dependencies = subtask.dependencies.filter((depId) => { + if (typeof depId === "string" && depId.includes(".")) { + if (!validSubtaskIds.has(depId)) { + log( + "info", + `Removing invalid subtask dependency from subtask ${subtaskId}: ${depId} (subtask does not exist)` + ); + stats.nonExistentDependenciesRemoved++; + return false; + } + return true; + } - // Then check for non-existent dependencies - subtask.dependencies = subtask.dependencies.filter((depId) => { - if (typeof depId === 'string' && depId.includes('.')) { - if (!validSubtaskIds.has(depId)) { - log( - 'info', - `Removing invalid subtask dependency from subtask ${subtaskId}: ${depId} (subtask does not exist)` - ); - stats.nonExistentDependenciesRemoved++; - return false; - } - return true; - } + // Handle numeric dependencies + const numericId = + typeof depId === "number" ? depId : parseInt(depId, 10); - // Handle numeric dependencies - const numericId = - typeof depId === 'number' ? depId : parseInt(depId, 10); + // Small numbers likely refer to subtasks in the same task + if (numericId < 100) { + const fullSubtaskId = `${task.id}.${numericId}`; - // Small numbers likely refer to subtasks in the same task - if (numericId < 100) { - const fullSubtaskId = `${task.id}.${numericId}`; + if (!validSubtaskIds.has(fullSubtaskId)) { + log( + "info", + `Removing invalid subtask dependency from subtask ${subtaskId}: ${numericId}` + ); + stats.nonExistentDependenciesRemoved++; + return false; + } - if (!validSubtaskIds.has(fullSubtaskId)) { - log( - 'info', - `Removing invalid subtask dependency from subtask ${subtaskId}: ${numericId}` - ); - stats.nonExistentDependenciesRemoved++; - return false; - } + return true; + } - return true; - } + // Otherwise it's a task reference + if (!validTaskIds.has(numericId)) { + log( + "info", + `Removing invalid task dependency from subtask ${subtaskId}: ${numericId}` + ); + stats.nonExistentDependenciesRemoved++; + return false; + } - // Otherwise it's a task reference - if (!validTaskIds.has(numericId)) { - log( - 'info', - `Removing invalid task dependency from subtask ${subtaskId}: ${numericId}` - ); - stats.nonExistentDependenciesRemoved++; - return false; - } + return true; + }); - return true; - }); + if (subtask.dependencies.length < originalLength) { + stats.subtasksFixed++; + } + } + }); + } + }); - if (subtask.dependencies.length < originalLength) { - stats.subtasksFixed++; - } - } - }); - } - }); + // Third phase: Check for circular dependencies + log("info", "Checking for circular dependencies..."); - // Third phase: Check for circular dependencies - log('info', 'Checking for circular dependencies...'); + // Build the dependency map for subtasks + const subtaskDependencyMap = new Map(); + data.tasks.forEach((task) => { + if (task.subtasks && Array.isArray(task.subtasks)) { + task.subtasks.forEach((subtask) => { + const subtaskId = `${task.id}.${subtask.id}`; - // Build the dependency map for subtasks - const subtaskDependencyMap = new Map(); - data.tasks.forEach((task) => { - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach((subtask) => { - const subtaskId = `${task.id}.${subtask.id}`; + if (subtask.dependencies && Array.isArray(subtask.dependencies)) { + const normalizedDeps = subtask.dependencies.map((depId) => { + if (typeof depId === "string" && depId.includes(".")) { + return depId; + } else if (typeof depId === "number" && depId < 100) { + return `${task.id}.${depId}`; + } + return String(depId); + }); + subtaskDependencyMap.set(subtaskId, normalizedDeps); + } else { + subtaskDependencyMap.set(subtaskId, []); + } + }); + } + }); - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - const normalizedDeps = subtask.dependencies.map((depId) => { - if (typeof depId === 'string' && depId.includes('.')) { - return depId; - } else if (typeof depId === 'number' && depId < 100) { - return `${task.id}.${depId}`; - } - return String(depId); - }); - subtaskDependencyMap.set(subtaskId, normalizedDeps); - } else { - subtaskDependencyMap.set(subtaskId, []); - } - }); - } - }); + // Check for and fix circular dependencies + for (const [subtaskId, dependencies] of subtaskDependencyMap.entries()) { + const visited = new Set(); + const recursionStack = new Set(); - // Check for and fix circular dependencies - for (const [subtaskId, dependencies] of subtaskDependencyMap.entries()) { - const visited = new Set(); - const recursionStack = new Set(); + // Detect cycles + const cycleEdges = findCycles( + subtaskId, + subtaskDependencyMap, + visited, + recursionStack + ); - // Detect cycles - const cycleEdges = findCycles( - subtaskId, - subtaskDependencyMap, - visited, - recursionStack - ); + if (cycleEdges.length > 0) { + const [taskId, subtaskNum] = subtaskId + .split(".") + .map((part) => Number(part)); + const task = data.tasks.find((t) => t.id === taskId); - if (cycleEdges.length > 0) { - const [taskId, subtaskNum] = subtaskId - .split('.') - .map((part) => Number(part)); - const task = data.tasks.find((t) => t.id === taskId); + if (task && task.subtasks) { + const subtask = task.subtasks.find((st) => st.id === subtaskNum); - if (task && task.subtasks) { - const subtask = task.subtasks.find((st) => st.id === subtaskNum); + if (subtask && subtask.dependencies) { + const originalLength = subtask.dependencies.length; - if (subtask && subtask.dependencies) { - const originalLength = subtask.dependencies.length; + const edgesToRemove = cycleEdges.map((edge) => { + if (edge.includes(".")) { + const [depTaskId, depSubtaskId] = edge + .split(".") + .map((part) => Number(part)); - const edgesToRemove = cycleEdges.map((edge) => { - if (edge.includes('.')) { - const [depTaskId, depSubtaskId] = edge - .split('.') - .map((part) => Number(part)); + if (depTaskId === taskId) { + return depSubtaskId; + } - if (depTaskId === taskId) { - return depSubtaskId; - } + return edge; + } - return edge; - } + return Number(edge); + }); - return Number(edge); - }); + subtask.dependencies = subtask.dependencies.filter((depId) => { + const normalizedDepId = + typeof depId === "number" && depId < 100 + ? `${taskId}.${depId}` + : String(depId); - subtask.dependencies = subtask.dependencies.filter((depId) => { - const normalizedDepId = - typeof depId === 'number' && depId < 100 - ? `${taskId}.${depId}` - : String(depId); + if ( + edgesToRemove.includes(depId) || + edgesToRemove.includes(normalizedDepId) + ) { + log( + "info", + `Breaking circular dependency: Removing ${normalizedDepId} from subtask ${subtaskId}` + ); + stats.circularDependenciesFixed++; + return false; + } + return true; + }); - if ( - edgesToRemove.includes(depId) || - edgesToRemove.includes(normalizedDepId) - ) { - log( - 'info', - `Breaking circular dependency: Removing ${normalizedDepId} from subtask ${subtaskId}` - ); - stats.circularDependenciesFixed++; - return false; - } - return true; - }); + if (subtask.dependencies.length < originalLength) { + stats.subtasksFixed++; + } + } + } + } + } - if (subtask.dependencies.length < originalLength) { - stats.subtasksFixed++; - } - } - } - } - } + // Check if any changes were made by comparing with original data + const dataChanged = JSON.stringify(data) !== JSON.stringify(originalData); - // Check if any changes were made by comparing with original data - const dataChanged = JSON.stringify(data) !== JSON.stringify(originalData); + if (dataChanged) { + // Save the changes + writeJSON(tasksPath, data); + log("success", "Fixed dependency issues in tasks.json"); - if (dataChanged) { - // Save the changes - writeJSON(tasksPath, data); - log('success', 'Fixed dependency issues in tasks.json'); + // Regenerate task files + log("info", "Regenerating task files to reflect dependency changes..."); + await generateTaskFiles(tasksPath, path.dirname(tasksPath)); + } else { + log("info", "No changes needed to fix dependencies"); + } - // Regenerate task files - log('info', 'Regenerating task files to reflect dependency changes...'); - await generateTaskFiles(tasksPath, path.dirname(tasksPath)); - } else { - log('info', 'No changes needed to fix dependencies'); - } + // Show detailed statistics report + const totalFixedAll = + stats.nonExistentDependenciesRemoved + + stats.selfDependenciesRemoved + + stats.duplicateDependenciesRemoved + + stats.circularDependenciesFixed; - // Show detailed statistics report - const totalFixedAll = - stats.nonExistentDependenciesRemoved + - stats.selfDependenciesRemoved + - stats.duplicateDependenciesRemoved + - stats.circularDependenciesFixed; + if (!isSilentMode()) { + if (totalFixedAll > 0) { + log("success", `Fixed ${totalFixedAll} dependency issues in total!`); - if (!isSilentMode()) { - if (totalFixedAll > 0) { - log('success', `Fixed ${totalFixedAll} dependency issues in total!`); + console.log( + boxen( + chalk.green(`Dependency Fixes Summary:\n\n`) + + `${chalk.cyan("Invalid dependencies removed:")} ${stats.nonExistentDependenciesRemoved}\n` + + `${chalk.cyan("Self-dependencies removed:")} ${stats.selfDependenciesRemoved}\n` + + `${chalk.cyan("Duplicate dependencies removed:")} ${stats.duplicateDependenciesRemoved}\n` + + `${chalk.cyan("Circular dependencies fixed:")} ${stats.circularDependenciesFixed}\n\n` + + `${chalk.cyan("Tasks fixed:")} ${stats.tasksFixed}\n` + + `${chalk.cyan("Subtasks fixed:")} ${stats.subtasksFixed}\n`, + { + padding: 1, + borderColor: "green", + borderStyle: "round", + margin: { top: 1, bottom: 1 }, + } + ) + ); + } else { + log( + "success", + "No dependency issues found - all dependencies are valid" + ); - console.log( - boxen( - chalk.green(`Dependency Fixes Summary:\n\n`) + - `${chalk.cyan('Invalid dependencies removed:')} ${stats.nonExistentDependenciesRemoved}\n` + - `${chalk.cyan('Self-dependencies removed:')} ${stats.selfDependenciesRemoved}\n` + - `${chalk.cyan('Duplicate dependencies removed:')} ${stats.duplicateDependenciesRemoved}\n` + - `${chalk.cyan('Circular dependencies fixed:')} ${stats.circularDependenciesFixed}\n\n` + - `${chalk.cyan('Tasks fixed:')} ${stats.tasksFixed}\n` + - `${chalk.cyan('Subtasks fixed:')} ${stats.subtasksFixed}\n`, - { - padding: 1, - borderColor: 'green', - borderStyle: 'round', - margin: { top: 1, bottom: 1 } - } - ) - ); - } else { - log( - 'success', - 'No dependency issues found - all dependencies are valid' - ); - - console.log( - boxen( - chalk.green(`All Dependencies Are Valid\n\n`) + - `${chalk.cyan('Tasks checked:')} ${data.tasks.length}\n` + - `${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`, - { - padding: 1, - borderColor: 'green', - borderStyle: 'round', - margin: { top: 1, bottom: 1 } - } - ) - ); - } - } - } catch (error) { - log('error', 'Error in fix-dependencies command:', error); - process.exit(1); - } + console.log( + boxen( + chalk.green(`All Dependencies Are Valid\n\n`) + + `${chalk.cyan("Tasks checked:")} ${data.tasks.length}\n` + + `${chalk.cyan("Total dependencies verified:")} ${countAllDependencies(data.tasks)}`, + { + padding: 1, + borderColor: "green", + borderStyle: "round", + margin: { top: 1, bottom: 1 }, + } + ) + ); + } + } + } catch (error) { + log("error", "Error in fix-dependencies command:", error); + process.exit(1); + } } /** @@ -1085,44 +1075,44 @@ async function fixDependenciesCommand(tasksPath, options = {}) { * @returns {boolean} - True if any changes were made */ function ensureAtLeastOneIndependentSubtask(tasksData) { - if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { - return false; - } + if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { + return false; + } - let changesDetected = false; + let changesDetected = false; - tasksData.tasks.forEach((task) => { - if ( - !task.subtasks || - !Array.isArray(task.subtasks) || - task.subtasks.length === 0 - ) { - return; - } + tasksData.tasks.forEach((task) => { + if ( + !task.subtasks || + !Array.isArray(task.subtasks) || + task.subtasks.length === 0 + ) { + return; + } - // Check if any subtask has no dependencies - const hasIndependentSubtask = task.subtasks.some( - (st) => - !st.dependencies || - !Array.isArray(st.dependencies) || - st.dependencies.length === 0 - ); + // Check if any subtask has no dependencies + const hasIndependentSubtask = task.subtasks.some( + (st) => + !st.dependencies || + !Array.isArray(st.dependencies) || + st.dependencies.length === 0 + ); - if (!hasIndependentSubtask) { - // Find the first subtask and clear its dependencies - if (task.subtasks.length > 0) { - const firstSubtask = task.subtasks[0]; - log( - 'debug', - `Ensuring at least one independent subtask: Clearing dependencies for subtask ${task.id}.${firstSubtask.id}` - ); - firstSubtask.dependencies = []; - changesDetected = true; - } - } - }); + if (!hasIndependentSubtask) { + // Find the first subtask and clear its dependencies + if (task.subtasks.length > 0) { + const firstSubtask = task.subtasks[0]; + log( + "debug", + `Ensuring at least one independent subtask: Clearing dependencies for subtask ${task.id}.${firstSubtask.id}` + ); + firstSubtask.dependencies = []; + changesDetected = true; + } + } + }); - return changesDetected; + return changesDetected; } /** @@ -1133,111 +1123,111 @@ function ensureAtLeastOneIndependentSubtask(tasksData) { * @returns {boolean} - True if any changes were made */ function validateAndFixDependencies(tasksData, tasksPath = null) { - if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { - log('error', 'Invalid tasks data'); - return false; - } + if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { + log("error", "Invalid tasks data"); + return false; + } - log('debug', 'Validating and fixing dependencies...'); + log("debug", "Validating and fixing dependencies..."); - // Create a deep copy for comparison - const originalData = JSON.parse(JSON.stringify(tasksData)); + // Create a deep copy for comparison + const originalData = JSON.parse(JSON.stringify(tasksData)); - // 1. Remove duplicate dependencies from tasks and subtasks - tasksData.tasks = tasksData.tasks.map((task) => { - // Handle task dependencies - if (task.dependencies) { - const uniqueDeps = [...new Set(task.dependencies)]; - task.dependencies = uniqueDeps; - } + // 1. Remove duplicate dependencies from tasks and subtasks + tasksData.tasks = tasksData.tasks.map((task) => { + // Handle task dependencies + if (task.dependencies) { + const uniqueDeps = [...new Set(task.dependencies)]; + task.dependencies = uniqueDeps; + } - // Handle subtask dependencies - if (task.subtasks) { - task.subtasks = task.subtasks.map((subtask) => { - if (subtask.dependencies) { - const uniqueDeps = [...new Set(subtask.dependencies)]; - subtask.dependencies = uniqueDeps; - } - return subtask; - }); - } - return task; - }); + // Handle subtask dependencies + if (task.subtasks) { + task.subtasks = task.subtasks.map((subtask) => { + if (subtask.dependencies) { + const uniqueDeps = [...new Set(subtask.dependencies)]; + subtask.dependencies = uniqueDeps; + } + return subtask; + }); + } + return task; + }); - // 2. Remove invalid task dependencies (non-existent tasks) - tasksData.tasks.forEach((task) => { - // Clean up task dependencies - if (task.dependencies) { - task.dependencies = task.dependencies.filter((depId) => { - // Remove self-dependencies - if (String(depId) === String(task.id)) { - return false; - } - // Remove non-existent dependencies - return taskExists(tasksData.tasks, depId); - }); - } + // 2. Remove invalid task dependencies (non-existent tasks) + tasksData.tasks.forEach((task) => { + // Clean up task dependencies + if (task.dependencies) { + task.dependencies = task.dependencies.filter((depId) => { + // Remove self-dependencies + if (String(depId) === String(task.id)) { + return false; + } + // Remove non-existent dependencies + return taskExists(tasksData.tasks, depId); + }); + } - // Clean up subtask dependencies - if (task.subtasks) { - task.subtasks.forEach((subtask) => { - if (subtask.dependencies) { - subtask.dependencies = subtask.dependencies.filter((depId) => { - // Handle numeric subtask references - if (typeof depId === 'number' && depId < 100) { - const fullSubtaskId = `${task.id}.${depId}`; - return taskExists(tasksData.tasks, fullSubtaskId); - } - // Handle full task/subtask references - return taskExists(tasksData.tasks, depId); - }); - } - }); - } - }); + // Clean up subtask dependencies + if (task.subtasks) { + task.subtasks.forEach((subtask) => { + if (subtask.dependencies) { + subtask.dependencies = subtask.dependencies.filter((depId) => { + // Handle numeric subtask references + if (typeof depId === "number" && depId < 100) { + const fullSubtaskId = `${task.id}.${depId}`; + return taskExists(tasksData.tasks, fullSubtaskId); + } + // Handle full task/subtask references + return taskExists(tasksData.tasks, depId); + }); + } + }); + } + }); - // 3. Ensure at least one subtask has no dependencies in each task - tasksData.tasks.forEach((task) => { - if (task.subtasks && task.subtasks.length > 0) { - const hasIndependentSubtask = task.subtasks.some( - (st) => - !st.dependencies || - !Array.isArray(st.dependencies) || - st.dependencies.length === 0 - ); + // 3. Ensure at least one subtask has no dependencies in each task + tasksData.tasks.forEach((task) => { + if (task.subtasks && task.subtasks.length > 0) { + const hasIndependentSubtask = task.subtasks.some( + (st) => + !st.dependencies || + !Array.isArray(st.dependencies) || + st.dependencies.length === 0 + ); - if (!hasIndependentSubtask) { - task.subtasks[0].dependencies = []; - } - } - }); + if (!hasIndependentSubtask) { + task.subtasks[0].dependencies = []; + } + } + }); - // Check if any changes were made by comparing with original data - const changesDetected = - JSON.stringify(tasksData) !== JSON.stringify(originalData); + // Check if any changes were made by comparing with original data + const changesDetected = + JSON.stringify(tasksData) !== JSON.stringify(originalData); - // Save changes if needed - if (tasksPath && changesDetected) { - try { - writeJSON(tasksPath, tasksData); - log('debug', 'Saved dependency fixes to tasks.json'); - } catch (error) { - log('error', 'Failed to save dependency fixes to tasks.json', error); - } - } + // Save changes if needed + if (tasksPath && changesDetected) { + try { + writeJSON(tasksPath, tasksData); + log("debug", "Saved dependency fixes to tasks.json"); + } catch (error) { + log("error", "Failed to save dependency fixes to tasks.json", error); + } + } - return changesDetected; + return changesDetected; } export { - addDependency, - removeDependency, - isCircularDependency, - validateTaskDependencies, - validateDependenciesCommand, - fixDependenciesCommand, - removeDuplicateDependencies, - cleanupSubtaskDependencies, - ensureAtLeastOneIndependentSubtask, - validateAndFixDependencies + addDependency, + removeDependency, + isCircularDependency, + validateTaskDependencies, + validateDependenciesCommand, + fixDependenciesCommand, + removeDuplicateDependencies, + cleanupSubtaskDependencies, + ensureAtLeastOneIndependentSubtask, + validateAndFixDependencies, }; diff --git a/scripts/modules/task-manager/add-task.js b/scripts/modules/task-manager/add-task.js index 1d0e13ec..e51e0884 100644 --- a/scripts/modules/task-manager/add-task.js +++ b/scripts/modules/task-manager/add-task.js @@ -1,40 +1,42 @@ -import path from 'path'; -import chalk from 'chalk'; -import boxen from 'boxen'; -import Table from 'cli-table3'; -import { z } from 'zod'; -import Fuse from 'fuse.js'; // Import Fuse.js for advanced fuzzy search +import path from "path"; +import chalk from "chalk"; +import boxen from "boxen"; +import Table from "cli-table3"; +import { z } from "zod"; +import Fuse from "fuse.js"; // Import Fuse.js for advanced fuzzy search import { - displayBanner, - getStatusWithColor, - startLoadingIndicator, - stopLoadingIndicator, - displayAiUsageSummary -} from '../ui.js'; -import { readJSON, writeJSON, log as consoleLog, truncate } from '../utils.js'; -import { generateObjectService } from '../ai-services-unified.js'; -import { getDefaultPriority } from '../config-manager.js'; -import generateTaskFiles from './generate-task-files.js'; + displayBanner, + getStatusWithColor, + startLoadingIndicator, + stopLoadingIndicator, + succeedLoadingIndicator, + failLoadingIndicator, + displayAiUsageSummary, +} from "../ui.js"; +import { readJSON, writeJSON, log as consoleLog, truncate } from "../utils.js"; +import { generateObjectService } from "../ai-services-unified.js"; +import { getDefaultPriority } from "../config-manager.js"; +import generateTaskFiles from "./generate-task-files.js"; // Define Zod schema for the expected AI output object const AiTaskDataSchema = z.object({ - title: z.string().describe('Clear, concise title for the task'), - description: z - .string() - .describe('A one or two sentence description of the task'), - details: z - .string() - .describe('In-depth implementation details, considerations, and guidance'), - testStrategy: z - .string() - .describe('Detailed approach for verifying task completion'), - dependencies: z - .array(z.number()) - .optional() - .describe( - 'Array of task IDs that this task depends on (must be completed before this task can start)' - ) + title: z.string().describe("Clear, concise title for the task"), + description: z + .string() + .describe("A one or two sentence description of the task"), + details: z + .string() + .describe("In-depth implementation details, considerations, and guidance"), + testStrategy: z + .string() + .describe("Detailed approach for verifying task completion"), + dependencies: z + .array(z.number()) + .optional() + .describe( + "Array of task IDs that this task depends on (must be completed before this task can start)" + ), }); /** @@ -57,843 +59,790 @@ const AiTaskDataSchema = z.object({ * @returns {Promise<object>} An object containing newTaskId and telemetryData */ async function addTask( - tasksPath, - prompt, - dependencies = [], - priority = null, - context = {}, - outputFormat = 'text', // Default to text for CLI - manualTaskData = null, - useResearch = false + tasksPath, + prompt, + dependencies = [], + priority = null, + context = {}, + outputFormat = "text", // Default to text for CLI + manualTaskData = null, + useResearch = false ) { - const { session, mcpLog, projectRoot, commandName, outputType } = context; - const isMCP = !!mcpLog; - - // Create a consistent logFn object regardless of context - const logFn = isMCP - ? mcpLog // Use MCP logger if provided - : { - // Create a wrapper around consoleLog for CLI - info: (...args) => consoleLog('info', ...args), - warn: (...args) => consoleLog('warn', ...args), - error: (...args) => consoleLog('error', ...args), - debug: (...args) => consoleLog('debug', ...args), - success: (...args) => consoleLog('success', ...args) - }; - - const effectivePriority = priority || getDefaultPriority(projectRoot); - - logFn.info( - `Adding new task with prompt: "${prompt}", Priority: ${effectivePriority}, Dependencies: ${dependencies.join(', ') || 'None'}, Research: ${useResearch}, ProjectRoot: ${projectRoot}` - ); - - let loadingIndicator = null; - let aiServiceResponse = null; // To store the full response from AI service - - // Create custom reporter that checks for MCP log - const report = (message, level = 'info') => { - if (mcpLog) { - mcpLog[level](message); - } else if (outputFormat === 'text') { - consoleLog(level, message); - } - }; - - /** - * Recursively builds a dependency graph for a given task - * @param {Array} tasks - All tasks from tasks.json - * @param {number} taskId - ID of the task to analyze - * @param {Set} visited - Set of already visited task IDs - * @param {Map} depthMap - Map of task ID to its depth in the graph - * @param {number} depth - Current depth in the recursion - * @return {Object} Dependency graph data - */ - function buildDependencyGraph( - tasks, - taskId, - visited = new Set(), - depthMap = new Map(), - depth = 0 - ) { - // Skip if we've already visited this task or it doesn't exist - if (visited.has(taskId)) { - return null; - } - - // Find the task - const task = tasks.find((t) => t.id === taskId); - if (!task) { - return null; - } - - // Mark as visited - visited.add(taskId); - - // Update depth if this is a deeper path to this task - if (!depthMap.has(taskId) || depth < depthMap.get(taskId)) { - depthMap.set(taskId, depth); - } - - // Process dependencies - const dependencyData = []; - if (task.dependencies && task.dependencies.length > 0) { - for (const depId of task.dependencies) { - const depData = buildDependencyGraph( - tasks, - depId, - visited, - depthMap, - depth + 1 - ); - if (depData) { - dependencyData.push(depData); - } - } - } - - return { - id: task.id, - title: task.title, - description: task.description, - status: task.status, - dependencies: dependencyData - }; - } - - try { - // Read the existing tasks - let data = readJSON(tasksPath); - - // If tasks.json doesn't exist or is invalid, create a new one - if (!data || !data.tasks) { - report('tasks.json not found or invalid. Creating a new one.', 'info'); - // Create default tasks data structure - data = { - tasks: [] - }; - // Ensure the directory exists and write the new file - writeJSON(tasksPath, data); - report('Created new tasks.json file with empty tasks array.', 'info'); - } - - // Find the highest task ID to determine the next ID - const highestId = - data.tasks.length > 0 ? Math.max(...data.tasks.map((t) => t.id)) : 0; - const newTaskId = highestId + 1; - - // Only show UI box for CLI mode - if (outputFormat === 'text') { - console.log( - boxen(chalk.white.bold(`Creating New Task #${newTaskId}`), { - padding: 1, - borderColor: 'blue', - borderStyle: 'round', - margin: { top: 1, bottom: 1 } - }) - ); - } - - // Validate dependencies before proceeding - const invalidDeps = dependencies.filter((depId) => { - // Ensure depId is parsed as a number for comparison - const numDepId = parseInt(depId, 10); - return isNaN(numDepId) || !data.tasks.some((t) => t.id === numDepId); - }); - - if (invalidDeps.length > 0) { - report( - `The following dependencies do not exist or are invalid: ${invalidDeps.join(', ')}`, - 'warn' - ); - report('Removing invalid dependencies...', 'info'); - dependencies = dependencies.filter( - (depId) => !invalidDeps.includes(depId) - ); - } - // Ensure dependencies are numbers - const numericDependencies = dependencies.map((dep) => parseInt(dep, 10)); - - // Build dependency graphs for explicitly specified dependencies - const dependencyGraphs = []; - const allRelatedTaskIds = new Set(); - const depthMap = new Map(); - - // First pass: build a complete dependency graph for each specified dependency - for (const depId of numericDependencies) { - const graph = buildDependencyGraph( - data.tasks, - depId, - new Set(), - depthMap - ); - if (graph) { - dependencyGraphs.push(graph); - } - } - - // Second pass: build a set of all related task IDs for flat analysis - for (const [taskId, depth] of depthMap.entries()) { - allRelatedTaskIds.add(taskId); - } - - let taskData; - - // Check if manual task data is provided - if (manualTaskData) { - report('Using manually provided task data', 'info'); - taskData = manualTaskData; - report('DEBUG: Taking MANUAL task data path.', 'debug'); - - // Basic validation for manual data - if ( - !taskData.title || - typeof taskData.title !== 'string' || - !taskData.description || - typeof taskData.description !== 'string' - ) { - throw new Error( - 'Manual task data must include at least a title and description.' - ); - } - } else { - report('DEBUG: Taking AI task generation path.', 'debug'); - // --- Refactored AI Interaction --- - report(`Generating task data with AI with prompt:\n${prompt}`, 'info'); - - // Create context string for task creation prompt - let contextTasks = ''; - - // Create a dependency map for better understanding of the task relationships - const taskMap = {}; - data.tasks.forEach((t) => { - // For each task, only include id, title, description, and dependencies - taskMap[t.id] = { - id: t.id, - title: t.title, - description: t.description, - dependencies: t.dependencies || [], - status: t.status - }; - }); - - // CLI-only feedback for the dependency analysis - if (outputFormat === 'text') { - console.log( - boxen(chalk.cyan.bold('Task Context Analysis') + '\n', { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - margin: { top: 0, bottom: 0 }, - borderColor: 'cyan', - borderStyle: 'round' - }) - ); - } - - // Initialize variables that will be used in either branch - let uniqueDetailedTasks = []; - let dependentTasks = []; - let promptCategory = null; - - if (numericDependencies.length > 0) { - // If specific dependencies were provided, focus on them - // Get all tasks that were found in the dependency graph - dependentTasks = Array.from(allRelatedTaskIds) - .map((id) => data.tasks.find((t) => t.id === id)) - .filter(Boolean); - - // Sort by depth in the dependency chain - dependentTasks.sort((a, b) => { - const depthA = depthMap.get(a.id) || 0; - const depthB = depthMap.get(b.id) || 0; - return depthA - depthB; // Lowest depth (root dependencies) first - }); - - // Limit the number of detailed tasks to avoid context explosion - uniqueDetailedTasks = dependentTasks.slice(0, 8); - - contextTasks = `\nThis task relates to a dependency structure with ${dependentTasks.length} related tasks in the chain.\n\nDirect dependencies:`; - const directDeps = data.tasks.filter((t) => - numericDependencies.includes(t.id) - ); - contextTasks += `\n${directDeps.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`).join('\n')}`; - - // Add an overview of indirect dependencies if present - const indirectDeps = dependentTasks.filter( - (t) => !numericDependencies.includes(t.id) - ); - if (indirectDeps.length > 0) { - contextTasks += `\n\nIndirect dependencies (dependencies of dependencies):`; - contextTasks += `\n${indirectDeps - .slice(0, 5) - .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) - .join('\n')}`; - if (indirectDeps.length > 5) { - contextTasks += `\n- ... and ${indirectDeps.length - 5} more indirect dependencies`; - } - } - - // Add more details about each dependency, prioritizing direct dependencies - contextTasks += `\n\nDetailed information about dependencies:`; - for (const depTask of uniqueDetailedTasks) { - const depthInfo = depthMap.get(depTask.id) - ? ` (depth: ${depthMap.get(depTask.id)})` - : ''; - const isDirect = numericDependencies.includes(depTask.id) - ? ' [DIRECT DEPENDENCY]' - : ''; - - contextTasks += `\n\n------ Task ${depTask.id}${isDirect}${depthInfo}: ${depTask.title} ------\n`; - contextTasks += `Description: ${depTask.description}\n`; - contextTasks += `Status: ${depTask.status || 'pending'}\n`; - contextTasks += `Priority: ${depTask.priority || 'medium'}\n`; - - // List its dependencies - if (depTask.dependencies && depTask.dependencies.length > 0) { - const depDeps = depTask.dependencies.map((dId) => { - const depDepTask = data.tasks.find((t) => t.id === dId); - return depDepTask - ? `Task ${dId}: ${depDepTask.title}` - : `Task ${dId}`; - }); - contextTasks += `Dependencies: ${depDeps.join(', ')}\n`; - } else { - contextTasks += `Dependencies: None\n`; - } - - // Add implementation details but truncate if too long - if (depTask.details) { - const truncatedDetails = - depTask.details.length > 400 - ? depTask.details.substring(0, 400) + '... (truncated)' - : depTask.details; - contextTasks += `Implementation Details: ${truncatedDetails}\n`; - } - } - - // Add dependency chain visualization - if (dependencyGraphs.length > 0) { - contextTasks += '\n\nDependency Chain Visualization:'; - - // Helper function to format dependency chain as text - function formatDependencyChain( - node, - prefix = '', - isLast = true, - depth = 0 - ) { - if (depth > 3) return ''; // Limit depth to avoid excessive nesting - - const connector = isLast ? 'โ””โ”€โ”€ ' : 'โ”œโ”€โ”€ '; - const childPrefix = isLast ? ' ' : 'โ”‚ '; - - let result = `\n${prefix}${connector}Task ${node.id}: ${node.title}`; - - if (node.dependencies && node.dependencies.length > 0) { - for (let i = 0; i < node.dependencies.length; i++) { - const isLastChild = i === node.dependencies.length - 1; - result += formatDependencyChain( - node.dependencies[i], - prefix + childPrefix, - isLastChild, - depth + 1 - ); - } - } - - return result; - } - - // Format each dependency graph - for (const graph of dependencyGraphs) { - contextTasks += formatDependencyChain(graph); - } - } - - // Show dependency analysis in CLI mode - if (outputFormat === 'text') { - if (directDeps.length > 0) { - console.log(chalk.gray(` Explicitly specified dependencies:`)); - directDeps.forEach((t) => { - console.log( - chalk.yellow(` โ€ข Task ${t.id}: ${truncate(t.title, 50)}`) - ); - }); - } - - if (indirectDeps.length > 0) { - console.log( - chalk.gray( - `\n Indirect dependencies (${indirectDeps.length} total):` - ) - ); - indirectDeps.slice(0, 3).forEach((t) => { - const depth = depthMap.get(t.id) || 0; - console.log( - chalk.cyan( - ` โ€ข Task ${t.id} [depth ${depth}]: ${truncate(t.title, 45)}` - ) - ); - }); - if (indirectDeps.length > 3) { - console.log( - chalk.cyan( - ` โ€ข ... and ${indirectDeps.length - 3} more indirect dependencies` - ) - ); - } - } - - // Visualize the dependency chain - if (dependencyGraphs.length > 0) { - console.log(chalk.gray(`\n Dependency chain visualization:`)); - - // Convert dependency graph to ASCII art for terminal - function visualizeDependencyGraph( - node, - prefix = '', - isLast = true, - depth = 0 - ) { - if (depth > 2) return; // Limit depth for display - - const connector = isLast ? 'โ””โ”€โ”€ ' : 'โ”œโ”€โ”€ '; - const childPrefix = isLast ? ' ' : 'โ”‚ '; - - console.log( - chalk.blue( - ` ${prefix}${connector}Task ${node.id}: ${truncate(node.title, 40)}` - ) - ); - - if (node.dependencies && node.dependencies.length > 0) { - for (let i = 0; i < node.dependencies.length; i++) { - const isLastChild = i === node.dependencies.length - 1; - visualizeDependencyGraph( - node.dependencies[i], - prefix + childPrefix, - isLastChild, - depth + 1 - ); - } - } - } - - // Visualize each dependency graph - for (const graph of dependencyGraphs) { - visualizeDependencyGraph(graph); - } - } - - console.log(); // Add spacing - } - } else { - // If no dependencies provided, use Fuse.js to find semantically related tasks - // Create fuzzy search index for all tasks - const searchOptions = { - includeScore: true, // Return match scores - threshold: 0.4, // Lower threshold = stricter matching (range 0-1) - keys: [ - { name: 'title', weight: 2 }, // Title is most important - { name: 'description', weight: 1.5 }, // Description is next - { name: 'details', weight: 0.8 }, // Details is less important - // Search dependencies to find tasks that depend on similar things - { name: 'dependencyTitles', weight: 0.5 } - ], - // Sort matches by score (lower is better) - shouldSort: true, - // Allow searching in nested properties - useExtendedSearch: true, - // Return up to 15 matches - limit: 15 - }; - - // Prepare task data with dependencies expanded as titles for better semantic search - const searchableTasks = data.tasks.map((task) => { - // Get titles of this task's dependencies if they exist - const dependencyTitles = - task.dependencies?.length > 0 - ? task.dependencies - .map((depId) => { - const depTask = data.tasks.find((t) => t.id === depId); - return depTask ? depTask.title : ''; - }) - .filter((title) => title) - .join(' ') - : ''; - - return { - ...task, - dependencyTitles - }; - }); - - // Create search index using Fuse.js - const fuse = new Fuse(searchableTasks, searchOptions); - - // Extract significant words and phrases from the prompt - const promptWords = prompt - .toLowerCase() - .replace(/[^\w\s-]/g, ' ') // Replace non-alphanumeric chars with spaces - .split(/\s+/) - .filter((word) => word.length > 3); // Words at least 4 chars - - // Use the user's prompt for fuzzy search - const fuzzyResults = fuse.search(prompt); - - // Also search for each significant word to catch different aspects - let wordResults = []; - for (const word of promptWords) { - if (word.length > 5) { - // Only use significant words - const results = fuse.search(word); - if (results.length > 0) { - wordResults.push(...results); - } - } - } - - // Merge and deduplicate results - const mergedResults = [...fuzzyResults]; - - // Add word results that aren't already in fuzzyResults - for (const wordResult of wordResults) { - if (!mergedResults.some((r) => r.item.id === wordResult.item.id)) { - mergedResults.push(wordResult); - } - } - - // Group search results by relevance - const highRelevance = mergedResults - .filter((result) => result.score < 0.25) - .map((result) => result.item); - - const mediumRelevance = mergedResults - .filter((result) => result.score >= 0.25 && result.score < 0.4) - .map((result) => result.item); - - // Get recent tasks (newest first) - const recentTasks = [...data.tasks] - .sort((a, b) => b.id - a.id) - .slice(0, 5); - - // Combine high relevance, medium relevance, and recent tasks - // Prioritize high relevance first - const allRelevantTasks = [...highRelevance]; - - // Add medium relevance if not already included - for (const task of mediumRelevance) { - if (!allRelevantTasks.some((t) => t.id === task.id)) { - allRelevantTasks.push(task); - } - } - - // Add recent tasks if not already included - for (const task of recentTasks) { - if (!allRelevantTasks.some((t) => t.id === task.id)) { - allRelevantTasks.push(task); - } - } - - // Get top N results for context - const relatedTasks = allRelevantTasks.slice(0, 8); - - // Also look for tasks with similar purposes or categories - const purposeCategories = [ - { pattern: /(command|cli|flag)/i, label: 'CLI commands' }, - { pattern: /(task|subtask|add)/i, label: 'Task management' }, - { pattern: /(dependency|depend)/i, label: 'Dependency handling' }, - { pattern: /(AI|model|prompt)/i, label: 'AI integration' }, - { pattern: /(UI|display|show)/i, label: 'User interface' }, - { pattern: /(schedule|time|cron)/i, label: 'Scheduling' }, // Added scheduling category - { pattern: /(config|setting|option)/i, label: 'Configuration' } // Added configuration category - ]; - - promptCategory = purposeCategories.find((cat) => - cat.pattern.test(prompt) - ); - const categoryTasks = promptCategory - ? data.tasks - .filter( - (t) => - promptCategory.pattern.test(t.title) || - promptCategory.pattern.test(t.description) || - (t.details && promptCategory.pattern.test(t.details)) - ) - .filter((t) => !relatedTasks.some((rt) => rt.id === t.id)) - .slice(0, 3) - : []; - - // Format basic task overviews - if (relatedTasks.length > 0) { - contextTasks = `\nRelevant tasks identified by semantic similarity:\n${relatedTasks - .map((t, i) => { - const relevanceMarker = i < highRelevance.length ? 'โญ ' : ''; - return `- ${relevanceMarker}Task ${t.id}: ${t.title} - ${t.description}`; - }) - .join('\n')}`; - } - - if (categoryTasks.length > 0) { - contextTasks += `\n\nTasks related to ${promptCategory.label}:\n${categoryTasks - .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) - .join('\n')}`; - } - - if ( - recentTasks.length > 0 && - !contextTasks.includes('Recently created tasks') - ) { - contextTasks += `\n\nRecently created tasks:\n${recentTasks - .filter((t) => !relatedTasks.some((rt) => rt.id === t.id)) - .slice(0, 3) - .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) - .join('\n')}`; - } - - // Add detailed information about the most relevant tasks - const allDetailedTasks = [ - ...relatedTasks.slice(0, 5), - ...categoryTasks.slice(0, 2) - ]; - uniqueDetailedTasks = Array.from( - new Map(allDetailedTasks.map((t) => [t.id, t])).values() - ).slice(0, 8); - - if (uniqueDetailedTasks.length > 0) { - contextTasks += `\n\nDetailed information about relevant tasks:`; - for (const task of uniqueDetailedTasks) { - contextTasks += `\n\n------ Task ${task.id}: ${task.title} ------\n`; - contextTasks += `Description: ${task.description}\n`; - contextTasks += `Status: ${task.status || 'pending'}\n`; - contextTasks += `Priority: ${task.priority || 'medium'}\n`; - if (task.dependencies && task.dependencies.length > 0) { - // Format dependency list with titles - const depList = task.dependencies.map((depId) => { - const depTask = data.tasks.find((t) => t.id === depId); - return depTask - ? `Task ${depId} (${depTask.title})` - : `Task ${depId}`; - }); - contextTasks += `Dependencies: ${depList.join(', ')}\n`; - } - // Add implementation details but truncate if too long - if (task.details) { - const truncatedDetails = - task.details.length > 400 - ? task.details.substring(0, 400) + '... (truncated)' - : task.details; - contextTasks += `Implementation Details: ${truncatedDetails}\n`; - } - } - } - - // Add a concise view of the task dependency structure - contextTasks += '\n\nSummary of task dependencies in the project:'; - - // Get pending/in-progress tasks that might be most relevant based on fuzzy search - // Prioritize tasks from our similarity search - const relevantTaskIds = new Set(uniqueDetailedTasks.map((t) => t.id)); - const relevantPendingTasks = data.tasks - .filter( - (t) => - (t.status === 'pending' || t.status === 'in-progress') && - // Either in our relevant set OR has relevant words in title/description - (relevantTaskIds.has(t.id) || - promptWords.some( - (word) => - t.title.toLowerCase().includes(word) || - t.description.toLowerCase().includes(word) - )) - ) - .slice(0, 10); - - for (const task of relevantPendingTasks) { - const depsStr = - task.dependencies && task.dependencies.length > 0 - ? task.dependencies.join(', ') - : 'None'; - contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`; - } - - // Additional analysis of common patterns - const similarPurposeTasks = promptCategory - ? data.tasks.filter( - (t) => - promptCategory.pattern.test(t.title) || - promptCategory.pattern.test(t.description) - ) - : []; - - let commonDeps = []; // Initialize commonDeps - - if (similarPurposeTasks.length > 0) { - contextTasks += `\n\nCommon patterns for ${promptCategory ? promptCategory.label : 'similar'} tasks:`; - - // Collect dependencies from similar purpose tasks - const similarDeps = similarPurposeTasks - .filter((t) => t.dependencies && t.dependencies.length > 0) - .map((t) => t.dependencies) - .flat(); - - // Count frequency of each dependency - const depCounts = {}; - similarDeps.forEach((dep) => { - depCounts[dep] = (depCounts[dep] || 0) + 1; - }); - - // Get most common dependencies for similar tasks - commonDeps = Object.entries(depCounts) - .sort((a, b) => b[1] - a[1]) - .slice(0, 5); - - if (commonDeps.length > 0) { - contextTasks += '\nMost common dependencies for similar tasks:'; - commonDeps.forEach(([depId, count]) => { - const depTask = data.tasks.find((t) => t.id === parseInt(depId)); - if (depTask) { - contextTasks += `\n- Task ${depId} (used by ${count} similar tasks): ${depTask.title}`; - } - }); - } - } - - // Show fuzzy search analysis in CLI mode - if (outputFormat === 'text') { - console.log( - chalk.gray( - ` Fuzzy search across ${data.tasks.length} tasks using full prompt and ${promptWords.length} keywords` - ) - ); - - if (highRelevance.length > 0) { - console.log( - chalk.gray(`\n High relevance matches (score < 0.25):`) - ); - highRelevance.slice(0, 5).forEach((t) => { - console.log( - chalk.yellow(` โ€ข โญ Task ${t.id}: ${truncate(t.title, 50)}`) - ); - }); - } - - if (mediumRelevance.length > 0) { - console.log( - chalk.gray(`\n Medium relevance matches (score < 0.4):`) - ); - mediumRelevance.slice(0, 3).forEach((t) => { - console.log( - chalk.green(` โ€ข Task ${t.id}: ${truncate(t.title, 50)}`) - ); - }); - } - - if (promptCategory && categoryTasks.length > 0) { - console.log( - chalk.gray(`\n Tasks related to ${promptCategory.label}:`) - ); - categoryTasks.forEach((t) => { - console.log( - chalk.magenta(` โ€ข Task ${t.id}: ${truncate(t.title, 50)}`) - ); - }); - } - - // Show dependency patterns - if (commonDeps && commonDeps.length > 0) { - console.log( - chalk.gray(`\n Common dependency patterns for similar tasks:`) - ); - commonDeps.slice(0, 3).forEach(([depId, count]) => { - const depTask = data.tasks.find((t) => t.id === parseInt(depId)); - if (depTask) { - console.log( - chalk.blue( - ` โ€ข Task ${depId} (${count}x): ${truncate(depTask.title, 45)}` - ) - ); - } - }); - } - - // Add information about which tasks will be provided in detail - if (uniqueDetailedTasks.length > 0) { - console.log( - chalk.gray( - `\n Providing detailed context for ${uniqueDetailedTasks.length} most relevant tasks:` - ) - ); - uniqueDetailedTasks.forEach((t) => { - const isHighRelevance = highRelevance.some( - (ht) => ht.id === t.id - ); - const relevanceIndicator = isHighRelevance ? 'โญ ' : ''; - console.log( - chalk.cyan( - ` โ€ข ${relevanceIndicator}Task ${t.id}: ${truncate(t.title, 40)}` - ) - ); - }); - } - - console.log(); // Add spacing - } - } - - // DETERMINE THE ACTUAL COUNT OF DETAILED TASKS BEING USED FOR AI CONTEXT - let actualDetailedTasksCount = 0; - if (numericDependencies.length > 0) { - // In explicit dependency mode, we used 'uniqueDetailedTasks' derived from 'dependentTasks' - // Ensure 'uniqueDetailedTasks' from THAT scope is used or re-evaluate. - // For simplicity, let's assume 'dependentTasks' reflects the detailed tasks. - actualDetailedTasksCount = dependentTasks.length; - } else { - // In fuzzy search mode, 'uniqueDetailedTasks' from THIS scope is correct. - actualDetailedTasksCount = uniqueDetailedTasks - ? uniqueDetailedTasks.length - : 0; - } - - // Add a visual transition to show we're moving to AI generation - only for CLI - if (outputFormat === 'text') { - console.log( - boxen( - chalk.white.bold('AI Task Generation') + - `\n\n${chalk.gray('Analyzing context and generating task details using AI...')}` + - `\n${chalk.cyan('Context size: ')}${chalk.yellow(contextTasks.length.toLocaleString())} characters` + - `\n${chalk.cyan('Dependency detection: ')}${chalk.yellow(numericDependencies.length > 0 ? 'Explicit dependencies' : 'Auto-discovery mode')}` + - `\n${chalk.cyan('Detailed tasks: ')}${chalk.yellow( - numericDependencies.length > 0 - ? dependentTasks.length // Use length of tasks from explicit dependency path - : uniqueDetailedTasks.length // Use length of tasks from fuzzy search path - )}` + - (promptCategory - ? `\n${chalk.cyan('Category detected: ')}${chalk.yellow(promptCategory.label)}` - : ''), - { - padding: { top: 0, bottom: 1, left: 1, right: 1 }, - margin: { top: 1, bottom: 0 }, - borderColor: 'white', - borderStyle: 'round' - } - ) - ); - console.log(); // Add spacing - } - - // System Prompt - Enhanced for dependency awareness - const systemPrompt = - "You are a helpful assistant that creates well-structured tasks for a software development project. Generate a single new task based on the user's description, adhering strictly to the provided JSON schema. Pay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on.\n\n" + - 'When determining dependencies for a new task, follow these principles:\n' + - '1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n' + - '2. Prioritize task dependencies that are semantically related to the functionality being built.\n' + - '3. Consider both direct dependencies (immediately prerequisite) and indirect dependencies.\n' + - '4. Avoid adding unnecessary dependencies - only include tasks that are genuinely prerequisite.\n' + - '5. Consider the current status of tasks - prefer completed tasks as dependencies when possible.\n' + - "6. Pay special attention to foundation tasks (1-5) but don't automatically include them without reason.\n" + - '7. Recent tasks (higher ID numbers) may be more relevant for newer functionality.\n\n' + - 'The dependencies array should contain task IDs (numbers) of prerequisite tasks.\n'; - - // Task Structure Description (for user prompt) - const taskStructureDesc = ` + const { session, mcpLog, projectRoot, commandName, outputType } = context; + const isMCP = !!mcpLog; + + // Create a consistent logFn object regardless of context + const logFn = isMCP + ? mcpLog // Use MCP logger if provided + : { + // Create a wrapper around consoleLog for CLI + info: (...args) => consoleLog("info", ...args), + warn: (...args) => consoleLog("warn", ...args), + error: (...args) => consoleLog("error", ...args), + debug: (...args) => consoleLog("debug", ...args), + success: (...args) => consoleLog("success", ...args), + }; + + const effectivePriority = priority || getDefaultPriority(projectRoot); + + logFn.info( + `Adding new task with prompt: "${prompt}", Priority: ${effectivePriority}, Dependencies: ${dependencies.join(", ") || "None"}, Research: ${useResearch}, ProjectRoot: ${projectRoot}` + ); + + let loadingIndicator = null; + let aiServiceResponse = null; // To store the full response from AI service + + // Create custom reporter that checks for MCP log + const report = (message, level = "info") => { + if (mcpLog) { + mcpLog[level](message); + } else if (outputFormat === "text") { + consoleLog(level, message); + } + }; + + /** + * Recursively builds a dependency graph for a given task + * @param {Array} tasks - All tasks from tasks.json + * @param {number} taskId - ID of the task to analyze + * @param {Set} visited - Set of already visited task IDs + * @param {Map} depthMap - Map of task ID to its depth in the graph + * @param {number} depth - Current depth in the recursion + * @return {Object} Dependency graph data + */ + function buildDependencyGraph( + tasks, + taskId, + visited = new Set(), + depthMap = new Map(), + depth = 0 + ) { + // Skip if we've already visited this task or it doesn't exist + if (visited.has(taskId)) { + return null; + } + + // Find the task + const task = tasks.find((t) => t.id === taskId); + if (!task) { + return null; + } + + // Mark as visited + visited.add(taskId); + + // Update depth if this is a deeper path to this task + if (!depthMap.has(taskId) || depth < depthMap.get(taskId)) { + depthMap.set(taskId, depth); + } + + // Process dependencies + const dependencyData = []; + if (task.dependencies && task.dependencies.length > 0) { + for (const depId of task.dependencies) { + const depData = buildDependencyGraph( + tasks, + depId, + visited, + depthMap, + depth + 1 + ); + if (depData) { + dependencyData.push(depData); + } + } + } + + return { + id: task.id, + title: task.title, + description: task.description, + status: task.status, + dependencies: dependencyData, + }; + } + + try { + // Read the existing tasks + let data = readJSON(tasksPath); + + // If tasks.json doesn't exist or is invalid, create a new one + if (!data || !data.tasks) { + report("tasks.json not found or invalid. Creating a new one.", "info"); + // Create default tasks data structure + data = { + tasks: [], + }; + // Ensure the directory exists and write the new file + writeJSON(tasksPath, data); + report("Created new tasks.json file with empty tasks array.", "info"); + } + + // Find the highest task ID to determine the next ID + const highestId = + data.tasks.length > 0 ? Math.max(...data.tasks.map((t) => t.id)) : 0; + const newTaskId = highestId + 1; + + // Only show UI box for CLI mode + if (outputFormat === "text") { + console.log( + boxen(chalk.white.bold(`Creating New Task #${newTaskId}`), { + padding: 1, + borderColor: "blue", + borderStyle: "round", + margin: { top: 1, bottom: 1 }, + }) + ); + } + + // Validate dependencies before proceeding + const invalidDeps = dependencies.filter((depId) => { + // Ensure depId is parsed as a number for comparison + const numDepId = parseInt(depId, 10); + return isNaN(numDepId) || !data.tasks.some((t) => t.id === numDepId); + }); + + if (invalidDeps.length > 0) { + report( + `The following dependencies do not exist or are invalid: ${invalidDeps.join(", ")}`, + "warn" + ); + report("Removing invalid dependencies...", "info"); + dependencies = dependencies.filter( + (depId) => !invalidDeps.includes(depId) + ); + } + // Ensure dependencies are numbers + const numericDependencies = dependencies.map((dep) => parseInt(dep, 10)); + + // Build dependency graphs for explicitly specified dependencies + const dependencyGraphs = []; + const allRelatedTaskIds = new Set(); + const depthMap = new Map(); + + // First pass: build a complete dependency graph for each specified dependency + for (const depId of numericDependencies) { + const graph = buildDependencyGraph( + data.tasks, + depId, + new Set(), + depthMap + ); + if (graph) { + dependencyGraphs.push(graph); + } + } + + // Second pass: build a set of all related task IDs for flat analysis + for (const [taskId, depth] of depthMap.entries()) { + allRelatedTaskIds.add(taskId); + } + + let taskData; + + // Check if manual task data is provided + if (manualTaskData) { + report("Using manually provided task data", "info"); + taskData = manualTaskData; + report("DEBUG: Taking MANUAL task data path.", "debug"); + + // Basic validation for manual data + if ( + !taskData.title || + typeof taskData.title !== "string" || + !taskData.description || + typeof taskData.description !== "string" + ) { + throw new Error( + "Manual task data must include at least a title and description." + ); + } + } else { + report("DEBUG: Taking AI task generation path.", "debug"); + // --- Refactored AI Interaction --- + report(`Generating task data with AI with prompt:\n${prompt}`, "info"); + + // Create context string for task creation prompt + let contextTasks = ""; + + // Create a dependency map for better understanding of the task relationships + const taskMap = {}; + data.tasks.forEach((t) => { + // For each task, only include id, title, description, and dependencies + taskMap[t.id] = { + id: t.id, + title: t.title, + description: t.description, + dependencies: t.dependencies || [], + status: t.status, + }; + }); + + // CLI-only feedback for the dependency analysis + if (outputFormat === "text") { + console.log( + boxen(chalk.cyan.bold("Task Context Analysis"), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 0, bottom: 0 }, + borderColor: "cyan", + borderStyle: "round", + }) + ); + } + + // Initialize variables that will be used in either branch + let uniqueDetailedTasks = []; + let dependentTasks = []; + let promptCategory = null; + + if (numericDependencies.length > 0) { + // If specific dependencies were provided, focus on them + // Get all tasks that were found in the dependency graph + dependentTasks = Array.from(allRelatedTaskIds) + .map((id) => data.tasks.find((t) => t.id === id)) + .filter(Boolean); + + // Sort by depth in the dependency chain + dependentTasks.sort((a, b) => { + const depthA = depthMap.get(a.id) || 0; + const depthB = depthMap.get(b.id) || 0; + return depthA - depthB; // Lowest depth (root dependencies) first + }); + + // Limit the number of detailed tasks to avoid context explosion + uniqueDetailedTasks = dependentTasks.slice(0, 8); + + contextTasks = `\nThis task relates to a dependency structure with ${dependentTasks.length} related tasks in the chain.\n\nDirect dependencies:`; + const directDeps = data.tasks.filter((t) => + numericDependencies.includes(t.id) + ); + contextTasks += `\n${directDeps.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`).join("\n")}`; + + // Add an overview of indirect dependencies if present + const indirectDeps = dependentTasks.filter( + (t) => !numericDependencies.includes(t.id) + ); + if (indirectDeps.length > 0) { + contextTasks += `\n\nIndirect dependencies (dependencies of dependencies):`; + contextTasks += `\n${indirectDeps + .slice(0, 5) + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join("\n")}`; + if (indirectDeps.length > 5) { + contextTasks += `\n- ... and ${indirectDeps.length - 5} more indirect dependencies`; + } + } + + // Add more details about each dependency, prioritizing direct dependencies + contextTasks += `\n\nDetailed information about dependencies:`; + for (const depTask of uniqueDetailedTasks) { + const depthInfo = depthMap.get(depTask.id) + ? ` (depth: ${depthMap.get(depTask.id)})` + : ""; + const isDirect = numericDependencies.includes(depTask.id) + ? " [DIRECT DEPENDENCY]" + : ""; + + contextTasks += `\n\n------ Task ${depTask.id}${isDirect}${depthInfo}: ${depTask.title} ------\n`; + contextTasks += `Description: ${depTask.description}\n`; + contextTasks += `Status: ${depTask.status || "pending"}\n`; + contextTasks += `Priority: ${depTask.priority || "medium"}\n`; + + // List its dependencies + if (depTask.dependencies && depTask.dependencies.length > 0) { + const depDeps = depTask.dependencies.map((dId) => { + const depDepTask = data.tasks.find((t) => t.id === dId); + return depDepTask + ? `Task ${dId}: ${depDepTask.title}` + : `Task ${dId}`; + }); + contextTasks += `Dependencies: ${depDeps.join(", ")}\n`; + } else { + contextTasks += `Dependencies: None\n`; + } + + // Add implementation details but truncate if too long + if (depTask.details) { + const truncatedDetails = + depTask.details.length > 400 + ? depTask.details.substring(0, 400) + "... (truncated)" + : depTask.details; + contextTasks += `Implementation Details: ${truncatedDetails}\n`; + } + } + + // Add dependency chain visualization + if (dependencyGraphs.length > 0) { + contextTasks += "\n\nDependency Chain Visualization:"; + + // Helper function to format dependency chain as text + function formatDependencyChain( + node, + prefix = "", + isLast = true, + depth = 0 + ) { + if (depth > 3) return ""; // Limit depth to avoid excessive nesting + + const connector = isLast ? "โ””โ”€โ”€ " : "โ”œโ”€โ”€ "; + const childPrefix = isLast ? " " : "โ”‚ "; + + let result = `\n${prefix}${connector}Task ${node.id}: ${node.title}`; + + if (node.dependencies && node.dependencies.length > 0) { + for (let i = 0; i < node.dependencies.length; i++) { + const isLastChild = i === node.dependencies.length - 1; + result += formatDependencyChain( + node.dependencies[i], + prefix + childPrefix, + isLastChild, + depth + 1 + ); + } + } + + return result; + } + + // Format each dependency graph + for (const graph of dependencyGraphs) { + contextTasks += formatDependencyChain(graph); + } + } + + // Show dependency analysis in CLI mode + if (outputFormat === "text") { + if (directDeps.length > 0) { + console.log(chalk.gray(` Explicitly specified dependencies:`)); + directDeps.forEach((t) => { + console.log( + chalk.yellow(` โ€ข Task ${t.id}: ${truncate(t.title, 50)}`) + ); + }); + } + + if (indirectDeps.length > 0) { + console.log( + chalk.gray( + `\n Indirect dependencies (${indirectDeps.length} total):` + ) + ); + indirectDeps.slice(0, 3).forEach((t) => { + const depth = depthMap.get(t.id) || 0; + console.log( + chalk.cyan( + ` โ€ข Task ${t.id} [depth ${depth}]: ${truncate(t.title, 45)}` + ) + ); + }); + if (indirectDeps.length > 3) { + console.log( + chalk.cyan( + ` โ€ข ... and ${indirectDeps.length - 3} more indirect dependencies` + ) + ); + } + } + + // Visualize the dependency chain + if (dependencyGraphs.length > 0) { + console.log(chalk.gray(`\n Dependency chain visualization:`)); + + // Convert dependency graph to ASCII art for terminal + function visualizeDependencyGraph( + node, + prefix = "", + isLast = true, + depth = 0 + ) { + if (depth > 2) return; // Limit depth for display + + const connector = isLast ? "โ””โ”€โ”€ " : "โ”œโ”€โ”€ "; + const childPrefix = isLast ? " " : "โ”‚ "; + + console.log( + chalk.blue( + ` ${prefix}${connector}Task ${node.id}: ${truncate(node.title, 40)}` + ) + ); + + if (node.dependencies && node.dependencies.length > 0) { + for (let i = 0; i < node.dependencies.length; i++) { + const isLastChild = i === node.dependencies.length - 1; + visualizeDependencyGraph( + node.dependencies[i], + prefix + childPrefix, + isLastChild, + depth + 1 + ); + } + } + } + + // Visualize each dependency graph + for (const graph of dependencyGraphs) { + visualizeDependencyGraph(graph); + } + } + + console.log(); // Add spacing + } + } else { + // If no dependencies provided, use Fuse.js to find semantically related tasks + // Create fuzzy search index for all tasks + const searchOptions = { + includeScore: true, // Return match scores + threshold: 0.4, // Lower threshold = stricter matching (range 0-1) + keys: [ + { name: "title", weight: 1.5 }, // Title is most important + { name: "description", weight: 2 }, // Description is very important + { name: "details", weight: 3 }, // Details is most important + // Search dependencies to find tasks that depend on similar things + { name: "dependencyTitles", weight: 0.5 }, + ], + // Sort matches by score (lower is better) + shouldSort: true, + // Allow searching in nested properties + useExtendedSearch: true, + // Return up to 50 matches + limit: 50, + }; + + // Prepare task data with dependencies expanded as titles for better semantic search + const searchableTasks = data.tasks.map((task) => { + // Get titles of this task's dependencies if they exist + const dependencyTitles = + task.dependencies?.length > 0 + ? task.dependencies + .map((depId) => { + const depTask = data.tasks.find((t) => t.id === depId); + return depTask ? depTask.title : ""; + }) + .filter((title) => title) + .join(" ") + : ""; + + return { + ...task, + dependencyTitles, + }; + }); + + // Create search index using Fuse.js + const fuse = new Fuse(searchableTasks, searchOptions); + + // Extract significant words and phrases from the prompt + const promptWords = prompt + .toLowerCase() + .replace(/[^\w\s-]/g, " ") // Replace non-alphanumeric chars with spaces + .split(/\s+/) + .filter((word) => word.length > 3); // Words at least 4 chars + + // Use the user's prompt for fuzzy search + const fuzzyResults = fuse.search(prompt); + + // Also search for each significant word to catch different aspects + let wordResults = []; + for (const word of promptWords) { + if (word.length > 5) { + // Only use significant words + const results = fuse.search(word); + if (results.length > 0) { + wordResults.push(...results); + } + } + } + + // Merge and deduplicate results + const mergedResults = [...fuzzyResults]; + + // Add word results that aren't already in fuzzyResults + for (const wordResult of wordResults) { + if (!mergedResults.some((r) => r.item.id === wordResult.item.id)) { + mergedResults.push(wordResult); + } + } + + // Group search results by relevance + const highRelevance = mergedResults + .filter((result) => result.score < 0.25) + .map((result) => result.item); + + const mediumRelevance = mergedResults + .filter((result) => result.score >= 0.25 && result.score < 0.4) + .map((result) => result.item); + + // Get recent tasks (newest first) + const recentTasks = [...data.tasks] + .sort((a, b) => b.id - a.id) + .slice(0, 5); + + // Combine high relevance, medium relevance, and recent tasks + // Prioritize high relevance first + const allRelevantTasks = [...highRelevance]; + + // Add medium relevance if not already included + for (const task of mediumRelevance) { + if (!allRelevantTasks.some((t) => t.id === task.id)) { + allRelevantTasks.push(task); + } + } + + // Add recent tasks if not already included + for (const task of recentTasks) { + if (!allRelevantTasks.some((t) => t.id === task.id)) { + allRelevantTasks.push(task); + } + } + + // Get top N results for context + const relatedTasks = allRelevantTasks.slice(0, 8); + + // Format basic task overviews + if (relatedTasks.length > 0) { + contextTasks = `\nRelevant tasks identified by semantic similarity:\n${relatedTasks + .map((t, i) => { + const relevanceMarker = i < highRelevance.length ? "โญ " : ""; + return `- ${relevanceMarker}Task ${t.id}: ${t.title} - ${t.description}`; + }) + .join("\n")}`; + } + + if ( + recentTasks.length > 0 && + !contextTasks.includes("Recently created tasks") + ) { + contextTasks += `\n\nRecently created tasks:\n${recentTasks + .filter((t) => !relatedTasks.some((rt) => rt.id === t.id)) + .slice(0, 3) + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join("\n")}`; + } + + // Add detailed information about the most relevant tasks + const allDetailedTasks = [...relatedTasks.slice(0, 25)]; + uniqueDetailedTasks = Array.from( + new Map(allDetailedTasks.map((t) => [t.id, t])).values() + ).slice(0, 20); + + if (uniqueDetailedTasks.length > 0) { + contextTasks += `\n\nDetailed information about relevant tasks:`; + for (const task of uniqueDetailedTasks) { + contextTasks += `\n\n------ Task ${task.id}: ${task.title} ------\n`; + contextTasks += `Description: ${task.description}\n`; + contextTasks += `Status: ${task.status || "pending"}\n`; + contextTasks += `Priority: ${task.priority || "medium"}\n`; + if (task.dependencies && task.dependencies.length > 0) { + // Format dependency list with titles + const depList = task.dependencies.map((depId) => { + const depTask = data.tasks.find((t) => t.id === depId); + return depTask + ? `Task ${depId} (${depTask.title})` + : `Task ${depId}`; + }); + contextTasks += `Dependencies: ${depList.join(", ")}\n`; + } + // Add implementation details but truncate if too long + if (task.details) { + const truncatedDetails = + task.details.length > 400 + ? task.details.substring(0, 400) + "... (truncated)" + : task.details; + contextTasks += `Implementation Details: ${truncatedDetails}\n`; + } + } + } + + // Add a concise view of the task dependency structure + contextTasks += "\n\nSummary of task dependencies in the project:"; + + // Get pending/in-progress tasks that might be most relevant based on fuzzy search + // Prioritize tasks from our similarity search + const relevantTaskIds = new Set(uniqueDetailedTasks.map((t) => t.id)); + const relevantPendingTasks = data.tasks + .filter( + (t) => + (t.status === "pending" || t.status === "in-progress") && + // Either in our relevant set OR has relevant words in title/description + (relevantTaskIds.has(t.id) || + promptWords.some( + (word) => + t.title.toLowerCase().includes(word) || + t.description.toLowerCase().includes(word) + )) + ) + .slice(0, 10); + + for (const task of relevantPendingTasks) { + const depsStr = + task.dependencies && task.dependencies.length > 0 + ? task.dependencies.join(", ") + : "None"; + contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`; + } + + // Additional analysis of common patterns + const similarPurposeTasks = data.tasks.filter((t) => + prompt.toLowerCase().includes(t.title.toLowerCase()) + ); + + let commonDeps = []; // Initialize commonDeps + + if (similarPurposeTasks.length > 0) { + contextTasks += `\n\nCommon patterns for similar tasks:`; + + // Collect dependencies from similar purpose tasks + const similarDeps = similarPurposeTasks + .filter((t) => t.dependencies && t.dependencies.length > 0) + .map((t) => t.dependencies) + .flat(); + + // Count frequency of each dependency + const depCounts = {}; + similarDeps.forEach((dep) => { + depCounts[dep] = (depCounts[dep] || 0) + 1; + }); + + // Get most common dependencies for similar tasks + commonDeps = Object.entries(depCounts) + .sort((a, b) => b[1] - a[1]) + .slice(0, 10); + + if (commonDeps.length > 0) { + contextTasks += "\nMost common dependencies for similar tasks:"; + commonDeps.forEach(([depId, count]) => { + const depTask = data.tasks.find((t) => t.id === parseInt(depId)); + if (depTask) { + contextTasks += `\n- Task ${depId} (used by ${count} similar tasks): ${depTask.title}`; + } + }); + } + } + + // Show fuzzy search analysis in CLI mode + if (outputFormat === "text") { + console.log( + chalk.gray( + ` Context search across ${data.tasks.length} tasks using full prompt and ${promptWords.length} keywords` + ) + ); + + if (highRelevance.length > 0) { + console.log( + chalk.gray(`\n High relevance matches (score < 0.25):`) + ); + highRelevance.slice(0, 25).forEach((t) => { + console.log( + chalk.yellow(` โ€ข โญ Task ${t.id}: ${truncate(t.title, 50)}`) + ); + }); + } + + if (mediumRelevance.length > 0) { + console.log( + chalk.gray(`\n Medium relevance matches (score < 0.4):`) + ); + mediumRelevance.slice(0, 10).forEach((t) => { + console.log( + chalk.green(` โ€ข Task ${t.id}: ${truncate(t.title, 50)}`) + ); + }); + } + + // Show dependency patterns + if (commonDeps && commonDeps.length > 0) { + console.log( + chalk.gray(`\n Common dependency patterns for similar tasks:`) + ); + commonDeps.slice(0, 3).forEach(([depId, count]) => { + const depTask = data.tasks.find((t) => t.id === parseInt(depId)); + if (depTask) { + console.log( + chalk.blue( + ` โ€ข Task ${depId} (${count}x): ${truncate(depTask.title, 45)}` + ) + ); + } + }); + } + + // Add information about which tasks will be provided in detail + if (uniqueDetailedTasks.length > 0) { + console.log( + chalk.gray( + `\n Providing detailed context for ${uniqueDetailedTasks.length} most relevant tasks:` + ) + ); + uniqueDetailedTasks.forEach((t) => { + const isHighRelevance = highRelevance.some( + (ht) => ht.id === t.id + ); + const relevanceIndicator = isHighRelevance ? "โญ " : ""; + console.log( + chalk.cyan( + ` โ€ข ${relevanceIndicator}Task ${t.id}: ${truncate(t.title, 40)}` + ) + ); + }); + } + + console.log(); // Add spacing + } + } + + // DETERMINE THE ACTUAL COUNT OF DETAILED TASKS BEING USED FOR AI CONTEXT + let actualDetailedTasksCount = 0; + if (numericDependencies.length > 0) { + // In explicit dependency mode, we used 'uniqueDetailedTasks' derived from 'dependentTasks' + // Ensure 'uniqueDetailedTasks' from THAT scope is used or re-evaluate. + // For simplicity, let's assume 'dependentTasks' reflects the detailed tasks. + actualDetailedTasksCount = dependentTasks.length; + } else { + // In fuzzy search mode, 'uniqueDetailedTasks' from THIS scope is correct. + actualDetailedTasksCount = uniqueDetailedTasks + ? uniqueDetailedTasks.length + : 0; + } + + // Add a visual transition to show we're moving to AI generation - only for CLI + if (outputFormat === "text") { + console.log( + boxen( + chalk.white.bold("AI Task Generation") + + `\n\n${chalk.gray("Analyzing context and generating task details using AI...")}` + + `\n${chalk.cyan("Context size: ")}${chalk.yellow(contextTasks.length.toLocaleString())} characters` + + `\n${chalk.cyan("Dependency detection: ")}${chalk.yellow(numericDependencies.length > 0 ? "Explicit dependencies" : "Auto-discovery mode")}` + + `\n${chalk.cyan("Detailed tasks: ")}${chalk.yellow( + numericDependencies.length > 0 + ? dependentTasks.length // Use length of tasks from explicit dependency path + : uniqueDetailedTasks.length // Use length of tasks from fuzzy search path + )}`, + { + padding: { top: 0, bottom: 1, left: 1, right: 1 }, + margin: { top: 1, bottom: 0 }, + borderColor: "white", + borderStyle: "round", + } + ) + ); + console.log(); // Add spacing + } + + // System Prompt - Enhanced for dependency awareness + const systemPrompt = + "You are a helpful assistant that creates well-structured tasks for a software development project. Generate a single new task based on the user's description, adhering strictly to the provided JSON schema. Pay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on.\n\n" + + "When determining dependencies for a new task, follow these principles:\n" + + "1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n" + + "2. Prioritize task dependencies that are semantically related to the functionality being built.\n" + + "3. Consider both direct dependencies (immediately prerequisite) and indirect dependencies.\n" + + "4. Avoid adding unnecessary dependencies - only include tasks that are genuinely prerequisite.\n" + + "5. Consider the current status of tasks - prefer completed tasks as dependencies when possible.\n" + + "6. Pay special attention to foundation tasks (1-5) but don't automatically include them without reason.\n" + + "7. Recent tasks (higher ID numbers) may be more relevant for newer functionality.\n\n" + + "The dependencies array should contain task IDs (numbers) of prerequisite tasks.\n"; + + // Task Structure Description (for user prompt) + const taskStructureDesc = ` { "title": "Task title goes here", "description": "A concise one or two sentence description of what the task involves", @@ -903,22 +852,22 @@ async function addTask( } `; - // Add any manually provided details to the prompt for context - let contextFromArgs = ''; - if (manualTaskData?.title) - contextFromArgs += `\n- Suggested Title: "${manualTaskData.title}"`; - if (manualTaskData?.description) - contextFromArgs += `\n- Suggested Description: "${manualTaskData.description}"`; - if (manualTaskData?.details) - contextFromArgs += `\n- Additional Details Context: "${manualTaskData.details}"`; - if (manualTaskData?.testStrategy) - contextFromArgs += `\n- Additional Test Strategy Context: "${manualTaskData.testStrategy}"`; + // Add any manually provided details to the prompt for context + let contextFromArgs = ""; + if (manualTaskData?.title) + contextFromArgs += `\n- Suggested Title: "${manualTaskData.title}"`; + if (manualTaskData?.description) + contextFromArgs += `\n- Suggested Description: "${manualTaskData.description}"`; + if (manualTaskData?.details) + contextFromArgs += `\n- Additional Details Context: "${manualTaskData.details}"`; + if (manualTaskData?.testStrategy) + contextFromArgs += `\n- Additional Test Strategy Context: "${manualTaskData.testStrategy}"`; - // User Prompt - const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project. + // User Prompt + const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project. ${contextTasks} - ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ""} Based on the information about existing tasks provided above, include appropriate dependencies in the "dependencies" array. Only include task IDs that this new task directly depends on. @@ -928,281 +877,297 @@ async function addTask( Make sure the details and test strategy are comprehensive and specific. DO NOT include the task ID in the title. `; - // Start the loading indicator - only for text mode - if (outputFormat === 'text') { - loadingIndicator = startLoadingIndicator( - `Generating new task with ${useResearch ? 'Research' : 'Main'} AI...\n` - ); - } + // Start the loading indicator - only for text mode + if (outputFormat === "text") { + loadingIndicator = startLoadingIndicator( + `Generating new task with ${useResearch ? "Research" : "Main"} AI... \n` + ); + } - try { - const serviceRole = useResearch ? 'research' : 'main'; - report('DEBUG: Calling generateObjectService...', 'debug'); + try { + const serviceRole = useResearch ? "research" : "main"; + report("DEBUG: Calling generateObjectService...", "debug"); - aiServiceResponse = await generateObjectService({ - // Capture the full response - role: serviceRole, - session: session, - projectRoot: projectRoot, - schema: AiTaskDataSchema, - objectName: 'newTaskData', - systemPrompt: systemPrompt, - prompt: userPrompt, - commandName: commandName || 'add-task', // Use passed commandName or default - outputType: outputType || (isMCP ? 'mcp' : 'cli') // Use passed outputType or derive - }); - report('DEBUG: generateObjectService returned successfully.', 'debug'); + aiServiceResponse = await generateObjectService({ + // Capture the full response + role: serviceRole, + session: session, + projectRoot: projectRoot, + schema: AiTaskDataSchema, + objectName: "newTaskData", + systemPrompt: systemPrompt, + prompt: userPrompt, + commandName: commandName || "add-task", // Use passed commandName or default + outputType: outputType || (isMCP ? "mcp" : "cli"), // Use passed outputType or derive + }); + report("DEBUG: generateObjectService returned successfully.", "debug"); - if (!aiServiceResponse || !aiServiceResponse.mainResult) { - throw new Error( - 'AI service did not return the expected object structure.' - ); - } + if (!aiServiceResponse || !aiServiceResponse.mainResult) { + throw new Error( + "AI service did not return the expected object structure." + ); + } - // Prefer mainResult if it looks like a valid task object, otherwise try mainResult.object - if ( - aiServiceResponse.mainResult.title && - aiServiceResponse.mainResult.description - ) { - taskData = aiServiceResponse.mainResult; - } else if ( - aiServiceResponse.mainResult.object && - aiServiceResponse.mainResult.object.title && - aiServiceResponse.mainResult.object.description - ) { - taskData = aiServiceResponse.mainResult.object; - } else { - throw new Error('AI service did not return a valid task object.'); - } + // Prefer mainResult if it looks like a valid task object, otherwise try mainResult.object + if ( + aiServiceResponse.mainResult.title && + aiServiceResponse.mainResult.description + ) { + taskData = aiServiceResponse.mainResult; + } else if ( + aiServiceResponse.mainResult.object && + aiServiceResponse.mainResult.object.title && + aiServiceResponse.mainResult.object.description + ) { + taskData = aiServiceResponse.mainResult.object; + } else { + throw new Error("AI service did not return a valid task object."); + } - report('Successfully generated task data from AI.', 'success'); - } catch (error) { - report( - `DEBUG: generateObjectService caught error: ${error.message}`, - 'debug' - ); - report(`Error generating task with AI: ${error.message}`, 'error'); - if (loadingIndicator) stopLoadingIndicator(loadingIndicator); - throw error; // Re-throw error after logging - } finally { - report('DEBUG: generateObjectService finally block reached.', 'debug'); - if (loadingIndicator) stopLoadingIndicator(loadingIndicator); // Ensure indicator stops - } - // --- End Refactored AI Interaction --- - } + report("Successfully generated task data from AI.", "success"); - // Create the new task object - const newTask = { - id: newTaskId, - title: taskData.title, - description: taskData.description, - details: taskData.details || '', - testStrategy: taskData.testStrategy || '', - status: 'pending', - dependencies: taskData.dependencies?.length - ? taskData.dependencies - : numericDependencies, // Use AI-suggested dependencies if available, fallback to manually specified - priority: effectivePriority, - subtasks: [] // Initialize with empty subtasks array - }; + // Success! Show checkmark + if (loadingIndicator) { + succeedLoadingIndicator( + loadingIndicator, + "Task generated successfully" + ); + loadingIndicator = null; // Clear it + } + } catch (error) { + // Failure! Show X + if (loadingIndicator) { + failLoadingIndicator(loadingIndicator, "AI generation failed"); + loadingIndicator = null; + } + report( + `DEBUG: generateObjectService caught error: ${error.message}`, + "debug" + ); + report(`Error generating task with AI: ${error.message}`, "error"); + throw error; // Re-throw error after logging + } finally { + report("DEBUG: generateObjectService finally block reached.", "debug"); + // Clean up if somehow still running + if (loadingIndicator) { + stopLoadingIndicator(loadingIndicator); + } + } + // --- End Refactored AI Interaction --- + } - // Additional check: validate all dependencies in the AI response - if (taskData.dependencies?.length) { - const allValidDeps = taskData.dependencies.every((depId) => { - const numDepId = parseInt(depId, 10); - return !isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId); - }); + // Create the new task object + const newTask = { + id: newTaskId, + title: taskData.title, + description: taskData.description, + details: taskData.details || "", + testStrategy: taskData.testStrategy || "", + status: "pending", + dependencies: taskData.dependencies?.length + ? taskData.dependencies + : numericDependencies, // Use AI-suggested dependencies if available, fallback to manually specified + priority: effectivePriority, + subtasks: [], // Initialize with empty subtasks array + }; - if (!allValidDeps) { - report( - 'AI suggested invalid dependencies. Filtering them out...', - 'warn' - ); - newTask.dependencies = taskData.dependencies.filter((depId) => { - const numDepId = parseInt(depId, 10); - return !isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId); - }); - } - } + // Additional check: validate all dependencies in the AI response + if (taskData.dependencies?.length) { + const allValidDeps = taskData.dependencies.every((depId) => { + const numDepId = parseInt(depId, 10); + return !isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId); + }); - // Add the task to the tasks array - data.tasks.push(newTask); + if (!allValidDeps) { + report( + "AI suggested invalid dependencies. Filtering them out...", + "warn" + ); + newTask.dependencies = taskData.dependencies.filter((depId) => { + const numDepId = parseInt(depId, 10); + return !isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId); + }); + } + } - report('DEBUG: Writing tasks.json...', 'debug'); - // Write the updated tasks to the file - writeJSON(tasksPath, data); - report('DEBUG: tasks.json written.', 'debug'); + // Add the task to the tasks array + data.tasks.push(newTask); - // Generate markdown task files - report('Generating task files...', 'info'); - report('DEBUG: Calling generateTaskFiles...', 'debug'); - // Pass mcpLog if available to generateTaskFiles - await generateTaskFiles(tasksPath, path.dirname(tasksPath), { mcpLog }); - report('DEBUG: generateTaskFiles finished.', 'debug'); + report("DEBUG: Writing tasks.json...", "debug"); + // Write the updated tasks to the file + writeJSON(tasksPath, data); + report("DEBUG: tasks.json written.", "debug"); - // Show success message - only for text output (CLI) - if (outputFormat === 'text') { - const table = new Table({ - head: [ - chalk.cyan.bold('ID'), - chalk.cyan.bold('Title'), - chalk.cyan.bold('Description') - ], - colWidths: [5, 30, 50] // Adjust widths as needed - }); + // Generate markdown task files + report("Generating task files...", "info"); + report("DEBUG: Calling generateTaskFiles...", "debug"); + // Pass mcpLog if available to generateTaskFiles + await generateTaskFiles(tasksPath, path.dirname(tasksPath), { mcpLog }); + report("DEBUG: generateTaskFiles finished.", "debug"); - table.push([ - newTask.id, - truncate(newTask.title, 27), - truncate(newTask.description, 47) - ]); + // Show success message - only for text output (CLI) + if (outputFormat === "text") { + const table = new Table({ + head: [ + chalk.cyan.bold("ID"), + chalk.cyan.bold("Title"), + chalk.cyan.bold("Description"), + ], + colWidths: [5, 30, 50], // Adjust widths as needed + }); - console.log(chalk.green('โœ… New task created successfully:')); - console.log(table.toString()); + table.push([ + newTask.id, + truncate(newTask.title, 27), + truncate(newTask.description, 47), + ]); - // Helper to get priority color - const getPriorityColor = (p) => { - switch (p?.toLowerCase()) { - case 'high': - return 'red'; - case 'low': - return 'gray'; - case 'medium': - default: - return 'yellow'; - } - }; + console.log(chalk.green("โœ“ New task created successfully:")); + console.log(table.toString()); - // Check if AI added new dependencies that weren't explicitly provided - const aiAddedDeps = newTask.dependencies.filter( - (dep) => !numericDependencies.includes(dep) - ); + // Helper to get priority color + const getPriorityColor = (p) => { + switch (p?.toLowerCase()) { + case "high": + return "red"; + case "low": + return "gray"; + case "medium": + default: + return "yellow"; + } + }; - // Check if AI removed any dependencies that were explicitly provided - const aiRemovedDeps = numericDependencies.filter( - (dep) => !newTask.dependencies.includes(dep) - ); + // Check if AI added new dependencies that weren't explicitly provided + const aiAddedDeps = newTask.dependencies.filter( + (dep) => !numericDependencies.includes(dep) + ); - // Get task titles for dependencies to display - const depTitles = {}; - newTask.dependencies.forEach((dep) => { - const depTask = data.tasks.find((t) => t.id === dep); - if (depTask) { - depTitles[dep] = truncate(depTask.title, 30); - } - }); + // Check if AI removed any dependencies that were explicitly provided + const aiRemovedDeps = numericDependencies.filter( + (dep) => !newTask.dependencies.includes(dep) + ); - // Prepare dependency display string - let dependencyDisplay = ''; - if (newTask.dependencies.length > 0) { - dependencyDisplay = chalk.white('Dependencies:') + '\n'; - newTask.dependencies.forEach((dep) => { - const isAiAdded = aiAddedDeps.includes(dep); - const depType = isAiAdded ? chalk.yellow(' (AI suggested)') : ''; - dependencyDisplay += - chalk.white( - ` - ${dep}: ${depTitles[dep] || 'Unknown task'}${depType}` - ) + '\n'; - }); - } else { - dependencyDisplay = chalk.white('Dependencies: None') + '\n'; - } + // Get task titles for dependencies to display + const depTitles = {}; + newTask.dependencies.forEach((dep) => { + const depTask = data.tasks.find((t) => t.id === dep); + if (depTask) { + depTitles[dep] = truncate(depTask.title, 30); + } + }); - // Add info about removed dependencies if any - if (aiRemovedDeps.length > 0) { - dependencyDisplay += - chalk.gray('\nUser-specified dependencies that were not used:') + - '\n'; - aiRemovedDeps.forEach((dep) => { - const depTask = data.tasks.find((t) => t.id === dep); - const title = depTask ? truncate(depTask.title, 30) : 'Unknown task'; - dependencyDisplay += chalk.gray(` - ${dep}: ${title}`) + '\n'; - }); - } + // Prepare dependency display string + let dependencyDisplay = ""; + if (newTask.dependencies.length > 0) { + dependencyDisplay = chalk.white("Dependencies:") + "\n"; + newTask.dependencies.forEach((dep) => { + const isAiAdded = aiAddedDeps.includes(dep); + const depType = isAiAdded ? chalk.yellow(" (AI suggested)") : ""; + dependencyDisplay += + chalk.white( + ` - ${dep}: ${depTitles[dep] || "Unknown task"}${depType}` + ) + "\n"; + }); + } else { + dependencyDisplay = chalk.white("Dependencies: None") + "\n"; + } - // Add dependency analysis summary - let dependencyAnalysis = ''; - if (aiAddedDeps.length > 0 || aiRemovedDeps.length > 0) { - dependencyAnalysis = - '\n' + chalk.white.bold('Dependency Analysis:') + '\n'; - if (aiAddedDeps.length > 0) { - dependencyAnalysis += - chalk.green( - `AI identified ${aiAddedDeps.length} additional dependencies` - ) + '\n'; - } - if (aiRemovedDeps.length > 0) { - dependencyAnalysis += - chalk.yellow( - `AI excluded ${aiRemovedDeps.length} user-provided dependencies` - ) + '\n'; - } - } + // Add info about removed dependencies if any + if (aiRemovedDeps.length > 0) { + dependencyDisplay += + chalk.gray("\nUser-specified dependencies that were not used:") + + "\n"; + aiRemovedDeps.forEach((dep) => { + const depTask = data.tasks.find((t) => t.id === dep); + const title = depTask ? truncate(depTask.title, 30) : "Unknown task"; + dependencyDisplay += chalk.gray(` - ${dep}: ${title}`) + "\n"; + }); + } - // Show success message box - console.log( - boxen( - chalk.white.bold(`Task ${newTaskId} Created Successfully`) + - '\n\n' + - chalk.white(`Title: ${newTask.title}`) + - '\n' + - chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) + - '\n' + - chalk.white( - `Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}` - ) + - '\n\n' + - dependencyDisplay + - dependencyAnalysis + - '\n' + - chalk.white.bold('Next Steps:') + - '\n' + - chalk.cyan( - `1. Run ${chalk.yellow(`task-master show ${newTaskId}`)} to see complete task details` - ) + - '\n' + - chalk.cyan( - `2. Run ${chalk.yellow(`task-master set-status --id=${newTaskId} --status=in-progress`)} to start working on it` - ) + - '\n' + - chalk.cyan( - `3. Run ${chalk.yellow(`task-master expand --id=${newTaskId}`)} to break it down into subtasks` - ), - { padding: 1, borderColor: 'green', borderStyle: 'round' } - ) - ); + // Add dependency analysis summary + let dependencyAnalysis = ""; + if (aiAddedDeps.length > 0 || aiRemovedDeps.length > 0) { + dependencyAnalysis = + "\n" + chalk.white.bold("Dependency Analysis:") + "\n"; + if (aiAddedDeps.length > 0) { + dependencyAnalysis += + chalk.green( + `AI identified ${aiAddedDeps.length} additional dependencies` + ) + "\n"; + } + if (aiRemovedDeps.length > 0) { + dependencyAnalysis += + chalk.yellow( + `AI excluded ${aiRemovedDeps.length} user-provided dependencies` + ) + "\n"; + } + } - // Display AI Usage Summary if telemetryData is available - if ( - aiServiceResponse && - aiServiceResponse.telemetryData && - (outputType === 'cli' || outputType === 'text') - ) { - displayAiUsageSummary(aiServiceResponse.telemetryData, 'cli'); - } - } + // Show success message box + console.log( + boxen( + chalk.white.bold(`Task ${newTaskId} Created Successfully`) + + "\n\n" + + chalk.white(`Title: ${newTask.title}`) + + "\n" + + chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) + + "\n" + + chalk.white( + `Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}` + ) + + "\n\n" + + dependencyDisplay + + dependencyAnalysis + + "\n" + + chalk.white.bold("Next Steps:") + + "\n" + + chalk.cyan( + `1. Run ${chalk.yellow(`task-master show ${newTaskId}`)} to see complete task details` + ) + + "\n" + + chalk.cyan( + `2. Run ${chalk.yellow(`task-master set-status --id=${newTaskId} --status=in-progress`)} to start working on it` + ) + + "\n" + + chalk.cyan( + `3. Run ${chalk.yellow(`task-master expand --id=${newTaskId}`)} to break it down into subtasks` + ), + { padding: 1, borderColor: "green", borderStyle: "round" } + ) + ); - report( - `DEBUG: Returning new task ID: ${newTaskId} and telemetry.`, - 'debug' - ); - return { - newTaskId: newTaskId, - telemetryData: aiServiceResponse ? aiServiceResponse.telemetryData : null - }; - } catch (error) { - // Stop any loading indicator on error - if (loadingIndicator) { - stopLoadingIndicator(loadingIndicator); - } + // Display AI Usage Summary if telemetryData is available + if ( + aiServiceResponse && + aiServiceResponse.telemetryData && + (outputType === "cli" || outputType === "text") + ) { + displayAiUsageSummary(aiServiceResponse.telemetryData, "cli"); + } + } - report(`Error adding task: ${error.message}`, 'error'); - if (outputFormat === 'text') { - console.error(chalk.red(`Error: ${error.message}`)); - } - // In MCP mode, we let the direct function handler catch and format - throw error; - } + report( + `DEBUG: Returning new task ID: ${newTaskId} and telemetry.`, + "debug" + ); + return { + newTaskId: newTaskId, + telemetryData: aiServiceResponse ? aiServiceResponse.telemetryData : null, + }; + } catch (error) { + // Stop any loading indicator on error + if (loadingIndicator) { + stopLoadingIndicator(loadingIndicator); + } + + report(`Error adding task: ${error.message}`, "error"); + if (outputFormat === "text") { + console.error(chalk.red(`Error: ${error.message}`)); + } + // In MCP mode, we let the direct function handler catch and format + throw error; + } } export default addTask; diff --git a/scripts/modules/task-manager/clear-subtasks.js b/scripts/modules/task-manager/clear-subtasks.js index 9ce01a27..f07dd897 100644 --- a/scripts/modules/task-manager/clear-subtasks.js +++ b/scripts/modules/task-manager/clear-subtasks.js @@ -1,11 +1,11 @@ -import path from 'path'; -import chalk from 'chalk'; -import boxen from 'boxen'; -import Table from 'cli-table3'; +import path from "path"; +import chalk from "chalk"; +import boxen from "boxen"; +import Table from "cli-table3"; -import { log, readJSON, writeJSON, truncate, isSilentMode } from '../utils.js'; -import { displayBanner } from '../ui.js'; -import generateTaskFiles from './generate-task-files.js'; +import { log, readJSON, writeJSON, truncate, isSilentMode } from "../utils.js"; +import { displayBanner } from "../ui.js"; +import generateTaskFiles from "./generate-task-files.js"; /** * Clear subtasks from specified tasks @@ -13,140 +13,138 @@ import generateTaskFiles from './generate-task-files.js'; * @param {string} taskIds - Task IDs to clear subtasks from */ function clearSubtasks(tasksPath, taskIds) { - displayBanner(); + log("info", `Reading tasks from ${tasksPath}...`); + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log("error", "No valid tasks found."); + process.exit(1); + } - log('info', `Reading tasks from ${tasksPath}...`); - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', 'No valid tasks found.'); - process.exit(1); - } + if (!isSilentMode()) { + console.log( + boxen(chalk.white.bold("Clearing Subtasks"), { + padding: 1, + borderColor: "blue", + borderStyle: "round", + margin: { top: 1, bottom: 1 }, + }) + ); + } - if (!isSilentMode()) { - console.log( - boxen(chalk.white.bold('Clearing Subtasks'), { - padding: 1, - borderColor: 'blue', - borderStyle: 'round', - margin: { top: 1, bottom: 1 } - }) - ); - } + // Handle multiple task IDs (comma-separated) + const taskIdArray = taskIds.split(",").map((id) => id.trim()); + let clearedCount = 0; - // Handle multiple task IDs (comma-separated) - const taskIdArray = taskIds.split(',').map((id) => id.trim()); - let clearedCount = 0; + // Create a summary table for the cleared subtasks + const summaryTable = new Table({ + head: [ + chalk.cyan.bold("Task ID"), + chalk.cyan.bold("Task Title"), + chalk.cyan.bold("Subtasks Cleared"), + ], + colWidths: [10, 50, 20], + style: { head: [], border: [] }, + }); - // Create a summary table for the cleared subtasks - const summaryTable = new Table({ - head: [ - chalk.cyan.bold('Task ID'), - chalk.cyan.bold('Task Title'), - chalk.cyan.bold('Subtasks Cleared') - ], - colWidths: [10, 50, 20], - style: { head: [], border: [] } - }); + taskIdArray.forEach((taskId) => { + const id = parseInt(taskId, 10); + if (isNaN(id)) { + log("error", `Invalid task ID: ${taskId}`); + return; + } - taskIdArray.forEach((taskId) => { - const id = parseInt(taskId, 10); - if (isNaN(id)) { - log('error', `Invalid task ID: ${taskId}`); - return; - } + const task = data.tasks.find((t) => t.id === id); + if (!task) { + log("error", `Task ${id} not found`); + return; + } - const task = data.tasks.find((t) => t.id === id); - if (!task) { - log('error', `Task ${id} not found`); - return; - } + if (!task.subtasks || task.subtasks.length === 0) { + log("info", `Task ${id} has no subtasks to clear`); + summaryTable.push([ + id.toString(), + truncate(task.title, 47), + chalk.yellow("No subtasks"), + ]); + return; + } - if (!task.subtasks || task.subtasks.length === 0) { - log('info', `Task ${id} has no subtasks to clear`); - summaryTable.push([ - id.toString(), - truncate(task.title, 47), - chalk.yellow('No subtasks') - ]); - return; - } + const subtaskCount = task.subtasks.length; + task.subtasks = []; + clearedCount++; + log("info", `Cleared ${subtaskCount} subtasks from task ${id}`); - const subtaskCount = task.subtasks.length; - task.subtasks = []; - clearedCount++; - log('info', `Cleared ${subtaskCount} subtasks from task ${id}`); + summaryTable.push([ + id.toString(), + truncate(task.title, 47), + chalk.green(`${subtaskCount} subtasks cleared`), + ]); + }); - summaryTable.push([ - id.toString(), - truncate(task.title, 47), - chalk.green(`${subtaskCount} subtasks cleared`) - ]); - }); + if (clearedCount > 0) { + writeJSON(tasksPath, data); - if (clearedCount > 0) { - writeJSON(tasksPath, data); + // Show summary table + if (!isSilentMode()) { + console.log( + boxen(chalk.white.bold("Subtask Clearing Summary:"), { + padding: { left: 2, right: 2, top: 0, bottom: 0 }, + margin: { top: 1, bottom: 0 }, + borderColor: "blue", + borderStyle: "round", + }) + ); + console.log(summaryTable.toString()); + } - // Show summary table - if (!isSilentMode()) { - console.log( - boxen(chalk.white.bold('Subtask Clearing Summary:'), { - padding: { left: 2, right: 2, top: 0, bottom: 0 }, - margin: { top: 1, bottom: 0 }, - borderColor: 'blue', - borderStyle: 'round' - }) - ); - console.log(summaryTable.toString()); - } + // Regenerate task files to reflect changes + log("info", "Regenerating task files..."); + generateTaskFiles(tasksPath, path.dirname(tasksPath)); - // Regenerate task files to reflect changes - log('info', 'Regenerating task files...'); - generateTaskFiles(tasksPath, path.dirname(tasksPath)); + // Success message + if (!isSilentMode()) { + console.log( + boxen( + chalk.green( + `Successfully cleared subtasks from ${chalk.bold(clearedCount)} task(s)` + ), + { + padding: 1, + borderColor: "green", + borderStyle: "round", + margin: { top: 1 }, + } + ) + ); - // Success message - if (!isSilentMode()) { - console.log( - boxen( - chalk.green( - `Successfully cleared subtasks from ${chalk.bold(clearedCount)} task(s)` - ), - { - padding: 1, - borderColor: 'green', - borderStyle: 'round', - margin: { top: 1 } - } - ) - ); - - // Next steps suggestion - console.log( - boxen( - chalk.white.bold('Next Steps:') + - '\n\n' + - `${chalk.cyan('1.')} Run ${chalk.yellow('task-master expand --id=<id>')} to generate new subtasks\n` + - `${chalk.cyan('2.')} Run ${chalk.yellow('task-master list --with-subtasks')} to verify changes`, - { - padding: 1, - borderColor: 'cyan', - borderStyle: 'round', - margin: { top: 1 } - } - ) - ); - } - } else { - if (!isSilentMode()) { - console.log( - boxen(chalk.yellow('No subtasks were cleared'), { - padding: 1, - borderColor: 'yellow', - borderStyle: 'round', - margin: { top: 1 } - }) - ); - } - } + // Next steps suggestion + console.log( + boxen( + chalk.white.bold("Next Steps:") + + "\n\n" + + `${chalk.cyan("1.")} Run ${chalk.yellow("task-master expand --id=<id>")} to generate new subtasks\n` + + `${chalk.cyan("2.")} Run ${chalk.yellow("task-master list --with-subtasks")} to verify changes`, + { + padding: 1, + borderColor: "cyan", + borderStyle: "round", + margin: { top: 1 }, + } + ) + ); + } + } else { + if (!isSilentMode()) { + console.log( + boxen(chalk.yellow("No subtasks were cleared"), { + padding: 1, + borderColor: "yellow", + borderStyle: "round", + margin: { top: 1 }, + }) + ); + } + } } export default clearSubtasks; diff --git a/scripts/modules/task-manager/list-tasks.js b/scripts/modules/task-manager/list-tasks.js index 1aea9fee..d82768cd 100644 --- a/scripts/modules/task-manager/list-tasks.js +++ b/scripts/modules/task-manager/list-tasks.js @@ -1,23 +1,23 @@ -import chalk from 'chalk'; -import boxen from 'boxen'; -import Table from 'cli-table3'; +import chalk from "chalk"; +import boxen from "boxen"; +import Table from "cli-table3"; import { - log, - readJSON, - truncate, - readComplexityReport, - addComplexityToTask -} from '../utils.js'; -import findNextTask from './find-next-task.js'; + log, + readJSON, + truncate, + readComplexityReport, + addComplexityToTask, +} from "../utils.js"; +import findNextTask from "./find-next-task.js"; import { - displayBanner, - getStatusWithColor, - formatDependenciesWithStatus, - getComplexityWithColor, - createProgressBar -} from '../ui.js'; + displayBanner, + getStatusWithColor, + formatDependenciesWithStatus, + getComplexityWithColor, + createProgressBar, +} from "../ui.js"; /** * List all tasks @@ -29,739 +29,734 @@ import { * @returns {Object} - Task list result for json format */ function listTasks( - tasksPath, - statusFilter, - reportPath = null, - withSubtasks = false, - outputFormat = 'text' + tasksPath, + statusFilter, + reportPath = null, + withSubtasks = false, + outputFormat = "text" ) { - try { - // Only display banner for text output - if (outputFormat === 'text') { - displayBanner(); - } + try { + const data = readJSON(tasksPath); // Reads the whole tasks.json + if (!data || !data.tasks) { + throw new Error(`No valid tasks found in ${tasksPath}`); + } - const data = readJSON(tasksPath); // Reads the whole tasks.json - if (!data || !data.tasks) { - throw new Error(`No valid tasks found in ${tasksPath}`); - } + // Add complexity scores to tasks if report exists + const complexityReport = readComplexityReport(reportPath); + // Apply complexity scores to tasks + if (complexityReport && complexityReport.complexityAnalysis) { + data.tasks.forEach((task) => addComplexityToTask(task, complexityReport)); + } - // Add complexity scores to tasks if report exists - const complexityReport = readComplexityReport(reportPath); - // Apply complexity scores to tasks - if (complexityReport && complexityReport.complexityAnalysis) { - data.tasks.forEach((task) => addComplexityToTask(task, complexityReport)); - } + // Filter tasks by status if specified + const filteredTasks = + statusFilter && statusFilter.toLowerCase() !== "all" // <-- Added check for 'all' + ? data.tasks.filter( + (task) => + task.status && + task.status.toLowerCase() === statusFilter.toLowerCase() + ) + : data.tasks; // Default to all tasks if no filter or filter is 'all' - // Filter tasks by status if specified - const filteredTasks = - statusFilter && statusFilter.toLowerCase() !== 'all' // <-- Added check for 'all' - ? data.tasks.filter( - (task) => - task.status && - task.status.toLowerCase() === statusFilter.toLowerCase() - ) - : data.tasks; // Default to all tasks if no filter or filter is 'all' + // Calculate completion statistics + const totalTasks = data.tasks.length; + const completedTasks = data.tasks.filter( + (task) => task.status === "done" || task.status === "completed" + ).length; + const completionPercentage = + totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0; - // Calculate completion statistics - const totalTasks = data.tasks.length; - const completedTasks = data.tasks.filter( - (task) => task.status === 'done' || task.status === 'completed' - ).length; - const completionPercentage = - totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0; + // Count statuses for tasks + const doneCount = completedTasks; + const inProgressCount = data.tasks.filter( + (task) => task.status === "in-progress" + ).length; + const pendingCount = data.tasks.filter( + (task) => task.status === "pending" + ).length; + const blockedCount = data.tasks.filter( + (task) => task.status === "blocked" + ).length; + const deferredCount = data.tasks.filter( + (task) => task.status === "deferred" + ).length; + const cancelledCount = data.tasks.filter( + (task) => task.status === "cancelled" + ).length; - // Count statuses for tasks - const doneCount = completedTasks; - const inProgressCount = data.tasks.filter( - (task) => task.status === 'in-progress' - ).length; - const pendingCount = data.tasks.filter( - (task) => task.status === 'pending' - ).length; - const blockedCount = data.tasks.filter( - (task) => task.status === 'blocked' - ).length; - const deferredCount = data.tasks.filter( - (task) => task.status === 'deferred' - ).length; - const cancelledCount = data.tasks.filter( - (task) => task.status === 'cancelled' - ).length; + // Count subtasks and their statuses + let totalSubtasks = 0; + let completedSubtasks = 0; + let inProgressSubtasks = 0; + let pendingSubtasks = 0; + let blockedSubtasks = 0; + let deferredSubtasks = 0; + let cancelledSubtasks = 0; - // Count subtasks and their statuses - let totalSubtasks = 0; - let completedSubtasks = 0; - let inProgressSubtasks = 0; - let pendingSubtasks = 0; - let blockedSubtasks = 0; - let deferredSubtasks = 0; - let cancelledSubtasks = 0; + data.tasks.forEach((task) => { + if (task.subtasks && task.subtasks.length > 0) { + totalSubtasks += task.subtasks.length; + completedSubtasks += task.subtasks.filter( + (st) => st.status === "done" || st.status === "completed" + ).length; + inProgressSubtasks += task.subtasks.filter( + (st) => st.status === "in-progress" + ).length; + pendingSubtasks += task.subtasks.filter( + (st) => st.status === "pending" + ).length; + blockedSubtasks += task.subtasks.filter( + (st) => st.status === "blocked" + ).length; + deferredSubtasks += task.subtasks.filter( + (st) => st.status === "deferred" + ).length; + cancelledSubtasks += task.subtasks.filter( + (st) => st.status === "cancelled" + ).length; + } + }); - data.tasks.forEach((task) => { - if (task.subtasks && task.subtasks.length > 0) { - totalSubtasks += task.subtasks.length; - completedSubtasks += task.subtasks.filter( - (st) => st.status === 'done' || st.status === 'completed' - ).length; - inProgressSubtasks += task.subtasks.filter( - (st) => st.status === 'in-progress' - ).length; - pendingSubtasks += task.subtasks.filter( - (st) => st.status === 'pending' - ).length; - blockedSubtasks += task.subtasks.filter( - (st) => st.status === 'blocked' - ).length; - deferredSubtasks += task.subtasks.filter( - (st) => st.status === 'deferred' - ).length; - cancelledSubtasks += task.subtasks.filter( - (st) => st.status === 'cancelled' - ).length; - } - }); + const subtaskCompletionPercentage = + totalSubtasks > 0 ? (completedSubtasks / totalSubtasks) * 100 : 0; - const subtaskCompletionPercentage = - totalSubtasks > 0 ? (completedSubtasks / totalSubtasks) * 100 : 0; + // For JSON output, return structured data + if (outputFormat === "json") { + // *** Modification: Remove 'details' field for JSON output *** + const tasksWithoutDetails = filteredTasks.map((task) => { + // <-- USES filteredTasks! + // Omit 'details' from the parent task + const { details, ...taskRest } = task; - // For JSON output, return structured data - if (outputFormat === 'json') { - // *** Modification: Remove 'details' field for JSON output *** - const tasksWithoutDetails = filteredTasks.map((task) => { - // <-- USES filteredTasks! - // Omit 'details' from the parent task - const { details, ...taskRest } = task; + // If subtasks exist, omit 'details' from them too + if (taskRest.subtasks && Array.isArray(taskRest.subtasks)) { + taskRest.subtasks = taskRest.subtasks.map((subtask) => { + const { details: subtaskDetails, ...subtaskRest } = subtask; + return subtaskRest; + }); + } + return taskRest; + }); + // *** End of Modification *** - // If subtasks exist, omit 'details' from them too - if (taskRest.subtasks && Array.isArray(taskRest.subtasks)) { - taskRest.subtasks = taskRest.subtasks.map((subtask) => { - const { details: subtaskDetails, ...subtaskRest } = subtask; - return subtaskRest; - }); - } - return taskRest; - }); - // *** End of Modification *** + return { + tasks: tasksWithoutDetails, // <--- THIS IS THE ARRAY BEING RETURNED + filter: statusFilter || "all", // Return the actual filter used + stats: { + total: totalTasks, + completed: doneCount, + inProgress: inProgressCount, + pending: pendingCount, + blocked: blockedCount, + deferred: deferredCount, + cancelled: cancelledCount, + completionPercentage, + subtasks: { + total: totalSubtasks, + completed: completedSubtasks, + inProgress: inProgressSubtasks, + pending: pendingSubtasks, + blocked: blockedSubtasks, + deferred: deferredSubtasks, + cancelled: cancelledSubtasks, + completionPercentage: subtaskCompletionPercentage, + }, + }, + }; + } - return { - tasks: tasksWithoutDetails, // <--- THIS IS THE ARRAY BEING RETURNED - filter: statusFilter || 'all', // Return the actual filter used - stats: { - total: totalTasks, - completed: doneCount, - inProgress: inProgressCount, - pending: pendingCount, - blocked: blockedCount, - deferred: deferredCount, - cancelled: cancelledCount, - completionPercentage, - subtasks: { - total: totalSubtasks, - completed: completedSubtasks, - inProgress: inProgressSubtasks, - pending: pendingSubtasks, - blocked: blockedSubtasks, - deferred: deferredSubtasks, - cancelled: cancelledSubtasks, - completionPercentage: subtaskCompletionPercentage - } - } - }; - } + // ... existing code for text output ... - // ... existing code for text output ... + // Calculate status breakdowns as percentages of total + const taskStatusBreakdown = { + "in-progress": totalTasks > 0 ? (inProgressCount / totalTasks) * 100 : 0, + pending: totalTasks > 0 ? (pendingCount / totalTasks) * 100 : 0, + blocked: totalTasks > 0 ? (blockedCount / totalTasks) * 100 : 0, + deferred: totalTasks > 0 ? (deferredCount / totalTasks) * 100 : 0, + cancelled: totalTasks > 0 ? (cancelledCount / totalTasks) * 100 : 0, + }; - // Calculate status breakdowns as percentages of total - const taskStatusBreakdown = { - 'in-progress': totalTasks > 0 ? (inProgressCount / totalTasks) * 100 : 0, - pending: totalTasks > 0 ? (pendingCount / totalTasks) * 100 : 0, - blocked: totalTasks > 0 ? (blockedCount / totalTasks) * 100 : 0, - deferred: totalTasks > 0 ? (deferredCount / totalTasks) * 100 : 0, - cancelled: totalTasks > 0 ? (cancelledCount / totalTasks) * 100 : 0 - }; + const subtaskStatusBreakdown = { + "in-progress": + totalSubtasks > 0 ? (inProgressSubtasks / totalSubtasks) * 100 : 0, + pending: totalSubtasks > 0 ? (pendingSubtasks / totalSubtasks) * 100 : 0, + blocked: totalSubtasks > 0 ? (blockedSubtasks / totalSubtasks) * 100 : 0, + deferred: + totalSubtasks > 0 ? (deferredSubtasks / totalSubtasks) * 100 : 0, + cancelled: + totalSubtasks > 0 ? (cancelledSubtasks / totalSubtasks) * 100 : 0, + }; - const subtaskStatusBreakdown = { - 'in-progress': - totalSubtasks > 0 ? (inProgressSubtasks / totalSubtasks) * 100 : 0, - pending: totalSubtasks > 0 ? (pendingSubtasks / totalSubtasks) * 100 : 0, - blocked: totalSubtasks > 0 ? (blockedSubtasks / totalSubtasks) * 100 : 0, - deferred: - totalSubtasks > 0 ? (deferredSubtasks / totalSubtasks) * 100 : 0, - cancelled: - totalSubtasks > 0 ? (cancelledSubtasks / totalSubtasks) * 100 : 0 - }; + // Create progress bars with status breakdowns + const taskProgressBar = createProgressBar( + completionPercentage, + 30, + taskStatusBreakdown + ); + const subtaskProgressBar = createProgressBar( + subtaskCompletionPercentage, + 30, + subtaskStatusBreakdown + ); - // Create progress bars with status breakdowns - const taskProgressBar = createProgressBar( - completionPercentage, - 30, - taskStatusBreakdown - ); - const subtaskProgressBar = createProgressBar( - subtaskCompletionPercentage, - 30, - subtaskStatusBreakdown - ); + // Calculate dependency statistics + const completedTaskIds = new Set( + data.tasks + .filter((t) => t.status === "done" || t.status === "completed") + .map((t) => t.id) + ); - // Calculate dependency statistics - const completedTaskIds = new Set( - data.tasks - .filter((t) => t.status === 'done' || t.status === 'completed') - .map((t) => t.id) - ); + const tasksWithNoDeps = data.tasks.filter( + (t) => + t.status !== "done" && + t.status !== "completed" && + (!t.dependencies || t.dependencies.length === 0) + ).length; - const tasksWithNoDeps = data.tasks.filter( - (t) => - t.status !== 'done' && - t.status !== 'completed' && - (!t.dependencies || t.dependencies.length === 0) - ).length; + const tasksWithAllDepsSatisfied = data.tasks.filter( + (t) => + t.status !== "done" && + t.status !== "completed" && + t.dependencies && + t.dependencies.length > 0 && + t.dependencies.every((depId) => completedTaskIds.has(depId)) + ).length; - const tasksWithAllDepsSatisfied = data.tasks.filter( - (t) => - t.status !== 'done' && - t.status !== 'completed' && - t.dependencies && - t.dependencies.length > 0 && - t.dependencies.every((depId) => completedTaskIds.has(depId)) - ).length; + const tasksWithUnsatisfiedDeps = data.tasks.filter( + (t) => + t.status !== "done" && + t.status !== "completed" && + t.dependencies && + t.dependencies.length > 0 && + !t.dependencies.every((depId) => completedTaskIds.has(depId)) + ).length; - const tasksWithUnsatisfiedDeps = data.tasks.filter( - (t) => - t.status !== 'done' && - t.status !== 'completed' && - t.dependencies && - t.dependencies.length > 0 && - !t.dependencies.every((depId) => completedTaskIds.has(depId)) - ).length; + // Calculate total tasks ready to work on (no deps + satisfied deps) + const tasksReadyToWork = tasksWithNoDeps + tasksWithAllDepsSatisfied; - // Calculate total tasks ready to work on (no deps + satisfied deps) - const tasksReadyToWork = tasksWithNoDeps + tasksWithAllDepsSatisfied; + // Calculate most depended-on tasks + const dependencyCount = {}; + data.tasks.forEach((task) => { + if (task.dependencies && task.dependencies.length > 0) { + task.dependencies.forEach((depId) => { + dependencyCount[depId] = (dependencyCount[depId] || 0) + 1; + }); + } + }); - // Calculate most depended-on tasks - const dependencyCount = {}; - data.tasks.forEach((task) => { - if (task.dependencies && task.dependencies.length > 0) { - task.dependencies.forEach((depId) => { - dependencyCount[depId] = (dependencyCount[depId] || 0) + 1; - }); - } - }); + // Find the most depended-on task + let mostDependedOnTaskId = null; + let maxDependents = 0; - // Find the most depended-on task - let mostDependedOnTaskId = null; - let maxDependents = 0; + for (const [taskId, count] of Object.entries(dependencyCount)) { + if (count > maxDependents) { + maxDependents = count; + mostDependedOnTaskId = parseInt(taskId); + } + } - for (const [taskId, count] of Object.entries(dependencyCount)) { - if (count > maxDependents) { - maxDependents = count; - mostDependedOnTaskId = parseInt(taskId); - } - } + // Get the most depended-on task + const mostDependedOnTask = + mostDependedOnTaskId !== null + ? data.tasks.find((t) => t.id === mostDependedOnTaskId) + : null; - // Get the most depended-on task - const mostDependedOnTask = - mostDependedOnTaskId !== null - ? data.tasks.find((t) => t.id === mostDependedOnTaskId) - : null; + // Calculate average dependencies per task + const totalDependencies = data.tasks.reduce( + (sum, task) => sum + (task.dependencies ? task.dependencies.length : 0), + 0 + ); + const avgDependenciesPerTask = totalDependencies / data.tasks.length; - // Calculate average dependencies per task - const totalDependencies = data.tasks.reduce( - (sum, task) => sum + (task.dependencies ? task.dependencies.length : 0), - 0 - ); - const avgDependenciesPerTask = totalDependencies / data.tasks.length; + // Find next task to work on, passing the complexity report + const nextItem = findNextTask(data.tasks, complexityReport); - // Find next task to work on, passing the complexity report - const nextItem = findNextTask(data.tasks, complexityReport); + // Get terminal width - more reliable method + let terminalWidth; + try { + // Try to get the actual terminal columns + terminalWidth = process.stdout.columns; + } catch (e) { + // Fallback if columns cannot be determined + log("debug", "Could not determine terminal width, using default"); + } + // Ensure we have a reasonable default if detection fails + terminalWidth = terminalWidth || 80; - // Get terminal width - more reliable method - let terminalWidth; - try { - // Try to get the actual terminal columns - terminalWidth = process.stdout.columns; - } catch (e) { - // Fallback if columns cannot be determined - log('debug', 'Could not determine terminal width, using default'); - } - // Ensure we have a reasonable default if detection fails - terminalWidth = terminalWidth || 80; + // Ensure terminal width is at least a minimum value to prevent layout issues + terminalWidth = Math.max(terminalWidth, 80); - // Ensure terminal width is at least a minimum value to prevent layout issues - terminalWidth = Math.max(terminalWidth, 80); + // Create dashboard content + const projectDashboardContent = + chalk.white.bold("Project Dashboard") + + "\n" + + `Tasks Progress: ${chalk.greenBright(taskProgressBar)} ${completionPercentage.toFixed(0)}%\n` + + `Done: ${chalk.green(doneCount)} In Progress: ${chalk.blue(inProgressCount)} Pending: ${chalk.yellow(pendingCount)} Blocked: ${chalk.red(blockedCount)} Deferred: ${chalk.gray(deferredCount)} Cancelled: ${chalk.gray(cancelledCount)}\n\n` + + `Subtasks Progress: ${chalk.cyan(subtaskProgressBar)} ${subtaskCompletionPercentage.toFixed(0)}%\n` + + `Completed: ${chalk.green(completedSubtasks)}/${totalSubtasks} In Progress: ${chalk.blue(inProgressSubtasks)} Pending: ${chalk.yellow(pendingSubtasks)} Blocked: ${chalk.red(blockedSubtasks)} Deferred: ${chalk.gray(deferredSubtasks)} Cancelled: ${chalk.gray(cancelledSubtasks)}\n\n` + + chalk.cyan.bold("Priority Breakdown:") + + "\n" + + `${chalk.red("โ€ข")} ${chalk.white("High priority:")} ${data.tasks.filter((t) => t.priority === "high").length}\n` + + `${chalk.yellow("โ€ข")} ${chalk.white("Medium priority:")} ${data.tasks.filter((t) => t.priority === "medium").length}\n` + + `${chalk.green("โ€ข")} ${chalk.white("Low priority:")} ${data.tasks.filter((t) => t.priority === "low").length}`; - // Create dashboard content - const projectDashboardContent = - chalk.white.bold('Project Dashboard') + - '\n' + - `Tasks Progress: ${chalk.greenBright(taskProgressBar)} ${completionPercentage.toFixed(0)}%\n` + - `Done: ${chalk.green(doneCount)} In Progress: ${chalk.blue(inProgressCount)} Pending: ${chalk.yellow(pendingCount)} Blocked: ${chalk.red(blockedCount)} Deferred: ${chalk.gray(deferredCount)} Cancelled: ${chalk.gray(cancelledCount)}\n\n` + - `Subtasks Progress: ${chalk.cyan(subtaskProgressBar)} ${subtaskCompletionPercentage.toFixed(0)}%\n` + - `Completed: ${chalk.green(completedSubtasks)}/${totalSubtasks} In Progress: ${chalk.blue(inProgressSubtasks)} Pending: ${chalk.yellow(pendingSubtasks)} Blocked: ${chalk.red(blockedSubtasks)} Deferred: ${chalk.gray(deferredSubtasks)} Cancelled: ${chalk.gray(cancelledSubtasks)}\n\n` + - chalk.cyan.bold('Priority Breakdown:') + - '\n' + - `${chalk.red('โ€ข')} ${chalk.white('High priority:')} ${data.tasks.filter((t) => t.priority === 'high').length}\n` + - `${chalk.yellow('โ€ข')} ${chalk.white('Medium priority:')} ${data.tasks.filter((t) => t.priority === 'medium').length}\n` + - `${chalk.green('โ€ข')} ${chalk.white('Low priority:')} ${data.tasks.filter((t) => t.priority === 'low').length}`; - - const dependencyDashboardContent = - chalk.white.bold('Dependency Status & Next Task') + - '\n' + - chalk.cyan.bold('Dependency Metrics:') + - '\n' + - `${chalk.green('โ€ข')} ${chalk.white('Tasks with no dependencies:')} ${tasksWithNoDeps}\n` + - `${chalk.green('โ€ข')} ${chalk.white('Tasks ready to work on:')} ${tasksReadyToWork}\n` + - `${chalk.yellow('โ€ข')} ${chalk.white('Tasks blocked by dependencies:')} ${tasksWithUnsatisfiedDeps}\n` + - `${chalk.magenta('โ€ข')} ${chalk.white('Most depended-on task:')} ${mostDependedOnTask ? chalk.cyan(`#${mostDependedOnTaskId} (${maxDependents} dependents)`) : chalk.gray('None')}\n` + - `${chalk.blue('โ€ข')} ${chalk.white('Avg dependencies per task:')} ${avgDependenciesPerTask.toFixed(1)}\n\n` + - chalk.cyan.bold('Next Task to Work On:') + - '\n' + - `ID: ${chalk.cyan(nextItem ? nextItem.id : 'N/A')} - ${nextItem ? chalk.white.bold(truncate(nextItem.title, 40)) : chalk.yellow('No task available')} + const dependencyDashboardContent = + chalk.white.bold("Dependency Status & Next Task") + + "\n" + + chalk.cyan.bold("Dependency Metrics:") + + "\n" + + `${chalk.green("โ€ข")} ${chalk.white("Tasks with no dependencies:")} ${tasksWithNoDeps}\n` + + `${chalk.green("โ€ข")} ${chalk.white("Tasks ready to work on:")} ${tasksReadyToWork}\n` + + `${chalk.yellow("โ€ข")} ${chalk.white("Tasks blocked by dependencies:")} ${tasksWithUnsatisfiedDeps}\n` + + `${chalk.magenta("โ€ข")} ${chalk.white("Most depended-on task:")} ${mostDependedOnTask ? chalk.cyan(`#${mostDependedOnTaskId} (${maxDependents} dependents)`) : chalk.gray("None")}\n` + + `${chalk.blue("โ€ข")} ${chalk.white("Avg dependencies per task:")} ${avgDependenciesPerTask.toFixed(1)}\n\n` + + chalk.cyan.bold("Next Task to Work On:") + + "\n" + + `ID: ${chalk.cyan(nextItem ? nextItem.id : "N/A")} - ${nextItem ? chalk.white.bold(truncate(nextItem.title, 40)) : chalk.yellow("No task available")} ` + - `Priority: ${nextItem ? chalk.white(nextItem.priority || 'medium') : ''} Dependencies: ${nextItem ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : ''} + `Priority: ${nextItem ? chalk.white(nextItem.priority || "medium") : ""} Dependencies: ${nextItem ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : ""} ` + - `Complexity: ${nextItem && nextItem.complexityScore ? getComplexityWithColor(nextItem.complexityScore) : chalk.gray('N/A')}`; + `Complexity: ${nextItem && nextItem.complexityScore ? getComplexityWithColor(nextItem.complexityScore) : chalk.gray("N/A")}`; - // Calculate width for side-by-side display - // Box borders, padding take approximately 4 chars on each side - const minDashboardWidth = 50; // Minimum width for dashboard - const minDependencyWidth = 50; // Minimum width for dependency dashboard - const totalMinWidth = minDashboardWidth + minDependencyWidth + 4; // Extra 4 chars for spacing + // Calculate width for side-by-side display + // Box borders, padding take approximately 4 chars on each side + const minDashboardWidth = 50; // Minimum width for dashboard + const minDependencyWidth = 50; // Minimum width for dependency dashboard + const totalMinWidth = minDashboardWidth + minDependencyWidth + 4; // Extra 4 chars for spacing - // If terminal is wide enough, show boxes side by side with responsive widths - if (terminalWidth >= totalMinWidth) { - // Calculate widths proportionally for each box - use exact 50% width each - const availableWidth = terminalWidth; - const halfWidth = Math.floor(availableWidth / 2); + // If terminal is wide enough, show boxes side by side with responsive widths + if (terminalWidth >= totalMinWidth) { + // Calculate widths proportionally for each box - use exact 50% width each + const availableWidth = terminalWidth; + const halfWidth = Math.floor(availableWidth / 2); - // Account for border characters (2 chars on each side) - const boxContentWidth = halfWidth - 4; + // Account for border characters (2 chars on each side) + const boxContentWidth = halfWidth - 4; - // Create boxen options with precise widths - const dashboardBox = boxen(projectDashboardContent, { - padding: 1, - borderColor: 'blue', - borderStyle: 'round', - width: boxContentWidth, - dimBorder: false - }); + // Create boxen options with precise widths + const dashboardBox = boxen(projectDashboardContent, { + padding: 1, + borderColor: "blue", + borderStyle: "round", + width: boxContentWidth, + dimBorder: false, + }); - const dependencyBox = boxen(dependencyDashboardContent, { - padding: 1, - borderColor: 'magenta', - borderStyle: 'round', - width: boxContentWidth, - dimBorder: false - }); + const dependencyBox = boxen(dependencyDashboardContent, { + padding: 1, + borderColor: "magenta", + borderStyle: "round", + width: boxContentWidth, + dimBorder: false, + }); - // Create a better side-by-side layout with exact spacing - const dashboardLines = dashboardBox.split('\n'); - const dependencyLines = dependencyBox.split('\n'); + // Create a better side-by-side layout with exact spacing + const dashboardLines = dashboardBox.split("\n"); + const dependencyLines = dependencyBox.split("\n"); - // Make sure both boxes have the same height - const maxHeight = Math.max(dashboardLines.length, dependencyLines.length); + // Make sure both boxes have the same height + const maxHeight = Math.max(dashboardLines.length, dependencyLines.length); - // For each line of output, pad the dashboard line to exactly halfWidth chars - // This ensures the dependency box starts at exactly the right position - const combinedLines = []; - for (let i = 0; i < maxHeight; i++) { - // Get the dashboard line (or empty string if we've run out of lines) - const dashLine = i < dashboardLines.length ? dashboardLines[i] : ''; - // Get the dependency line (or empty string if we've run out of lines) - const depLine = i < dependencyLines.length ? dependencyLines[i] : ''; + // For each line of output, pad the dashboard line to exactly halfWidth chars + // This ensures the dependency box starts at exactly the right position + const combinedLines = []; + for (let i = 0; i < maxHeight; i++) { + // Get the dashboard line (or empty string if we've run out of lines) + const dashLine = i < dashboardLines.length ? dashboardLines[i] : ""; + // Get the dependency line (or empty string if we've run out of lines) + const depLine = i < dependencyLines.length ? dependencyLines[i] : ""; - // Remove any trailing spaces from dashLine before padding to exact width - const trimmedDashLine = dashLine.trimEnd(); - // Pad the dashboard line to exactly halfWidth chars with no extra spaces - const paddedDashLine = trimmedDashLine.padEnd(halfWidth, ' '); + // Remove any trailing spaces from dashLine before padding to exact width + const trimmedDashLine = dashLine.trimEnd(); + // Pad the dashboard line to exactly halfWidth chars with no extra spaces + const paddedDashLine = trimmedDashLine.padEnd(halfWidth, " "); - // Join the lines with no space in between - combinedLines.push(paddedDashLine + depLine); - } + // Join the lines with no space in between + combinedLines.push(paddedDashLine + depLine); + } - // Join all lines and output - console.log(combinedLines.join('\n')); - } else { - // Terminal too narrow, show boxes stacked vertically - const dashboardBox = boxen(projectDashboardContent, { - padding: 1, - borderColor: 'blue', - borderStyle: 'round', - margin: { top: 0, bottom: 1 } - }); + // Join all lines and output + console.log(combinedLines.join("\n")); + } else { + // Terminal too narrow, show boxes stacked vertically + const dashboardBox = boxen(projectDashboardContent, { + padding: 1, + borderColor: "blue", + borderStyle: "round", + margin: { top: 0, bottom: 1 }, + }); - const dependencyBox = boxen(dependencyDashboardContent, { - padding: 1, - borderColor: 'magenta', - borderStyle: 'round', - margin: { top: 0, bottom: 1 } - }); + const dependencyBox = boxen(dependencyDashboardContent, { + padding: 1, + borderColor: "magenta", + borderStyle: "round", + margin: { top: 0, bottom: 1 }, + }); - // Display stacked vertically - console.log(dashboardBox); - console.log(dependencyBox); - } + // Display stacked vertically + console.log(dashboardBox); + console.log(dependencyBox); + } - if (filteredTasks.length === 0) { - console.log( - boxen( - statusFilter - ? chalk.yellow(`No tasks with status '${statusFilter}' found`) - : chalk.yellow('No tasks found'), - { padding: 1, borderColor: 'yellow', borderStyle: 'round' } - ) - ); - return; - } + if (filteredTasks.length === 0) { + console.log( + boxen( + statusFilter + ? chalk.yellow(`No tasks with status '${statusFilter}' found`) + : chalk.yellow("No tasks found"), + { padding: 1, borderColor: "yellow", borderStyle: "round" } + ) + ); + return; + } - // COMPLETELY REVISED TABLE APPROACH - // Define percentage-based column widths and calculate actual widths - // Adjust percentages based on content type and user requirements + // COMPLETELY REVISED TABLE APPROACH + // Define percentage-based column widths and calculate actual widths + // Adjust percentages based on content type and user requirements - // Adjust ID width if showing subtasks (subtask IDs are longer: e.g., "1.2") - const idWidthPct = withSubtasks ? 10 : 7; + // Adjust ID width if showing subtasks (subtask IDs are longer: e.g., "1.2") + const idWidthPct = withSubtasks ? 10 : 7; - // Calculate max status length to accommodate "in-progress" - const statusWidthPct = 15; + // Calculate max status length to accommodate "in-progress" + const statusWidthPct = 15; - // Increase priority column width as requested - const priorityWidthPct = 12; + // Increase priority column width as requested + const priorityWidthPct = 12; - // Make dependencies column smaller as requested (-20%) - const depsWidthPct = 20; + // Make dependencies column smaller as requested (-20%) + const depsWidthPct = 20; - const complexityWidthPct = 10; + const complexityWidthPct = 10; - // Calculate title/description width as remaining space (+20% from dependencies reduction) - const titleWidthPct = - 100 - - idWidthPct - - statusWidthPct - - priorityWidthPct - - depsWidthPct - - complexityWidthPct; + // Calculate title/description width as remaining space (+20% from dependencies reduction) + const titleWidthPct = + 100 - + idWidthPct - + statusWidthPct - + priorityWidthPct - + depsWidthPct - + complexityWidthPct; - // Allow 10 characters for borders and padding - const availableWidth = terminalWidth - 10; + // Allow 10 characters for borders and padding + const availableWidth = terminalWidth - 10; - // Calculate actual column widths based on percentages - const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); - const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); - const priorityWidth = Math.floor(availableWidth * (priorityWidthPct / 100)); - const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); - const complexityWidth = Math.floor( - availableWidth * (complexityWidthPct / 100) - ); - const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); + // Calculate actual column widths based on percentages + const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); + const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); + const priorityWidth = Math.floor(availableWidth * (priorityWidthPct / 100)); + const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); + const complexityWidth = Math.floor( + availableWidth * (complexityWidthPct / 100) + ); + const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); - // Create a table with correct borders and spacing - const table = new Table({ - head: [ - chalk.cyan.bold('ID'), - chalk.cyan.bold('Title'), - chalk.cyan.bold('Status'), - chalk.cyan.bold('Priority'), - chalk.cyan.bold('Dependencies'), - chalk.cyan.bold('Complexity') - ], - colWidths: [ - idWidth, - titleWidth, - statusWidth, - priorityWidth, - depsWidth, - complexityWidth // Added complexity column width - ], - style: { - head: [], // No special styling for header - border: [], // No special styling for border - compact: false // Use default spacing - }, - wordWrap: true, - wrapOnWordBoundary: true - }); + // Create a table with correct borders and spacing + const table = new Table({ + head: [ + chalk.cyan.bold("ID"), + chalk.cyan.bold("Title"), + chalk.cyan.bold("Status"), + chalk.cyan.bold("Priority"), + chalk.cyan.bold("Dependencies"), + chalk.cyan.bold("Complexity"), + ], + colWidths: [ + idWidth, + titleWidth, + statusWidth, + priorityWidth, + depsWidth, + complexityWidth, // Added complexity column width + ], + style: { + head: [], // No special styling for header + border: [], // No special styling for border + compact: false, // Use default spacing + }, + wordWrap: true, + wrapOnWordBoundary: true, + }); - // Process tasks for the table - filteredTasks.forEach((task) => { - // Format dependencies with status indicators (colored) - let depText = 'None'; - if (task.dependencies && task.dependencies.length > 0) { - // Use the proper formatDependenciesWithStatus function for colored status - depText = formatDependenciesWithStatus( - task.dependencies, - data.tasks, - true, - complexityReport - ); - } else { - depText = chalk.gray('None'); - } + // Process tasks for the table + filteredTasks.forEach((task) => { + // Format dependencies with status indicators (colored) + let depText = "None"; + if (task.dependencies && task.dependencies.length > 0) { + // Use the proper formatDependenciesWithStatus function for colored status + depText = formatDependenciesWithStatus( + task.dependencies, + data.tasks, + true, + complexityReport + ); + } else { + depText = chalk.gray("None"); + } - // Clean up any ANSI codes or confusing characters - const cleanTitle = task.title.replace(/\n/g, ' '); + // Clean up any ANSI codes or confusing characters + const cleanTitle = task.title.replace(/\n/g, " "); - // Get priority color - const priorityColor = - { - high: chalk.red, - medium: chalk.yellow, - low: chalk.gray - }[task.priority || 'medium'] || chalk.white; + // Get priority color + const priorityColor = + { + high: chalk.red, + medium: chalk.yellow, + low: chalk.gray, + }[task.priority || "medium"] || chalk.white; - // Format status - const status = getStatusWithColor(task.status, true); + // Format status + const status = getStatusWithColor(task.status, true); - // Add the row without truncating dependencies - table.push([ - task.id.toString(), - truncate(cleanTitle, titleWidth - 3), - status, - priorityColor(truncate(task.priority || 'medium', priorityWidth - 2)), - depText, - task.complexityScore - ? getComplexityWithColor(task.complexityScore) - : chalk.gray('N/A') - ]); + // Add the row without truncating dependencies + table.push([ + task.id.toString(), + truncate(cleanTitle, titleWidth - 3), + status, + priorityColor(truncate(task.priority || "medium", priorityWidth - 2)), + depText, + task.complexityScore + ? getComplexityWithColor(task.complexityScore) + : chalk.gray("N/A"), + ]); - // Add subtasks if requested - if (withSubtasks && task.subtasks && task.subtasks.length > 0) { - task.subtasks.forEach((subtask) => { - // Format subtask dependencies with status indicators - let subtaskDepText = 'None'; - if (subtask.dependencies && subtask.dependencies.length > 0) { - // Handle both subtask-to-subtask and subtask-to-task dependencies - const formattedDeps = subtask.dependencies - .map((depId) => { - // Check if it's a dependency on another subtask - if (typeof depId === 'number' && depId < 100) { - const foundSubtask = task.subtasks.find( - (st) => st.id === depId - ); - if (foundSubtask) { - const isDone = - foundSubtask.status === 'done' || - foundSubtask.status === 'completed'; - const isInProgress = foundSubtask.status === 'in-progress'; + // Add subtasks if requested + if (withSubtasks && task.subtasks && task.subtasks.length > 0) { + task.subtasks.forEach((subtask) => { + // Format subtask dependencies with status indicators + let subtaskDepText = "None"; + if (subtask.dependencies && subtask.dependencies.length > 0) { + // Handle both subtask-to-subtask and subtask-to-task dependencies + const formattedDeps = subtask.dependencies + .map((depId) => { + // Check if it's a dependency on another subtask + if (typeof depId === "number" && depId < 100) { + const foundSubtask = task.subtasks.find( + (st) => st.id === depId + ); + if (foundSubtask) { + const isDone = + foundSubtask.status === "done" || + foundSubtask.status === "completed"; + const isInProgress = foundSubtask.status === "in-progress"; - // Use consistent color formatting instead of emojis - if (isDone) { - return chalk.green.bold(`${task.id}.${depId}`); - } else if (isInProgress) { - return chalk.hex('#FFA500').bold(`${task.id}.${depId}`); - } else { - return chalk.red.bold(`${task.id}.${depId}`); - } - } - } - // Default to regular task dependency - const depTask = data.tasks.find((t) => t.id === depId); - if (depTask) { - // Add complexity to depTask before checking status - addComplexityToTask(depTask, complexityReport); - const isDone = - depTask.status === 'done' || depTask.status === 'completed'; - const isInProgress = depTask.status === 'in-progress'; - // Use the same color scheme as in formatDependenciesWithStatus - if (isDone) { - return chalk.green.bold(`${depId}`); - } else if (isInProgress) { - return chalk.hex('#FFA500').bold(`${depId}`); - } else { - return chalk.red.bold(`${depId}`); - } - } - return chalk.cyan(depId.toString()); - }) - .join(', '); + // Use consistent color formatting instead of emojis + if (isDone) { + return chalk.green.bold(`${task.id}.${depId}`); + } else if (isInProgress) { + return chalk.hex("#FFA500").bold(`${task.id}.${depId}`); + } else { + return chalk.red.bold(`${task.id}.${depId}`); + } + } + } + // Default to regular task dependency + const depTask = data.tasks.find((t) => t.id === depId); + if (depTask) { + // Add complexity to depTask before checking status + addComplexityToTask(depTask, complexityReport); + const isDone = + depTask.status === "done" || depTask.status === "completed"; + const isInProgress = depTask.status === "in-progress"; + // Use the same color scheme as in formatDependenciesWithStatus + if (isDone) { + return chalk.green.bold(`${depId}`); + } else if (isInProgress) { + return chalk.hex("#FFA500").bold(`${depId}`); + } else { + return chalk.red.bold(`${depId}`); + } + } + return chalk.cyan(depId.toString()); + }) + .join(", "); - subtaskDepText = formattedDeps || chalk.gray('None'); - } + subtaskDepText = formattedDeps || chalk.gray("None"); + } - // Add the subtask row without truncating dependencies - table.push([ - `${task.id}.${subtask.id}`, - chalk.dim(`โ””โ”€ ${truncate(subtask.title, titleWidth - 5)}`), - getStatusWithColor(subtask.status, true), - chalk.dim('-'), - subtaskDepText, - subtask.complexityScore - ? chalk.gray(`${subtask.complexityScore}`) - : chalk.gray('N/A') - ]); - }); - } - }); + // Add the subtask row without truncating dependencies + table.push([ + `${task.id}.${subtask.id}`, + chalk.dim(`โ””โ”€ ${truncate(subtask.title, titleWidth - 5)}`), + getStatusWithColor(subtask.status, true), + chalk.dim("-"), + subtaskDepText, + subtask.complexityScore + ? chalk.gray(`${subtask.complexityScore}`) + : chalk.gray("N/A"), + ]); + }); + } + }); - // Ensure we output the table even if it had to wrap - try { - console.log(table.toString()); - } catch (err) { - log('error', `Error rendering table: ${err.message}`); + // Ensure we output the table even if it had to wrap + try { + console.log(table.toString()); + } catch (err) { + log("error", `Error rendering table: ${err.message}`); - // Fall back to simpler output - console.log( - chalk.yellow( - '\nFalling back to simple task list due to terminal width constraints:' - ) - ); - filteredTasks.forEach((task) => { - console.log( - `${chalk.cyan(task.id)}: ${chalk.white(task.title)} - ${getStatusWithColor(task.status)}` - ); - }); - } + // Fall back to simpler output + console.log( + chalk.yellow( + "\nFalling back to simple task list due to terminal width constraints:" + ) + ); + filteredTasks.forEach((task) => { + console.log( + `${chalk.cyan(task.id)}: ${chalk.white(task.title)} - ${getStatusWithColor(task.status)}` + ); + }); + } - // Show filter info if applied - if (statusFilter) { - console.log(chalk.yellow(`\nFiltered by status: ${statusFilter}`)); - console.log( - chalk.yellow(`Showing ${filteredTasks.length} of ${totalTasks} tasks`) - ); - } + // Show filter info if applied + if (statusFilter) { + console.log(chalk.yellow(`\nFiltered by status: ${statusFilter}`)); + console.log( + chalk.yellow(`Showing ${filteredTasks.length} of ${totalTasks} tasks`) + ); + } - // Define priority colors - const priorityColors = { - high: chalk.red.bold, - medium: chalk.yellow, - low: chalk.gray - }; + // Define priority colors + const priorityColors = { + high: chalk.red.bold, + medium: chalk.yellow, + low: chalk.gray, + }; - // Show next task box in a prominent color - if (nextItem) { - // Prepare subtasks section if they exist (Only tasks have .subtasks property) - let subtasksSection = ''; - // Check if the nextItem is a top-level task before looking for subtasks - const parentTaskForSubtasks = data.tasks.find( - (t) => String(t.id) === String(nextItem.id) - ); // Find the original task object - if ( - parentTaskForSubtasks && - parentTaskForSubtasks.subtasks && - parentTaskForSubtasks.subtasks.length > 0 - ) { - subtasksSection = `\n\n${chalk.white.bold('Subtasks:')}\n`; - subtasksSection += parentTaskForSubtasks.subtasks - .map((subtask) => { - // Add complexity to subtask before display - addComplexityToTask(subtask, complexityReport); - // Using a more simplified format for subtask status display - const status = subtask.status || 'pending'; - const statusColors = { - done: chalk.green, - completed: chalk.green, - pending: chalk.yellow, - 'in-progress': chalk.blue, - deferred: chalk.gray, - blocked: chalk.red, - cancelled: chalk.gray - }; - const statusColor = - statusColors[status.toLowerCase()] || chalk.white; - // Ensure subtask ID is displayed correctly using parent ID from the original task object - return `${chalk.cyan(`${parentTaskForSubtasks.id}.${subtask.id}`)} [${statusColor(status)}] ${subtask.title}`; - }) - .join('\n'); - } + // Show next task box in a prominent color + if (nextItem) { + // Prepare subtasks section if they exist (Only tasks have .subtasks property) + let subtasksSection = ""; + // Check if the nextItem is a top-level task before looking for subtasks + const parentTaskForSubtasks = data.tasks.find( + (t) => String(t.id) === String(nextItem.id) + ); // Find the original task object + if ( + parentTaskForSubtasks && + parentTaskForSubtasks.subtasks && + parentTaskForSubtasks.subtasks.length > 0 + ) { + subtasksSection = `\n\n${chalk.white.bold("Subtasks:")}\n`; + subtasksSection += parentTaskForSubtasks.subtasks + .map((subtask) => { + // Add complexity to subtask before display + addComplexityToTask(subtask, complexityReport); + // Using a more simplified format for subtask status display + const status = subtask.status || "pending"; + const statusColors = { + done: chalk.green, + completed: chalk.green, + pending: chalk.yellow, + "in-progress": chalk.blue, + deferred: chalk.gray, + blocked: chalk.red, + cancelled: chalk.gray, + }; + const statusColor = + statusColors[status.toLowerCase()] || chalk.white; + // Ensure subtask ID is displayed correctly using parent ID from the original task object + return `${chalk.cyan(`${parentTaskForSubtasks.id}.${subtask.id}`)} [${statusColor(status)}] ${subtask.title}`; + }) + .join("\n"); + } - console.log( - boxen( - chalk.hex('#FF8800').bold( - // Use nextItem.id and nextItem.title - `๐Ÿ”ฅ Next Task to Work On: #${nextItem.id} - ${nextItem.title}` - ) + - '\n\n' + - // Use nextItem.priority, nextItem.status, nextItem.dependencies - `${chalk.white('Priority:')} ${priorityColors[nextItem.priority || 'medium'](nextItem.priority || 'medium')} ${chalk.white('Status:')} ${getStatusWithColor(nextItem.status, true)}\n` + - `${chalk.white('Dependencies:')} ${nextItem.dependencies && nextItem.dependencies.length > 0 ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : chalk.gray('None')}\n\n` + - // Use nextTask.description (Note: findNextTask doesn't return description, need to fetch original task/subtask for this) - // *** Fetching original item for description and details *** - `${chalk.white('Description:')} ${getWorkItemDescription(nextItem, data.tasks)}` + - subtasksSection + // <-- Subtasks are handled above now - '\n\n' + - // Use nextItem.id - `${chalk.cyan('Start working:')} ${chalk.yellow(`task-master set-status --id=${nextItem.id} --status=in-progress`)}\n` + - // Use nextItem.id - `${chalk.cyan('View details:')} ${chalk.yellow(`task-master show ${nextItem.id}`)}`, - { - padding: { left: 2, right: 2, top: 1, bottom: 1 }, - borderColor: '#FF8800', - borderStyle: 'round', - margin: { top: 1, bottom: 1 }, - title: 'โšก RECOMMENDED NEXT TASK โšก', - titleAlignment: 'center', - width: terminalWidth - 4, - fullscreen: false - } - ) - ); - } else { - console.log( - boxen( - chalk.hex('#FF8800').bold('No eligible next task found') + - '\n\n' + - 'All pending tasks have dependencies that are not yet completed, or all tasks are done.', - { - padding: 1, - borderColor: '#FF8800', - borderStyle: 'round', - margin: { top: 1, bottom: 1 }, - title: 'โšก NEXT TASK โšก', - titleAlignment: 'center', - width: terminalWidth - 4 // Use full terminal width minus a small margin - } - ) - ); - } + console.log( + boxen( + chalk.hex("#FF8800").bold( + // Use nextItem.id and nextItem.title + `๐Ÿ”ฅ Next Task to Work On: #${nextItem.id} - ${nextItem.title}` + ) + + "\n\n" + + // Use nextItem.priority, nextItem.status, nextItem.dependencies + `${chalk.white("Priority:")} ${priorityColors[nextItem.priority || "medium"](nextItem.priority || "medium")} ${chalk.white("Status:")} ${getStatusWithColor(nextItem.status, true)}\n` + + `${chalk.white("Dependencies:")} ${nextItem.dependencies && nextItem.dependencies.length > 0 ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : chalk.gray("None")}\n\n` + + // Use nextTask.description (Note: findNextTask doesn't return description, need to fetch original task/subtask for this) + // *** Fetching original item for description and details *** + `${chalk.white("Description:")} ${getWorkItemDescription(nextItem, data.tasks)}` + + subtasksSection + // <-- Subtasks are handled above now + "\n\n" + + // Use nextItem.id + `${chalk.cyan("Start working:")} ${chalk.yellow(`task-master set-status --id=${nextItem.id} --status=in-progress`)}\n` + + // Use nextItem.id + `${chalk.cyan("View details:")} ${chalk.yellow(`task-master show ${nextItem.id}`)}`, + { + padding: { left: 2, right: 2, top: 1, bottom: 1 }, + borderColor: "#FF8800", + borderStyle: "round", + margin: { top: 1, bottom: 1 }, + title: "โšก RECOMMENDED NEXT TASK โšก", + titleAlignment: "center", + width: terminalWidth - 4, + fullscreen: false, + } + ) + ); + } else { + console.log( + boxen( + chalk.hex("#FF8800").bold("No eligible next task found") + + "\n\n" + + "All pending tasks have dependencies that are not yet completed, or all tasks are done.", + { + padding: 1, + borderColor: "#FF8800", + borderStyle: "round", + margin: { top: 1, bottom: 1 }, + title: "โšก NEXT TASK โšก", + titleAlignment: "center", + width: terminalWidth - 4, // Use full terminal width minus a small margin + } + ) + ); + } - // Show next steps - console.log( - boxen( - chalk.white.bold('Suggested Next Steps:') + - '\n\n' + - `${chalk.cyan('1.')} Run ${chalk.yellow('task-master next')} to see what to work on next\n` + - `${chalk.cyan('2.')} Run ${chalk.yellow('task-master expand --id=<id>')} to break down a task into subtasks\n` + - `${chalk.cyan('3.')} Run ${chalk.yellow('task-master set-status --id=<id> --status=done')} to mark a task as complete`, - { - padding: 1, - borderColor: 'gray', - borderStyle: 'round', - margin: { top: 1 } - } - ) - ); - } catch (error) { - log('error', `Error listing tasks: ${error.message}`); + // Show next steps + console.log( + boxen( + chalk.white.bold("Suggested Next Steps:") + + "\n\n" + + `${chalk.cyan("1.")} Run ${chalk.yellow("task-master next")} to see what to work on next\n` + + `${chalk.cyan("2.")} Run ${chalk.yellow("task-master expand --id=<id>")} to break down a task into subtasks\n` + + `${chalk.cyan("3.")} Run ${chalk.yellow("task-master set-status --id=<id> --status=done")} to mark a task as complete`, + { + padding: 1, + borderColor: "gray", + borderStyle: "round", + margin: { top: 1 }, + } + ) + ); + } catch (error) { + log("error", `Error listing tasks: ${error.message}`); - if (outputFormat === 'json') { - // Return structured error for JSON output - throw { - code: 'TASK_LIST_ERROR', - message: error.message, - details: error.stack - }; - } + if (outputFormat === "json") { + // Return structured error for JSON output + throw { + code: "TASK_LIST_ERROR", + message: error.message, + details: error.stack, + }; + } - console.error(chalk.red(`Error: ${error.message}`)); - process.exit(1); - } + console.error(chalk.red(`Error: ${error.message}`)); + process.exit(1); + } } // *** Helper function to get description for task or subtask *** function getWorkItemDescription(item, allTasks) { - if (!item) return 'N/A'; - if (item.parentId) { - // It's a subtask - const parent = allTasks.find((t) => t.id === item.parentId); - const subtask = parent?.subtasks?.find( - (st) => `${parent.id}.${st.id}` === item.id - ); - return subtask?.description || 'No description available.'; - } else { - // It's a top-level task - const task = allTasks.find((t) => String(t.id) === String(item.id)); - return task?.description || 'No description available.'; - } + if (!item) return "N/A"; + if (item.parentId) { + // It's a subtask + const parent = allTasks.find((t) => t.id === item.parentId); + const subtask = parent?.subtasks?.find( + (st) => `${parent.id}.${st.id}` === item.id + ); + return subtask?.description || "No description available."; + } else { + // It's a top-level task + const task = allTasks.find((t) => String(t.id) === String(item.id)); + return task?.description || "No description available."; + } } export default listTasks; diff --git a/scripts/modules/task-manager/set-task-status.js b/scripts/modules/task-manager/set-task-status.js index 9278fdff..7e66babd 100644 --- a/scripts/modules/task-manager/set-task-status.js +++ b/scripts/modules/task-manager/set-task-status.js @@ -1,17 +1,17 @@ -import path from 'path'; -import chalk from 'chalk'; -import boxen from 'boxen'; +import path from "path"; +import chalk from "chalk"; +import boxen from "boxen"; -import { log, readJSON, writeJSON, findTaskById } from '../utils.js'; -import { displayBanner } from '../ui.js'; -import { validateTaskDependencies } from '../dependency-manager.js'; -import { getDebugFlag } from '../config-manager.js'; -import updateSingleTaskStatus from './update-single-task-status.js'; -import generateTaskFiles from './generate-task-files.js'; +import { log, readJSON, writeJSON, findTaskById } from "../utils.js"; +import { displayBanner } from "../ui.js"; +import { validateTaskDependencies } from "../dependency-manager.js"; +import { getDebugFlag } from "../config-manager.js"; +import updateSingleTaskStatus from "./update-single-task-status.js"; +import generateTaskFiles from "./generate-task-files.js"; import { - isValidTaskStatus, - TASK_STATUS_OPTIONS -} from '../../../src/constants/task-status.js'; + isValidTaskStatus, + TASK_STATUS_OPTIONS, +} from "../../../src/constants/task-status.js"; /** * Set the status of a task @@ -22,102 +22,100 @@ import { * @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode */ async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) { - try { - if (!isValidTaskStatus(newStatus)) { - throw new Error( - `Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}` - ); - } - // Determine if we're in MCP mode by checking for mcpLog - const isMcpMode = !!options?.mcpLog; + try { + if (!isValidTaskStatus(newStatus)) { + throw new Error( + `Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(", ")}` + ); + } + // Determine if we're in MCP mode by checking for mcpLog + const isMcpMode = !!options?.mcpLog; - // Only display UI elements if not in MCP mode - if (!isMcpMode) { - displayBanner(); + // Only display UI elements if not in MCP mode + if (!isMcpMode) { + console.log( + boxen(chalk.white.bold(`Updating Task Status to: ${newStatus}`), { + padding: 1, + borderColor: "blue", + borderStyle: "round", + }) + ); + } - console.log( - boxen(chalk.white.bold(`Updating Task Status to: ${newStatus}`), { - padding: 1, - borderColor: 'blue', - borderStyle: 'round' - }) - ); - } + log("info", `Reading tasks from ${tasksPath}...`); + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + throw new Error(`No valid tasks found in ${tasksPath}`); + } - log('info', `Reading tasks from ${tasksPath}...`); - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - throw new Error(`No valid tasks found in ${tasksPath}`); - } + // Handle multiple task IDs (comma-separated) + const taskIds = taskIdInput.split(",").map((id) => id.trim()); + const updatedTasks = []; - // Handle multiple task IDs (comma-separated) - const taskIds = taskIdInput.split(',').map((id) => id.trim()); - const updatedTasks = []; + // Update each task + for (const id of taskIds) { + await updateSingleTaskStatus(tasksPath, id, newStatus, data, !isMcpMode); + updatedTasks.push(id); + } - // Update each task - for (const id of taskIds) { - await updateSingleTaskStatus(tasksPath, id, newStatus, data, !isMcpMode); - updatedTasks.push(id); - } + // Write the updated tasks to the file + writeJSON(tasksPath, data); - // Write the updated tasks to the file - writeJSON(tasksPath, data); + // Validate dependencies after status update + log("info", "Validating dependencies after status update..."); + validateTaskDependencies(data.tasks); - // Validate dependencies after status update - log('info', 'Validating dependencies after status update...'); - validateTaskDependencies(data.tasks); + // Generate individual task files + log("info", "Regenerating task files..."); + await generateTaskFiles(tasksPath, path.dirname(tasksPath), { + mcpLog: options.mcpLog, + }); - // Generate individual task files - log('info', 'Regenerating task files...'); - await generateTaskFiles(tasksPath, path.dirname(tasksPath), { - mcpLog: options.mcpLog - }); + // Display success message - only in CLI mode + if (!isMcpMode) { + for (const id of updatedTasks) { + const task = findTaskById(data.tasks, id); + const taskName = task ? task.title : id; - // Display success message - only in CLI mode - if (!isMcpMode) { - for (const id of updatedTasks) { - const task = findTaskById(data.tasks, id); - const taskName = task ? task.title : id; + console.log( + boxen( + chalk.white.bold(`Successfully updated task ${id} status:`) + + "\n" + + `From: ${chalk.yellow(task ? task.status : "unknown")}\n` + + `To: ${chalk.green(newStatus)}`, + { padding: 1, borderColor: "green", borderStyle: "round" } + ) + ); + } + } - console.log( - boxen( - chalk.white.bold(`Successfully updated task ${id} status:`) + - '\n' + - `From: ${chalk.yellow(task ? task.status : 'unknown')}\n` + - `To: ${chalk.green(newStatus)}`, - { padding: 1, borderColor: 'green', borderStyle: 'round' } - ) - ); - } - } + // Return success value for programmatic use + return { + success: true, + updatedTasks: updatedTasks.map((id) => ({ + id, + status: newStatus, + })), + }; + } catch (error) { + log("error", `Error setting task status: ${error.message}`); - // Return success value for programmatic use - return { - success: true, - updatedTasks: updatedTasks.map((id) => ({ - id, - status: newStatus - })) - }; - } catch (error) { - log('error', `Error setting task status: ${error.message}`); + // Only show error UI in CLI mode + if (!options?.mcpLog) { + console.error(chalk.red(`Error: ${error.message}`)); - // Only show error UI in CLI mode - if (!options?.mcpLog) { - console.error(chalk.red(`Error: ${error.message}`)); + // Pass session to getDebugFlag + if (getDebugFlag(options?.session)) { + // Use getter + console.error(error); + } - // Pass session to getDebugFlag - if (getDebugFlag(options?.session)) { - // Use getter - console.error(error); - } - - process.exit(1); - } else { - // In MCP mode, throw the error for the caller to handle - throw error; - } - } + process.exit(1); + } else { + // In MCP mode, throw the error for the caller to handle + throw error; + } + } } export default setTaskStatus; diff --git a/scripts/modules/ui.js b/scripts/modules/ui.js index 1118f8c7..9c1307ca 100644 --- a/scripts/modules/ui.js +++ b/scripts/modules/ui.js @@ -40,7 +40,7 @@ const warmGradient = gradient(["#fb8b24", "#e36414", "#9a031e"]); function displayBanner() { if (isSilentMode()) return; - console.clear(); + // console.clear(); // Removing this to avoid clearing the terminal per command const bannerText = figlet.textSync("Task Master", { font: "Standard", horizontalLayout: "default", @@ -78,6 +78,8 @@ function displayBanner() { * @returns {Object} Spinner object */ function startLoadingIndicator(message) { + if (isSilentMode()) return null; + const spinner = ora({ text: message, color: "cyan", @@ -87,15 +89,75 @@ function startLoadingIndicator(message) { } /** - * Stop a loading indicator + * Stop a loading indicator (basic stop, no success/fail indicator) * @param {Object} spinner - Spinner object to stop */ function stopLoadingIndicator(spinner) { - if (spinner && spinner.stop) { + if (spinner && typeof spinner.stop === "function") { spinner.stop(); } } +/** + * Complete a loading indicator with success (shows checkmark) + * @param {Object} spinner - Spinner object to complete + * @param {string} message - Optional success message (defaults to current text) + */ +function succeedLoadingIndicator(spinner, message = null) { + if (spinner && typeof spinner.succeed === "function") { + if (message) { + spinner.succeed(message); + } else { + spinner.succeed(); + } + } +} + +/** + * Complete a loading indicator with failure (shows X) + * @param {Object} spinner - Spinner object to fail + * @param {string} message - Optional failure message (defaults to current text) + */ +function failLoadingIndicator(spinner, message = null) { + if (spinner && typeof spinner.fail === "function") { + if (message) { + spinner.fail(message); + } else { + spinner.fail(); + } + } +} + +/** + * Complete a loading indicator with warning (shows warning symbol) + * @param {Object} spinner - Spinner object to warn + * @param {string} message - Optional warning message (defaults to current text) + */ +function warnLoadingIndicator(spinner, message = null) { + if (spinner && typeof spinner.warn === "function") { + if (message) { + spinner.warn(message); + } else { + spinner.warn(); + } + } +} + +/** + * Complete a loading indicator with info (shows info symbol) + * @param {Object} spinner - Spinner object to complete with info + * @param {string} message - Optional info message (defaults to current text) + */ +function infoLoadingIndicator(spinner, message = null) { + if (spinner && typeof spinner.info === "function") { + if (message) { + spinner.info(message); + } else { + spinner.info(); + } + } +} + /** * Create a colored progress bar * @param {number} percent - The completion percentage @@ -232,14 +294,14 @@ function getStatusWithColor(status, forTable = false) { } const statusConfig = { - done: { color: chalk.green, icon: "โœ…", tableIcon: "โœ“" }, - completed: { color: chalk.green, icon: "โœ…", tableIcon: "โœ“" }, - pending: { color: chalk.yellow, icon: "โฑ๏ธ", tableIcon: "โฑ" }, + done: { color: chalk.green, icon: "โœ“", tableIcon: "โœ“" }, + completed: { color: chalk.green, icon: "โœ“", tableIcon: "โœ“" }, + pending: { color: chalk.yellow, icon: "โ—‹", tableIcon: "โฑ" }, "in-progress": { color: chalk.hex("#FFA500"), icon: "๐Ÿ”„", tableIcon: "โ–บ" }, - deferred: { color: chalk.gray, icon: "โฑ๏ธ", tableIcon: "โฑ" }, - blocked: { color: chalk.red, icon: "โŒ", tableIcon: "โœ—" }, - review: { color: chalk.magenta, icon: "๐Ÿ‘€", tableIcon: "๐Ÿ‘" }, - cancelled: { color: chalk.gray, icon: "โŒ", tableIcon: "โœ—" }, + deferred: { color: chalk.gray, icon: "x", tableIcon: "โฑ" }, + blocked: { color: chalk.red, icon: "!", tableIcon: "โœ—" }, + review: { color: chalk.magenta, icon: "?", tableIcon: "?" }, + cancelled: { color: chalk.gray, icon: "โŒ", tableIcon: "x" }, }; const config = statusConfig[status.toLowerCase()] || { @@ -383,8 +445,6 @@ function formatDependenciesWithStatus( * Display a comprehensive help guide */ function displayHelp() { - displayBanner(); - // Get terminal width - moved to top of function to make it available throughout const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect @@ -767,8 +827,6 @@ function truncateString(str, maxLength) { * @param {string} tasksPath - Path to the tasks.json file */ async function displayNextTask(tasksPath, complexityReportPath = null) { - displayBanner(); - // Read the tasks file const data = readJSON(tasksPath); if (!data || !data.tasks) { @@ -1039,8 +1097,6 @@ async function displayTaskById( complexityReportPath = null, statusFilter = null ) { - displayBanner(); - // Read the tasks file const data = readJSON(tasksPath); if (!data || !data.tasks) { @@ -1495,8 +1551,6 @@ async function displayTaskById( * @param {string} reportPath - Path to the complexity report file */ async function displayComplexityReport(reportPath) { - displayBanner(); - // Check if the report exists if (!fs.existsSync(reportPath)) { console.log( @@ -2094,4 +2148,8 @@ export { displayModelConfiguration, displayAvailableModels, displayAiUsageSummary, + succeedLoadingIndicator, + failLoadingIndicator, + warnLoadingIndicator, + infoLoadingIndicator, }; From 27edbd8f3fe5e2ac200b80e7f27f4c0e74a074d6 Mon Sep 17 00:00:00 2001 From: Eyal Toledano <eyal@microangel.so> Date: Sat, 7 Jun 2025 20:28:28 -0400 Subject: [PATCH 06/10] chore: changeset --- .changeset/four-cups-enter.md | 39 +++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 .changeset/four-cups-enter.md diff --git a/.changeset/four-cups-enter.md b/.changeset/four-cups-enter.md new file mode 100644 index 00000000..8ccc147d --- /dev/null +++ b/.changeset/four-cups-enter.md @@ -0,0 +1,39 @@ +--- +"task-master-ai": patch +--- + +Enhanced add-task fuzzy search intelligence and improved user experience + +**Smarter Task Discovery:** + +- Remove hardcoded category system that always matched "Task management" +- Eliminate arbitrary limits on fuzzy search results (5โ†’25 high relevance, 3โ†’10 medium relevance, 8โ†’20 detailed tasks) +- Improve semantic weighting in Fuse.js search (details=3, description=2, title=1.5) for better relevance +- Generate context-driven task recommendations based on true semantic similarity + +**Enhanced Terminal Experience:** + +- Fix duplicate banner display issue that was "eating" terminal history (closes #553) +- Remove console.clear() and redundant displayBanner() calls from UI functions +- Preserve command history for better development workflow +- Streamline banner display across all commands (list, next, show, set-status, clear-subtasks, dependency commands) + +**Visual Improvements:** + +- Replace emoji complexity indicators with clean filled circle characters (โ—) for professional appearance +- Improve consistency and readability of task complexity display + +**AI Provider Compatibility:** + +- Change generateObject mode from 'tool' to 'auto' for better cross-provider compatibility +- Add qwen3-235n-a22b:free model support (closes #687) +- Add smart warnings for free OpenRouter models with limitations (rate limits, restricted context, no tool_use) + +**Technical Improvements:** + +- Enhanced context generation in add-task to rely on semantic similarity rather than rigid pattern matching +- Improved dependency analysis and common pattern detection +- Better handling of task relationships and relevance scoring +- More intelligent task suggestion algorithms + +The add-task system now provides truly relevant task context based on semantic understanding rather than arbitrary categories and limits, while maintaining a cleaner and more professional terminal experience. From faae0b419d451304957d845b551d36c9c157073e Mon Sep 17 00:00:00 2001 From: Eyal Toledano <eyal@microangel.so> Date: Sat, 7 Jun 2025 20:30:51 -0400 Subject: [PATCH 07/10] chore: passes tests and linting --- .taskmaster/config.json | 64 +- scripts/modules/ai-services-unified.js | 1064 ++--- scripts/modules/dependency-manager.js | 1976 ++++----- scripts/modules/supported-models.json | 850 ++-- scripts/modules/task-manager/add-task.js | 2190 +++++----- .../modules/task-manager/clear-subtasks.js | 252 +- scripts/modules/task-manager/list-tasks.js | 1332 +++--- scripts/modules/task-manager/models.js | 1038 ++--- .../modules/task-manager/set-task-status.js | 186 +- scripts/modules/ui.js | 3568 ++++++++--------- src/ai-providers/base-provider.js | 364 +- .../modules/task-manager/add-task.test.js | 668 +-- tests/unit/ui.test.js | 6 +- 13 files changed, 6781 insertions(+), 6777 deletions(-) diff --git a/.taskmaster/config.json b/.taskmaster/config.json index 442dfc1c..a61d10d5 100644 --- a/.taskmaster/config.json +++ b/.taskmaster/config.json @@ -1,33 +1,33 @@ { - "models": { - "main": { - "provider": "anthropic", - "modelId": "claude-sonnet-4-20250514", - "maxTokens": 50000, - "temperature": 0.2 - }, - "research": { - "provider": "perplexity", - "modelId": "sonar-pro", - "maxTokens": 8700, - "temperature": 0.1 - }, - "fallback": { - "provider": "anthropic", - "modelId": "claude-3-7-sonnet-20250219", - "maxTokens": 128000, - "temperature": 0.2 - } - }, - "global": { - "logLevel": "info", - "debug": false, - "defaultSubtasks": 5, - "defaultPriority": "medium", - "projectName": "Taskmaster", - "ollamaBaseURL": "http://localhost:11434/api", - "bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com", - "userId": "1234567890", - "azureBaseURL": "https://your-endpoint.azure.com/" - } -} \ No newline at end of file + "models": { + "main": { + "provider": "anthropic", + "modelId": "claude-sonnet-4-20250514", + "maxTokens": 50000, + "temperature": 0.2 + }, + "research": { + "provider": "perplexity", + "modelId": "sonar-pro", + "maxTokens": 8700, + "temperature": 0.1 + }, + "fallback": { + "provider": "anthropic", + "modelId": "claude-3-7-sonnet-20250219", + "maxTokens": 128000, + "temperature": 0.2 + } + }, + "global": { + "logLevel": "info", + "debug": false, + "defaultSubtasks": 5, + "defaultPriority": "medium", + "projectName": "Taskmaster", + "ollamaBaseURL": "http://localhost:11434/api", + "bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com", + "userId": "1234567890", + "azureBaseURL": "https://your-endpoint.azure.com/" + } +} diff --git a/scripts/modules/ai-services-unified.js b/scripts/modules/ai-services-unified.js index adcad88d..cd16682a 100644 --- a/scripts/modules/ai-services-unified.js +++ b/scripts/modules/ai-services-unified.js @@ -8,82 +8,82 @@ // --- Core Dependencies --- import { - getMainProvider, - getMainModelId, - getResearchProvider, - getResearchModelId, - getFallbackProvider, - getFallbackModelId, - getParametersForRole, - getUserId, - MODEL_MAP, - getDebugFlag, - getBaseUrlForRole, - isApiKeySet, - getOllamaBaseURL, - getAzureBaseURL, - getBedrockBaseURL, - getVertexProjectId, - getVertexLocation, -} from "./config-manager.js"; -import { log, findProjectRoot, resolveEnvVariable } from "./utils.js"; + getMainProvider, + getMainModelId, + getResearchProvider, + getResearchModelId, + getFallbackProvider, + getFallbackModelId, + getParametersForRole, + getUserId, + MODEL_MAP, + getDebugFlag, + getBaseUrlForRole, + isApiKeySet, + getOllamaBaseURL, + getAzureBaseURL, + getBedrockBaseURL, + getVertexProjectId, + getVertexLocation +} from './config-manager.js'; +import { log, findProjectRoot, resolveEnvVariable } from './utils.js'; // Import provider classes import { - AnthropicAIProvider, - PerplexityAIProvider, - GoogleAIProvider, - OpenAIProvider, - XAIProvider, - OpenRouterAIProvider, - OllamaAIProvider, - BedrockAIProvider, - AzureProvider, - VertexAIProvider, -} from "../../src/ai-providers/index.js"; + AnthropicAIProvider, + PerplexityAIProvider, + GoogleAIProvider, + OpenAIProvider, + XAIProvider, + OpenRouterAIProvider, + OllamaAIProvider, + BedrockAIProvider, + AzureProvider, + VertexAIProvider +} from '../../src/ai-providers/index.js'; // Create provider instances const PROVIDERS = { - anthropic: new AnthropicAIProvider(), - perplexity: new PerplexityAIProvider(), - google: new GoogleAIProvider(), - openai: new OpenAIProvider(), - xai: new XAIProvider(), - openrouter: new OpenRouterAIProvider(), - ollama: new OllamaAIProvider(), - bedrock: new BedrockAIProvider(), - azure: new AzureProvider(), - vertex: new VertexAIProvider(), + anthropic: new AnthropicAIProvider(), + perplexity: new PerplexityAIProvider(), + google: new GoogleAIProvider(), + openai: new OpenAIProvider(), + xai: new XAIProvider(), + openrouter: new OpenRouterAIProvider(), + ollama: new OllamaAIProvider(), + bedrock: new BedrockAIProvider(), + azure: new AzureProvider(), + vertex: new VertexAIProvider() }; // Helper function to get cost for a specific model function _getCostForModel(providerName, modelId) { - if (!MODEL_MAP || !MODEL_MAP[providerName]) { - log( - "warn", - `Provider "${providerName}" not found in MODEL_MAP. Cannot determine cost for model ${modelId}.` - ); - return { inputCost: 0, outputCost: 0, currency: "USD" }; // Default to zero cost - } + if (!MODEL_MAP || !MODEL_MAP[providerName]) { + log( + 'warn', + `Provider "${providerName}" not found in MODEL_MAP. Cannot determine cost for model ${modelId}.` + ); + return { inputCost: 0, outputCost: 0, currency: 'USD' }; // Default to zero cost + } - const modelData = MODEL_MAP[providerName].find((m) => m.id === modelId); + const modelData = MODEL_MAP[providerName].find((m) => m.id === modelId); - if (!modelData || !modelData.cost_per_1m_tokens) { - log( - "debug", - `Cost data not found for model "${modelId}" under provider "${providerName}". Assuming zero cost.` - ); - return { inputCost: 0, outputCost: 0, currency: "USD" }; // Default to zero cost - } + if (!modelData || !modelData.cost_per_1m_tokens) { + log( + 'debug', + `Cost data not found for model "${modelId}" under provider "${providerName}". Assuming zero cost.` + ); + return { inputCost: 0, outputCost: 0, currency: 'USD' }; // Default to zero cost + } - // Ensure currency is part of the returned object, defaulting if not present - const currency = modelData.cost_per_1m_tokens.currency || "USD"; + // Ensure currency is part of the returned object, defaulting if not present + const currency = modelData.cost_per_1m_tokens.currency || 'USD'; - return { - inputCost: modelData.cost_per_1m_tokens.input || 0, - outputCost: modelData.cost_per_1m_tokens.output || 0, - currency: currency, - }; + return { + inputCost: modelData.cost_per_1m_tokens.input || 0, + outputCost: modelData.cost_per_1m_tokens.output || 0, + currency: currency + }; } // --- Configuration for Retries --- @@ -92,16 +92,16 @@ const INITIAL_RETRY_DELAY_MS = 1000; // Helper function to check if an error is retryable function isRetryableError(error) { - const errorMessage = error.message?.toLowerCase() || ""; - return ( - errorMessage.includes("rate limit") || - errorMessage.includes("overloaded") || - errorMessage.includes("service temporarily unavailable") || - errorMessage.includes("timeout") || - errorMessage.includes("network error") || - error.status === 429 || - error.status >= 500 - ); + const errorMessage = error.message?.toLowerCase() || ''; + return ( + errorMessage.includes('rate limit') || + errorMessage.includes('overloaded') || + errorMessage.includes('service temporarily unavailable') || + errorMessage.includes('timeout') || + errorMessage.includes('network error') || + error.status === 429 || + error.status >= 500 + ); } /** @@ -111,45 +111,45 @@ function isRetryableError(error) { * @returns {string} A concise error message. */ function _extractErrorMessage(error) { - try { - // Attempt 1: Look for Vercel SDK specific nested structure (common) - if (error?.data?.error?.message) { - return error.data.error.message; - } + try { + // Attempt 1: Look for Vercel SDK specific nested structure (common) + if (error?.data?.error?.message) { + return error.data.error.message; + } - // Attempt 2: Look for nested error message directly in the error object - if (error?.error?.message) { - return error.error.message; - } + // Attempt 2: Look for nested error message directly in the error object + if (error?.error?.message) { + return error.error.message; + } - // Attempt 3: Look for nested error message in response body if it's JSON string - if (typeof error?.responseBody === "string") { - try { - const body = JSON.parse(error.responseBody); - if (body?.error?.message) { - return body.error.message; - } - } catch (parseError) { - // Ignore if responseBody is not valid JSON - } - } + // Attempt 3: Look for nested error message in response body if it's JSON string + if (typeof error?.responseBody === 'string') { + try { + const body = JSON.parse(error.responseBody); + if (body?.error?.message) { + return body.error.message; + } + } catch (parseError) { + // Ignore if responseBody is not valid JSON + } + } - // Attempt 4: Use the top-level message if it exists - if (typeof error?.message === "string" && error.message) { - return error.message; - } + // Attempt 4: Use the top-level message if it exists + if (typeof error?.message === 'string' && error.message) { + return error.message; + } - // Attempt 5: Handle simple string errors - if (typeof error === "string") { - return error; - } + // Attempt 5: Handle simple string errors + if (typeof error === 'string') { + return error; + } - // Fallback - return "An unknown AI service error occurred."; - } catch (e) { - // Safety net - return "Failed to extract error message."; - } + // Fallback + return 'An unknown AI service error occurred.'; + } catch (e) { + // Safety net + return 'Failed to extract error message.'; + } } /** @@ -161,40 +161,40 @@ function _extractErrorMessage(error) { * @throws {Error} If a required API key is missing. */ function _resolveApiKey(providerName, session, projectRoot = null) { - const keyMap = { - openai: "OPENAI_API_KEY", - anthropic: "ANTHROPIC_API_KEY", - google: "GOOGLE_API_KEY", - perplexity: "PERPLEXITY_API_KEY", - mistral: "MISTRAL_API_KEY", - azure: "AZURE_OPENAI_API_KEY", - openrouter: "OPENROUTER_API_KEY", - xai: "XAI_API_KEY", - ollama: "OLLAMA_API_KEY", - bedrock: "AWS_ACCESS_KEY_ID", - vertex: "GOOGLE_API_KEY", - }; + const keyMap = { + openai: 'OPENAI_API_KEY', + anthropic: 'ANTHROPIC_API_KEY', + google: 'GOOGLE_API_KEY', + perplexity: 'PERPLEXITY_API_KEY', + mistral: 'MISTRAL_API_KEY', + azure: 'AZURE_OPENAI_API_KEY', + openrouter: 'OPENROUTER_API_KEY', + xai: 'XAI_API_KEY', + ollama: 'OLLAMA_API_KEY', + bedrock: 'AWS_ACCESS_KEY_ID', + vertex: 'GOOGLE_API_KEY' + }; - const envVarName = keyMap[providerName]; - if (!envVarName) { - throw new Error( - `Unknown provider '${providerName}' for API key resolution.` - ); - } + const envVarName = keyMap[providerName]; + if (!envVarName) { + throw new Error( + `Unknown provider '${providerName}' for API key resolution.` + ); + } - const apiKey = resolveEnvVariable(envVarName, session, projectRoot); + const apiKey = resolveEnvVariable(envVarName, session, projectRoot); - // Special handling for providers that can use alternative auth - if (providerName === "ollama" || providerName === "bedrock") { - return apiKey || null; - } + // Special handling for providers that can use alternative auth + if (providerName === 'ollama' || providerName === 'bedrock') { + return apiKey || null; + } - if (!apiKey) { - throw new Error( - `Required API key ${envVarName} for provider '${providerName}' is not set in environment, session, or .env file.` - ); - } - return apiKey; + if (!apiKey) { + throw new Error( + `Required API key ${envVarName} for provider '${providerName}' is not set in environment, session, or .env file.` + ); + } + return apiKey; } /** @@ -209,62 +209,62 @@ function _resolveApiKey(providerName, session, projectRoot = null) { * @throws {Error} If the call fails after all retries. */ async function _attemptProviderCallWithRetries( - provider, - serviceType, - callParams, - providerName, - modelId, - attemptRole + provider, + serviceType, + callParams, + providerName, + modelId, + attemptRole ) { - let retries = 0; - const fnName = serviceType; + let retries = 0; + const fnName = serviceType; - while (retries <= MAX_RETRIES) { - try { - if (getDebugFlag()) { - log( - "info", - `Attempt ${retries + 1}/${MAX_RETRIES + 1} calling ${fnName} (Provider: ${providerName}, Model: ${modelId}, Role: ${attemptRole})` - ); - } + while (retries <= MAX_RETRIES) { + try { + if (getDebugFlag()) { + log( + 'info', + `Attempt ${retries + 1}/${MAX_RETRIES + 1} calling ${fnName} (Provider: ${providerName}, Model: ${modelId}, Role: ${attemptRole})` + ); + } - // Call the appropriate method on the provider instance - const result = await provider[serviceType](callParams); + // Call the appropriate method on the provider instance + const result = await provider[serviceType](callParams); - if (getDebugFlag()) { - log( - "info", - `${fnName} succeeded for role ${attemptRole} (Provider: ${providerName}) on attempt ${retries + 1}` - ); - } - return result; - } catch (error) { - log( - "warn", - `Attempt ${retries + 1} failed for role ${attemptRole} (${fnName} / ${providerName}): ${error.message}` - ); + if (getDebugFlag()) { + log( + 'info', + `${fnName} succeeded for role ${attemptRole} (Provider: ${providerName}) on attempt ${retries + 1}` + ); + } + return result; + } catch (error) { + log( + 'warn', + `Attempt ${retries + 1} failed for role ${attemptRole} (${fnName} / ${providerName}): ${error.message}` + ); - if (isRetryableError(error) && retries < MAX_RETRIES) { - retries++; - const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retries - 1); - log( - "info", - `Something went wrong on the provider side. Retrying in ${delay / 1000}s...` - ); - await new Promise((resolve) => setTimeout(resolve, delay)); - } else { - log( - "error", - `Something went wrong on the provider side. Max retries reached for role ${attemptRole} (${fnName} / ${providerName}).` - ); - throw error; - } - } - } - // Should not be reached due to throw in the else block - throw new Error( - `Exhausted all retries for role ${attemptRole} (${fnName} / ${providerName})` - ); + if (isRetryableError(error) && retries < MAX_RETRIES) { + retries++; + const delay = INITIAL_RETRY_DELAY_MS * Math.pow(2, retries - 1); + log( + 'info', + `Something went wrong on the provider side. Retrying in ${delay / 1000}s...` + ); + await new Promise((resolve) => setTimeout(resolve, delay)); + } else { + log( + 'error', + `Something went wrong on the provider side. Max retries reached for role ${attemptRole} (${fnName} / ${providerName}).` + ); + throw error; + } + } + } + // Should not be reached due to throw in the else block + throw new Error( + `Exhausted all retries for role ${attemptRole} (${fnName} / ${providerName})` + ); } /** @@ -283,313 +283,313 @@ async function _attemptProviderCallWithRetries( * @returns {Promise<any>} Result from the underlying provider call. */ async function _unifiedServiceRunner(serviceType, params) { - const { - role: initialRole, - session, - projectRoot, - systemPrompt, - prompt, - schema, - objectName, - commandName, - outputType, - ...restApiParams - } = params; - if (getDebugFlag()) { - log("info", `${serviceType}Service called`, { - role: initialRole, - commandName, - outputType, - projectRoot, - }); - } + const { + role: initialRole, + session, + projectRoot, + systemPrompt, + prompt, + schema, + objectName, + commandName, + outputType, + ...restApiParams + } = params; + if (getDebugFlag()) { + log('info', `${serviceType}Service called`, { + role: initialRole, + commandName, + outputType, + projectRoot + }); + } - const effectiveProjectRoot = projectRoot || findProjectRoot(); - const userId = getUserId(effectiveProjectRoot); + const effectiveProjectRoot = projectRoot || findProjectRoot(); + const userId = getUserId(effectiveProjectRoot); - let sequence; - if (initialRole === "main") { - sequence = ["main", "fallback", "research"]; - } else if (initialRole === "research") { - sequence = ["research", "fallback", "main"]; - } else if (initialRole === "fallback") { - sequence = ["fallback", "main", "research"]; - } else { - log( - "warn", - `Unknown initial role: ${initialRole}. Defaulting to main -> fallback -> research sequence.` - ); - sequence = ["main", "fallback", "research"]; - } + let sequence; + if (initialRole === 'main') { + sequence = ['main', 'fallback', 'research']; + } else if (initialRole === 'research') { + sequence = ['research', 'fallback', 'main']; + } else if (initialRole === 'fallback') { + sequence = ['fallback', 'main', 'research']; + } else { + log( + 'warn', + `Unknown initial role: ${initialRole}. Defaulting to main -> fallback -> research sequence.` + ); + sequence = ['main', 'fallback', 'research']; + } - let lastError = null; - let lastCleanErrorMessage = - "AI service call failed for all configured roles."; + let lastError = null; + let lastCleanErrorMessage = + 'AI service call failed for all configured roles.'; - for (const currentRole of sequence) { - let providerName, - modelId, - apiKey, - roleParams, - provider, - baseURL, - providerResponse, - telemetryData = null; + for (const currentRole of sequence) { + let providerName, + modelId, + apiKey, + roleParams, + provider, + baseURL, + providerResponse, + telemetryData = null; - try { - log("info", `New AI service call with role: ${currentRole}`); + try { + log('info', `New AI service call with role: ${currentRole}`); - if (currentRole === "main") { - providerName = getMainProvider(effectiveProjectRoot); - modelId = getMainModelId(effectiveProjectRoot); - } else if (currentRole === "research") { - providerName = getResearchProvider(effectiveProjectRoot); - modelId = getResearchModelId(effectiveProjectRoot); - } else if (currentRole === "fallback") { - providerName = getFallbackProvider(effectiveProjectRoot); - modelId = getFallbackModelId(effectiveProjectRoot); - } else { - log( - "error", - `Unknown role encountered in _unifiedServiceRunner: ${currentRole}` - ); - lastError = - lastError || new Error(`Unknown AI role specified: ${currentRole}`); - continue; - } + if (currentRole === 'main') { + providerName = getMainProvider(effectiveProjectRoot); + modelId = getMainModelId(effectiveProjectRoot); + } else if (currentRole === 'research') { + providerName = getResearchProvider(effectiveProjectRoot); + modelId = getResearchModelId(effectiveProjectRoot); + } else if (currentRole === 'fallback') { + providerName = getFallbackProvider(effectiveProjectRoot); + modelId = getFallbackModelId(effectiveProjectRoot); + } else { + log( + 'error', + `Unknown role encountered in _unifiedServiceRunner: ${currentRole}` + ); + lastError = + lastError || new Error(`Unknown AI role specified: ${currentRole}`); + continue; + } - if (!providerName || !modelId) { - log( - "warn", - `Skipping role '${currentRole}': Provider or Model ID not configured.` - ); - lastError = - lastError || - new Error( - `Configuration missing for role '${currentRole}'. Provider: ${providerName}, Model: ${modelId}` - ); - continue; - } + if (!providerName || !modelId) { + log( + 'warn', + `Skipping role '${currentRole}': Provider or Model ID not configured.` + ); + lastError = + lastError || + new Error( + `Configuration missing for role '${currentRole}'. Provider: ${providerName}, Model: ${modelId}` + ); + continue; + } - // Get provider instance - provider = PROVIDERS[providerName?.toLowerCase()]; - if (!provider) { - log( - "warn", - `Skipping role '${currentRole}': Provider '${providerName}' not supported.` - ); - lastError = - lastError || - new Error(`Unsupported provider configured: ${providerName}`); - continue; - } + // Get provider instance + provider = PROVIDERS[providerName?.toLowerCase()]; + if (!provider) { + log( + 'warn', + `Skipping role '${currentRole}': Provider '${providerName}' not supported.` + ); + lastError = + lastError || + new Error(`Unsupported provider configured: ${providerName}`); + continue; + } - // Check API key if needed - if (providerName?.toLowerCase() !== "ollama") { - if (!isApiKeySet(providerName, session, effectiveProjectRoot)) { - log( - "warn", - `Skipping role '${currentRole}' (Provider: ${providerName}): API key not set or invalid.` - ); - lastError = - lastError || - new Error( - `API key for provider '${providerName}' (role: ${currentRole}) is not set.` - ); - continue; // Skip to the next role in the sequence - } - } + // Check API key if needed + if (providerName?.toLowerCase() !== 'ollama') { + if (!isApiKeySet(providerName, session, effectiveProjectRoot)) { + log( + 'warn', + `Skipping role '${currentRole}' (Provider: ${providerName}): API key not set or invalid.` + ); + lastError = + lastError || + new Error( + `API key for provider '${providerName}' (role: ${currentRole}) is not set.` + ); + continue; // Skip to the next role in the sequence + } + } - // Get base URL if configured (optional for most providers) - baseURL = getBaseUrlForRole(currentRole, effectiveProjectRoot); + // Get base URL if configured (optional for most providers) + baseURL = getBaseUrlForRole(currentRole, effectiveProjectRoot); - // For Azure, use the global Azure base URL if role-specific URL is not configured - if (providerName?.toLowerCase() === "azure" && !baseURL) { - baseURL = getAzureBaseURL(effectiveProjectRoot); - log("debug", `Using global Azure base URL: ${baseURL}`); - } else if (providerName?.toLowerCase() === "ollama" && !baseURL) { - // For Ollama, use the global Ollama base URL if role-specific URL is not configured - baseURL = getOllamaBaseURL(effectiveProjectRoot); - log("debug", `Using global Ollama base URL: ${baseURL}`); - } else if (providerName?.toLowerCase() === "bedrock" && !baseURL) { - // For Bedrock, use the global Bedrock base URL if role-specific URL is not configured - baseURL = getBedrockBaseURL(effectiveProjectRoot); - log("debug", `Using global Bedrock base URL: ${baseURL}`); - } + // For Azure, use the global Azure base URL if role-specific URL is not configured + if (providerName?.toLowerCase() === 'azure' && !baseURL) { + baseURL = getAzureBaseURL(effectiveProjectRoot); + log('debug', `Using global Azure base URL: ${baseURL}`); + } else if (providerName?.toLowerCase() === 'ollama' && !baseURL) { + // For Ollama, use the global Ollama base URL if role-specific URL is not configured + baseURL = getOllamaBaseURL(effectiveProjectRoot); + log('debug', `Using global Ollama base URL: ${baseURL}`); + } else if (providerName?.toLowerCase() === 'bedrock' && !baseURL) { + // For Bedrock, use the global Bedrock base URL if role-specific URL is not configured + baseURL = getBedrockBaseURL(effectiveProjectRoot); + log('debug', `Using global Bedrock base URL: ${baseURL}`); + } - // Get AI parameters for the current role - roleParams = getParametersForRole(currentRole, effectiveProjectRoot); - apiKey = _resolveApiKey( - providerName?.toLowerCase(), - session, - effectiveProjectRoot - ); + // Get AI parameters for the current role + roleParams = getParametersForRole(currentRole, effectiveProjectRoot); + apiKey = _resolveApiKey( + providerName?.toLowerCase(), + session, + effectiveProjectRoot + ); - // Prepare provider-specific configuration - let providerSpecificParams = {}; + // Prepare provider-specific configuration + let providerSpecificParams = {}; - // Handle Vertex AI specific configuration - if (providerName?.toLowerCase() === "vertex") { - // Get Vertex project ID and location - const projectId = - getVertexProjectId(effectiveProjectRoot) || - resolveEnvVariable( - "VERTEX_PROJECT_ID", - session, - effectiveProjectRoot - ); + // Handle Vertex AI specific configuration + if (providerName?.toLowerCase() === 'vertex') { + // Get Vertex project ID and location + const projectId = + getVertexProjectId(effectiveProjectRoot) || + resolveEnvVariable( + 'VERTEX_PROJECT_ID', + session, + effectiveProjectRoot + ); - const location = - getVertexLocation(effectiveProjectRoot) || - resolveEnvVariable( - "VERTEX_LOCATION", - session, - effectiveProjectRoot - ) || - "us-central1"; + const location = + getVertexLocation(effectiveProjectRoot) || + resolveEnvVariable( + 'VERTEX_LOCATION', + session, + effectiveProjectRoot + ) || + 'us-central1'; - // Get credentials path if available - const credentialsPath = resolveEnvVariable( - "GOOGLE_APPLICATION_CREDENTIALS", - session, - effectiveProjectRoot - ); + // Get credentials path if available + const credentialsPath = resolveEnvVariable( + 'GOOGLE_APPLICATION_CREDENTIALS', + session, + effectiveProjectRoot + ); - // Add Vertex-specific parameters - providerSpecificParams = { - projectId, - location, - ...(credentialsPath && { credentials: { credentialsFromEnv: true } }), - }; + // Add Vertex-specific parameters + providerSpecificParams = { + projectId, + location, + ...(credentialsPath && { credentials: { credentialsFromEnv: true } }) + }; - log( - "debug", - `Using Vertex AI configuration: Project ID=${projectId}, Location=${location}` - ); - } + log( + 'debug', + `Using Vertex AI configuration: Project ID=${projectId}, Location=${location}` + ); + } - const messages = []; - if (systemPrompt) { - messages.push({ role: "system", content: systemPrompt }); - } + const messages = []; + if (systemPrompt) { + messages.push({ role: 'system', content: systemPrompt }); + } - // IN THE FUTURE WHEN DOING CONTEXT IMPROVEMENTS - // { - // type: 'text', - // text: 'Large cached context here like a tasks json', - // providerOptions: { - // anthropic: { cacheControl: { type: 'ephemeral' } } - // } - // } + // IN THE FUTURE WHEN DOING CONTEXT IMPROVEMENTS + // { + // type: 'text', + // text: 'Large cached context here like a tasks json', + // providerOptions: { + // anthropic: { cacheControl: { type: 'ephemeral' } } + // } + // } - // Example - // if (params.context) { // context is a json string of a tasks object or some other stu - // messages.push({ - // type: 'text', - // text: params.context, - // providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } } } - // }); - // } + // Example + // if (params.context) { // context is a json string of a tasks object or some other stu + // messages.push({ + // type: 'text', + // text: params.context, + // providerOptions: { anthropic: { cacheControl: { type: 'ephemeral' } } } + // }); + // } - if (prompt) { - messages.push({ role: "user", content: prompt }); - } else { - throw new Error("User prompt content is missing."); - } + if (prompt) { + messages.push({ role: 'user', content: prompt }); + } else { + throw new Error('User prompt content is missing.'); + } - const callParams = { - apiKey, - modelId, - maxTokens: roleParams.maxTokens, - temperature: roleParams.temperature, - messages, - ...(baseURL && { baseURL }), - ...(serviceType === "generateObject" && { schema, objectName }), - ...providerSpecificParams, - ...restApiParams, - }; + const callParams = { + apiKey, + modelId, + maxTokens: roleParams.maxTokens, + temperature: roleParams.temperature, + messages, + ...(baseURL && { baseURL }), + ...(serviceType === 'generateObject' && { schema, objectName }), + ...providerSpecificParams, + ...restApiParams + }; - providerResponse = await _attemptProviderCallWithRetries( - provider, - serviceType, - callParams, - providerName, - modelId, - currentRole - ); + providerResponse = await _attemptProviderCallWithRetries( + provider, + serviceType, + callParams, + providerName, + modelId, + currentRole + ); - if (userId && providerResponse && providerResponse.usage) { - try { - telemetryData = await logAiUsage({ - userId, - commandName, - providerName, - modelId, - inputTokens: providerResponse.usage.inputTokens, - outputTokens: providerResponse.usage.outputTokens, - outputType, - }); - } catch (telemetryError) { - // logAiUsage already logs its own errors and returns null on failure - // No need to log again here, telemetryData will remain null - } - } else if (userId && providerResponse && !providerResponse.usage) { - log( - "warn", - `Cannot log telemetry for ${commandName} (${providerName}/${modelId}): AI result missing 'usage' data. (May be expected for streams)` - ); - } + if (userId && providerResponse && providerResponse.usage) { + try { + telemetryData = await logAiUsage({ + userId, + commandName, + providerName, + modelId, + inputTokens: providerResponse.usage.inputTokens, + outputTokens: providerResponse.usage.outputTokens, + outputType + }); + } catch (telemetryError) { + // logAiUsage already logs its own errors and returns null on failure + // No need to log again here, telemetryData will remain null + } + } else if (userId && providerResponse && !providerResponse.usage) { + log( + 'warn', + `Cannot log telemetry for ${commandName} (${providerName}/${modelId}): AI result missing 'usage' data. (May be expected for streams)` + ); + } - let finalMainResult; - if (serviceType === "generateText") { - finalMainResult = providerResponse.text; - } else if (serviceType === "generateObject") { - finalMainResult = providerResponse.object; - } else if (serviceType === "streamText") { - finalMainResult = providerResponse; - } else { - log( - "error", - `Unknown serviceType in _unifiedServiceRunner: ${serviceType}` - ); - finalMainResult = providerResponse; - } + let finalMainResult; + if (serviceType === 'generateText') { + finalMainResult = providerResponse.text; + } else if (serviceType === 'generateObject') { + finalMainResult = providerResponse.object; + } else if (serviceType === 'streamText') { + finalMainResult = providerResponse; + } else { + log( + 'error', + `Unknown serviceType in _unifiedServiceRunner: ${serviceType}` + ); + finalMainResult = providerResponse; + } - return { - mainResult: finalMainResult, - telemetryData: telemetryData, - }; - } catch (error) { - const cleanMessage = _extractErrorMessage(error); - log( - "error", - `Service call failed for role ${currentRole} (Provider: ${providerName || "unknown"}, Model: ${modelId || "unknown"}): ${cleanMessage}` - ); - lastError = error; - lastCleanErrorMessage = cleanMessage; + return { + mainResult: finalMainResult, + telemetryData: telemetryData + }; + } catch (error) { + const cleanMessage = _extractErrorMessage(error); + log( + 'error', + `Service call failed for role ${currentRole} (Provider: ${providerName || 'unknown'}, Model: ${modelId || 'unknown'}): ${cleanMessage}` + ); + lastError = error; + lastCleanErrorMessage = cleanMessage; - if (serviceType === "generateObject") { - const lowerCaseMessage = cleanMessage.toLowerCase(); - if ( - lowerCaseMessage.includes( - "no endpoints found that support tool use" - ) || - lowerCaseMessage.includes("does not support tool_use") || - lowerCaseMessage.includes("tool use is not supported") || - lowerCaseMessage.includes("tools are not supported") || - lowerCaseMessage.includes("function calling is not supported") || - lowerCaseMessage.includes("tool use is not supported") - ) { - const specificErrorMsg = `Model '${modelId || "unknown"}' via provider '${providerName || "unknown"}' does not support the 'tool use' required by generateObjectService. Please configure a model that supports tool/function calling for the '${currentRole}' role, or use generateTextService if structured output is not strictly required.`; - log("error", `[Tool Support Error] ${specificErrorMsg}`); - throw new Error(specificErrorMsg); - } - } - } - } + if (serviceType === 'generateObject') { + const lowerCaseMessage = cleanMessage.toLowerCase(); + if ( + lowerCaseMessage.includes( + 'no endpoints found that support tool use' + ) || + lowerCaseMessage.includes('does not support tool_use') || + lowerCaseMessage.includes('tool use is not supported') || + lowerCaseMessage.includes('tools are not supported') || + lowerCaseMessage.includes('function calling is not supported') || + lowerCaseMessage.includes('tool use is not supported') + ) { + const specificErrorMsg = `Model '${modelId || 'unknown'}' via provider '${providerName || 'unknown'}' does not support the 'tool use' required by generateObjectService. Please configure a model that supports tool/function calling for the '${currentRole}' role, or use generateTextService if structured output is not strictly required.`; + log('error', `[Tool Support Error] ${specificErrorMsg}`); + throw new Error(specificErrorMsg); + } + } + } + } - log("error", `All roles in the sequence [${sequence.join(", ")}] failed.`); - throw new Error(lastCleanErrorMessage); + log('error', `All roles in the sequence [${sequence.join(', ')}] failed.`); + throw new Error(lastCleanErrorMessage); } /** @@ -607,11 +607,11 @@ async function _unifiedServiceRunner(serviceType, params) { * @returns {Promise<object>} Result object containing generated text and usage data. */ async function generateTextService(params) { - // Ensure default outputType if not provided - const defaults = { outputType: "cli" }; - const combinedParams = { ...defaults, ...params }; - // TODO: Validate commandName exists? - return _unifiedServiceRunner("generateText", combinedParams); + // Ensure default outputType if not provided + const defaults = { outputType: 'cli' }; + const combinedParams = { ...defaults, ...params }; + // TODO: Validate commandName exists? + return _unifiedServiceRunner('generateText', combinedParams); } /** @@ -629,13 +629,13 @@ async function generateTextService(params) { * @returns {Promise<object>} Result object containing the stream and usage data. */ async function streamTextService(params) { - const defaults = { outputType: "cli" }; - const combinedParams = { ...defaults, ...params }; - // TODO: Validate commandName exists? - // NOTE: Telemetry for streaming might be tricky as usage data often comes at the end. - // The current implementation logs *after* the stream is returned. - // We might need to adjust how usage is captured/logged for streams. - return _unifiedServiceRunner("streamText", combinedParams); + const defaults = { outputType: 'cli' }; + const combinedParams = { ...defaults, ...params }; + // TODO: Validate commandName exists? + // NOTE: Telemetry for streaming might be tricky as usage data often comes at the end. + // The current implementation logs *after* the stream is returned. + // We might need to adjust how usage is captured/logged for streams. + return _unifiedServiceRunner('streamText', combinedParams); } /** @@ -656,14 +656,14 @@ async function streamTextService(params) { * @returns {Promise<object>} Result object containing the generated object and usage data. */ async function generateObjectService(params) { - const defaults = { - objectName: "generated_object", - maxRetries: 3, - outputType: "cli", - }; - const combinedParams = { ...defaults, ...params }; - // TODO: Validate commandName exists? - return _unifiedServiceRunner("generateObject", combinedParams); + const defaults = { + objectName: 'generated_object', + maxRetries: 3, + outputType: 'cli' + }; + const combinedParams = { ...defaults, ...params }; + // TODO: Validate commandName exists? + return _unifiedServiceRunner('generateObject', combinedParams); } // --- Telemetry Function --- @@ -679,61 +679,61 @@ async function generateObjectService(params) { * @param {number} params.outputTokens - Number of output tokens. */ async function logAiUsage({ - userId, - commandName, - providerName, - modelId, - inputTokens, - outputTokens, - outputType, + userId, + commandName, + providerName, + modelId, + inputTokens, + outputTokens, + outputType }) { - try { - const isMCP = outputType === "mcp"; - const timestamp = new Date().toISOString(); - const totalTokens = (inputTokens || 0) + (outputTokens || 0); + try { + const isMCP = outputType === 'mcp'; + const timestamp = new Date().toISOString(); + const totalTokens = (inputTokens || 0) + (outputTokens || 0); - // Destructure currency along with costs - const { inputCost, outputCost, currency } = _getCostForModel( - providerName, - modelId - ); + // Destructure currency along with costs + const { inputCost, outputCost, currency } = _getCostForModel( + providerName, + modelId + ); - const totalCost = - ((inputTokens || 0) / 1_000_000) * inputCost + - ((outputTokens || 0) / 1_000_000) * outputCost; + const totalCost = + ((inputTokens || 0) / 1_000_000) * inputCost + + ((outputTokens || 0) / 1_000_000) * outputCost; - const telemetryData = { - timestamp, - userId, - commandName, - modelUsed: modelId, // Consistent field name from requirements - providerName, // Keep provider name for context - inputTokens: inputTokens || 0, - outputTokens: outputTokens || 0, - totalTokens, - totalCost: parseFloat(totalCost.toFixed(6)), - currency, // Add currency to the telemetry data - }; + const telemetryData = { + timestamp, + userId, + commandName, + modelUsed: modelId, // Consistent field name from requirements + providerName, // Keep provider name for context + inputTokens: inputTokens || 0, + outputTokens: outputTokens || 0, + totalTokens, + totalCost: parseFloat(totalCost.toFixed(6)), + currency // Add currency to the telemetry data + }; - if (getDebugFlag()) { - log("info", "AI Usage Telemetry:", telemetryData); - } + if (getDebugFlag()) { + log('info', 'AI Usage Telemetry:', telemetryData); + } - // TODO (Subtask 77.2): Send telemetryData securely to the external endpoint. + // TODO (Subtask 77.2): Send telemetryData securely to the external endpoint. - return telemetryData; - } catch (error) { - log("error", `Failed to log AI usage telemetry: ${error.message}`, { - error, - }); - // Don't re-throw; telemetry failure shouldn't block core functionality. - return null; - } + return telemetryData; + } catch (error) { + log('error', `Failed to log AI usage telemetry: ${error.message}`, { + error + }); + // Don't re-throw; telemetry failure shouldn't block core functionality. + return null; + } } export { - generateTextService, - streamTextService, - generateObjectService, - logAiUsage, + generateTextService, + streamTextService, + generateObjectService, + logAiUsage }; diff --git a/scripts/modules/dependency-manager.js b/scripts/modules/dependency-manager.js index a6417780..9b556143 100644 --- a/scripts/modules/dependency-manager.js +++ b/scripts/modules/dependency-manager.js @@ -3,23 +3,23 @@ * Manages task dependencies and relationships */ -import path from "path"; -import chalk from "chalk"; -import boxen from "boxen"; +import path from 'path'; +import chalk from 'chalk'; +import boxen from 'boxen'; import { - log, - readJSON, - writeJSON, - taskExists, - formatTaskId, - findCycles, - isSilentMode, -} from "./utils.js"; + log, + readJSON, + writeJSON, + taskExists, + formatTaskId, + findCycles, + isSilentMode +} from './utils.js'; -import { displayBanner } from "./ui.js"; +import { displayBanner } from './ui.js'; -import { generateTaskFiles } from "./task-manager.js"; +import { generateTaskFiles } from './task-manager.js'; /** * Add a dependency to a task @@ -28,183 +28,183 @@ import { generateTaskFiles } from "./task-manager.js"; * @param {number|string} dependencyId - ID of the task to add as dependency */ async function addDependency(tasksPath, taskId, dependencyId) { - log("info", `Adding dependency ${dependencyId} to task ${taskId}...`); + log('info', `Adding dependency ${dependencyId} to task ${taskId}...`); - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log("error", "No valid tasks found in tasks.json"); - process.exit(1); - } + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log('error', 'No valid tasks found in tasks.json'); + process.exit(1); + } - // Format the task and dependency IDs correctly - const formattedTaskId = - typeof taskId === "string" && taskId.includes(".") - ? taskId - : parseInt(taskId, 10); + // Format the task and dependency IDs correctly + const formattedTaskId = + typeof taskId === 'string' && taskId.includes('.') + ? taskId + : parseInt(taskId, 10); - const formattedDependencyId = formatTaskId(dependencyId); + const formattedDependencyId = formatTaskId(dependencyId); - // Check if the dependency task or subtask actually exists - if (!taskExists(data.tasks, formattedDependencyId)) { - log( - "error", - `Dependency target ${formattedDependencyId} does not exist in tasks.json` - ); - process.exit(1); - } + // Check if the dependency task or subtask actually exists + if (!taskExists(data.tasks, formattedDependencyId)) { + log( + 'error', + `Dependency target ${formattedDependencyId} does not exist in tasks.json` + ); + process.exit(1); + } - // Find the task to update - let targetTask = null; - let isSubtask = false; + // Find the task to update + let targetTask = null; + let isSubtask = false; - if (typeof formattedTaskId === "string" && formattedTaskId.includes(".")) { - // Handle dot notation for subtasks (e.g., "1.2") - const [parentId, subtaskId] = formattedTaskId - .split(".") - .map((id) => parseInt(id, 10)); - const parentTask = data.tasks.find((t) => t.id === parentId); + if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) { + // Handle dot notation for subtasks (e.g., "1.2") + const [parentId, subtaskId] = formattedTaskId + .split('.') + .map((id) => parseInt(id, 10)); + const parentTask = data.tasks.find((t) => t.id === parentId); - if (!parentTask) { - log("error", `Parent task ${parentId} not found.`); - process.exit(1); - } + if (!parentTask) { + log('error', `Parent task ${parentId} not found.`); + process.exit(1); + } - if (!parentTask.subtasks) { - log("error", `Parent task ${parentId} has no subtasks.`); - process.exit(1); - } + if (!parentTask.subtasks) { + log('error', `Parent task ${parentId} has no subtasks.`); + process.exit(1); + } - targetTask = parentTask.subtasks.find((s) => s.id === subtaskId); - isSubtask = true; + targetTask = parentTask.subtasks.find((s) => s.id === subtaskId); + isSubtask = true; - if (!targetTask) { - log("error", `Subtask ${formattedTaskId} not found.`); - process.exit(1); - } - } else { - // Regular task (not a subtask) - targetTask = data.tasks.find((t) => t.id === formattedTaskId); + if (!targetTask) { + log('error', `Subtask ${formattedTaskId} not found.`); + process.exit(1); + } + } else { + // Regular task (not a subtask) + targetTask = data.tasks.find((t) => t.id === formattedTaskId); - if (!targetTask) { - log("error", `Task ${formattedTaskId} not found.`); - process.exit(1); - } - } + if (!targetTask) { + log('error', `Task ${formattedTaskId} not found.`); + process.exit(1); + } + } - // Initialize dependencies array if it doesn't exist - if (!targetTask.dependencies) { - targetTask.dependencies = []; - } + // Initialize dependencies array if it doesn't exist + if (!targetTask.dependencies) { + targetTask.dependencies = []; + } - // Check if dependency already exists - if ( - targetTask.dependencies.some((d) => { - // Convert both to strings for comparison to handle both numeric and string IDs - return String(d) === String(formattedDependencyId); - }) - ) { - log( - "warn", - `Dependency ${formattedDependencyId} already exists in task ${formattedTaskId}.` - ); - return; - } + // Check if dependency already exists + if ( + targetTask.dependencies.some((d) => { + // Convert both to strings for comparison to handle both numeric and string IDs + return String(d) === String(formattedDependencyId); + }) + ) { + log( + 'warn', + `Dependency ${formattedDependencyId} already exists in task ${formattedTaskId}.` + ); + return; + } - // Check if the task is trying to depend on itself - compare full IDs (including subtask parts) - if (String(formattedTaskId) === String(formattedDependencyId)) { - log("error", `Task ${formattedTaskId} cannot depend on itself.`); - process.exit(1); - } + // Check if the task is trying to depend on itself - compare full IDs (including subtask parts) + if (String(formattedTaskId) === String(formattedDependencyId)) { + log('error', `Task ${formattedTaskId} cannot depend on itself.`); + process.exit(1); + } - // For subtasks of the same parent, we need to make sure we're not treating it as a self-dependency - // Check if we're dealing with subtasks with the same parent task - let isSelfDependency = false; + // For subtasks of the same parent, we need to make sure we're not treating it as a self-dependency + // Check if we're dealing with subtasks with the same parent task + let isSelfDependency = false; - if ( - typeof formattedTaskId === "string" && - typeof formattedDependencyId === "string" && - formattedTaskId.includes(".") && - formattedDependencyId.includes(".") - ) { - const [taskParentId] = formattedTaskId.split("."); - const [depParentId] = formattedDependencyId.split("."); + if ( + typeof formattedTaskId === 'string' && + typeof formattedDependencyId === 'string' && + formattedTaskId.includes('.') && + formattedDependencyId.includes('.') + ) { + const [taskParentId] = formattedTaskId.split('.'); + const [depParentId] = formattedDependencyId.split('.'); - // Only treat it as a self-dependency if both the parent ID and subtask ID are identical - isSelfDependency = formattedTaskId === formattedDependencyId; + // Only treat it as a self-dependency if both the parent ID and subtask ID are identical + isSelfDependency = formattedTaskId === formattedDependencyId; - // Log for debugging - log( - "debug", - `Adding dependency between subtasks: ${formattedTaskId} depends on ${formattedDependencyId}` - ); - log( - "debug", - `Parent IDs: ${taskParentId} and ${depParentId}, Self-dependency check: ${isSelfDependency}` - ); - } + // Log for debugging + log( + 'debug', + `Adding dependency between subtasks: ${formattedTaskId} depends on ${formattedDependencyId}` + ); + log( + 'debug', + `Parent IDs: ${taskParentId} and ${depParentId}, Self-dependency check: ${isSelfDependency}` + ); + } - if (isSelfDependency) { - log("error", `Subtask ${formattedTaskId} cannot depend on itself.`); - process.exit(1); - } + if (isSelfDependency) { + log('error', `Subtask ${formattedTaskId} cannot depend on itself.`); + process.exit(1); + } - // Check for circular dependencies - let dependencyChain = [formattedTaskId]; - if ( - !isCircularDependency(data.tasks, formattedDependencyId, dependencyChain) - ) { - // Add the dependency - targetTask.dependencies.push(formattedDependencyId); + // Check for circular dependencies + let dependencyChain = [formattedTaskId]; + if ( + !isCircularDependency(data.tasks, formattedDependencyId, dependencyChain) + ) { + // Add the dependency + targetTask.dependencies.push(formattedDependencyId); - // Sort dependencies numerically or by parent task ID first, then subtask ID - targetTask.dependencies.sort((a, b) => { - if (typeof a === "number" && typeof b === "number") { - return a - b; - } else if (typeof a === "string" && typeof b === "string") { - const [aParent, aChild] = a.split(".").map(Number); - const [bParent, bChild] = b.split(".").map(Number); - return aParent !== bParent ? aParent - bParent : aChild - bChild; - } else if (typeof a === "number") { - return -1; // Numbers come before strings - } else { - return 1; // Strings come after numbers - } - }); + // Sort dependencies numerically or by parent task ID first, then subtask ID + targetTask.dependencies.sort((a, b) => { + if (typeof a === 'number' && typeof b === 'number') { + return a - b; + } else if (typeof a === 'string' && typeof b === 'string') { + const [aParent, aChild] = a.split('.').map(Number); + const [bParent, bChild] = b.split('.').map(Number); + return aParent !== bParent ? aParent - bParent : aChild - bChild; + } else if (typeof a === 'number') { + return -1; // Numbers come before strings + } else { + return 1; // Strings come after numbers + } + }); - // Save changes - writeJSON(tasksPath, data); - log( - "success", - `Added dependency ${formattedDependencyId} to task ${formattedTaskId}` - ); + // Save changes + writeJSON(tasksPath, data); + log( + 'success', + `Added dependency ${formattedDependencyId} to task ${formattedTaskId}` + ); - // Display a more visually appealing success message - if (!isSilentMode()) { - console.log( - boxen( - chalk.green(`Successfully added dependency:\n\n`) + - `Task ${chalk.bold(formattedTaskId)} now depends on ${chalk.bold(formattedDependencyId)}`, - { - padding: 1, - borderColor: "green", - borderStyle: "round", - margin: { top: 1 }, - } - ) - ); - } + // Display a more visually appealing success message + if (!isSilentMode()) { + console.log( + boxen( + chalk.green(`Successfully added dependency:\n\n`) + + `Task ${chalk.bold(formattedTaskId)} now depends on ${chalk.bold(formattedDependencyId)}`, + { + padding: 1, + borderColor: 'green', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); + } - // Generate updated task files - await generateTaskFiles(tasksPath, path.dirname(tasksPath)); + // Generate updated task files + await generateTaskFiles(tasksPath, path.dirname(tasksPath)); - log("info", "Task files regenerated with updated dependencies."); - } else { - log( - "error", - `Cannot add dependency ${formattedDependencyId} to task ${formattedTaskId} as it would create a circular dependency.` - ); - process.exit(1); - } + log('info', 'Task files regenerated with updated dependencies.'); + } else { + log( + 'error', + `Cannot add dependency ${formattedDependencyId} to task ${formattedTaskId} as it would create a circular dependency.` + ); + process.exit(1); + } } /** @@ -214,127 +214,127 @@ async function addDependency(tasksPath, taskId, dependencyId) { * @param {number|string} dependencyId - ID of the task to remove as dependency */ async function removeDependency(tasksPath, taskId, dependencyId) { - log("info", `Removing dependency ${dependencyId} from task ${taskId}...`); + log('info', `Removing dependency ${dependencyId} from task ${taskId}...`); - // Read tasks file - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log("error", "No valid tasks found."); - process.exit(1); - } + // Read tasks file + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log('error', 'No valid tasks found.'); + process.exit(1); + } - // Format the task and dependency IDs correctly - const formattedTaskId = - typeof taskId === "string" && taskId.includes(".") - ? taskId - : parseInt(taskId, 10); + // Format the task and dependency IDs correctly + const formattedTaskId = + typeof taskId === 'string' && taskId.includes('.') + ? taskId + : parseInt(taskId, 10); - const formattedDependencyId = formatTaskId(dependencyId); + const formattedDependencyId = formatTaskId(dependencyId); - // Find the task to update - let targetTask = null; - let isSubtask = false; + // Find the task to update + let targetTask = null; + let isSubtask = false; - if (typeof formattedTaskId === "string" && formattedTaskId.includes(".")) { - // Handle dot notation for subtasks (e.g., "1.2") - const [parentId, subtaskId] = formattedTaskId - .split(".") - .map((id) => parseInt(id, 10)); - const parentTask = data.tasks.find((t) => t.id === parentId); + if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) { + // Handle dot notation for subtasks (e.g., "1.2") + const [parentId, subtaskId] = formattedTaskId + .split('.') + .map((id) => parseInt(id, 10)); + const parentTask = data.tasks.find((t) => t.id === parentId); - if (!parentTask) { - log("error", `Parent task ${parentId} not found.`); - process.exit(1); - } + if (!parentTask) { + log('error', `Parent task ${parentId} not found.`); + process.exit(1); + } - if (!parentTask.subtasks) { - log("error", `Parent task ${parentId} has no subtasks.`); - process.exit(1); - } + if (!parentTask.subtasks) { + log('error', `Parent task ${parentId} has no subtasks.`); + process.exit(1); + } - targetTask = parentTask.subtasks.find((s) => s.id === subtaskId); - isSubtask = true; + targetTask = parentTask.subtasks.find((s) => s.id === subtaskId); + isSubtask = true; - if (!targetTask) { - log("error", `Subtask ${formattedTaskId} not found.`); - process.exit(1); - } - } else { - // Regular task (not a subtask) - targetTask = data.tasks.find((t) => t.id === formattedTaskId); + if (!targetTask) { + log('error', `Subtask ${formattedTaskId} not found.`); + process.exit(1); + } + } else { + // Regular task (not a subtask) + targetTask = data.tasks.find((t) => t.id === formattedTaskId); - if (!targetTask) { - log("error", `Task ${formattedTaskId} not found.`); - process.exit(1); - } - } + if (!targetTask) { + log('error', `Task ${formattedTaskId} not found.`); + process.exit(1); + } + } - // Check if the task has any dependencies - if (!targetTask.dependencies || targetTask.dependencies.length === 0) { - log( - "info", - `Task ${formattedTaskId} has no dependencies, nothing to remove.` - ); - return; - } + // Check if the task has any dependencies + if (!targetTask.dependencies || targetTask.dependencies.length === 0) { + log( + 'info', + `Task ${formattedTaskId} has no dependencies, nothing to remove.` + ); + return; + } - // Normalize the dependency ID for comparison to handle different formats - const normalizedDependencyId = String(formattedDependencyId); + // Normalize the dependency ID for comparison to handle different formats + const normalizedDependencyId = String(formattedDependencyId); - // Check if the dependency exists by comparing string representations - const dependencyIndex = targetTask.dependencies.findIndex((dep) => { - // Convert both to strings for comparison - let depStr = String(dep); + // Check if the dependency exists by comparing string representations + const dependencyIndex = targetTask.dependencies.findIndex((dep) => { + // Convert both to strings for comparison + let depStr = String(dep); - // Special handling for numeric IDs that might be subtask references - if (typeof dep === "number" && dep < 100 && isSubtask) { - // It's likely a reference to another subtask in the same parent task - // Convert to full format for comparison (e.g., 2 -> "1.2" for a subtask in task 1) - const [parentId] = formattedTaskId.split("."); - depStr = `${parentId}.${dep}`; - } + // Special handling for numeric IDs that might be subtask references + if (typeof dep === 'number' && dep < 100 && isSubtask) { + // It's likely a reference to another subtask in the same parent task + // Convert to full format for comparison (e.g., 2 -> "1.2" for a subtask in task 1) + const [parentId] = formattedTaskId.split('.'); + depStr = `${parentId}.${dep}`; + } - return depStr === normalizedDependencyId; - }); + return depStr === normalizedDependencyId; + }); - if (dependencyIndex === -1) { - log( - "info", - `Task ${formattedTaskId} does not depend on ${formattedDependencyId}, no changes made.` - ); - return; - } + if (dependencyIndex === -1) { + log( + 'info', + `Task ${formattedTaskId} does not depend on ${formattedDependencyId}, no changes made.` + ); + return; + } - // Remove the dependency - targetTask.dependencies.splice(dependencyIndex, 1); + // Remove the dependency + targetTask.dependencies.splice(dependencyIndex, 1); - // Save the updated tasks - writeJSON(tasksPath, data); + // Save the updated tasks + writeJSON(tasksPath, data); - // Success message - log( - "success", - `Removed dependency: Task ${formattedTaskId} no longer depends on ${formattedDependencyId}` - ); + // Success message + log( + 'success', + `Removed dependency: Task ${formattedTaskId} no longer depends on ${formattedDependencyId}` + ); - if (!isSilentMode()) { - // Display a more visually appealing success message - console.log( - boxen( - chalk.green(`Successfully removed dependency:\n\n`) + - `Task ${chalk.bold(formattedTaskId)} no longer depends on ${chalk.bold(formattedDependencyId)}`, - { - padding: 1, - borderColor: "green", - borderStyle: "round", - margin: { top: 1 }, - } - ) - ); - } + if (!isSilentMode()) { + // Display a more visually appealing success message + console.log( + boxen( + chalk.green(`Successfully removed dependency:\n\n`) + + `Task ${chalk.bold(formattedTaskId)} no longer depends on ${chalk.bold(formattedDependencyId)}`, + { + padding: 1, + borderColor: 'green', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); + } - // Regenerate task files - await generateTaskFiles(tasksPath, path.dirname(tasksPath)); + // Regenerate task files + await generateTaskFiles(tasksPath, path.dirname(tasksPath)); } /** @@ -345,54 +345,54 @@ async function removeDependency(tasksPath, taskId, dependencyId) { * @returns {boolean} True if circular dependency would be created */ function isCircularDependency(tasks, taskId, chain = []) { - // Convert taskId to string for comparison - const taskIdStr = String(taskId); + // Convert taskId to string for comparison + const taskIdStr = String(taskId); - // If we've seen this task before in the chain, we have a circular dependency - if (chain.some((id) => String(id) === taskIdStr)) { - return true; - } + // If we've seen this task before in the chain, we have a circular dependency + if (chain.some((id) => String(id) === taskIdStr)) { + return true; + } - // Find the task or subtask - let task = null; - let parentIdForSubtask = null; + // Find the task or subtask + let task = null; + let parentIdForSubtask = null; - // Check if this is a subtask reference (e.g., "1.2") - if (taskIdStr.includes(".")) { - const [parentId, subtaskId] = taskIdStr.split(".").map(Number); - const parentTask = tasks.find((t) => t.id === parentId); - parentIdForSubtask = parentId; // Store parent ID if it's a subtask + // Check if this is a subtask reference (e.g., "1.2") + if (taskIdStr.includes('.')) { + const [parentId, subtaskId] = taskIdStr.split('.').map(Number); + const parentTask = tasks.find((t) => t.id === parentId); + parentIdForSubtask = parentId; // Store parent ID if it's a subtask - if (parentTask && parentTask.subtasks) { - task = parentTask.subtasks.find((st) => st.id === subtaskId); - } - } else { - // Regular task - task = tasks.find((t) => String(t.id) === taskIdStr); - } + if (parentTask && parentTask.subtasks) { + task = parentTask.subtasks.find((st) => st.id === subtaskId); + } + } else { + // Regular task + task = tasks.find((t) => String(t.id) === taskIdStr); + } - if (!task) { - return false; // Task doesn't exist, can't create circular dependency - } + if (!task) { + return false; // Task doesn't exist, can't create circular dependency + } - // No dependencies, can't create circular dependency - if (!task.dependencies || task.dependencies.length === 0) { - return false; - } + // No dependencies, can't create circular dependency + if (!task.dependencies || task.dependencies.length === 0) { + return false; + } - // Check each dependency recursively - const newChain = [...chain, taskIdStr]; // Use taskIdStr for consistency - return task.dependencies.some((depId) => { - let normalizedDepId = String(depId); - // Normalize relative subtask dependencies - if (typeof depId === "number" && parentIdForSubtask !== null) { - // If the current task is a subtask AND the dependency is a number, - // assume it refers to a sibling subtask. - normalizedDepId = `${parentIdForSubtask}.${depId}`; - } - // Pass the normalized ID to the recursive call - return isCircularDependency(tasks, normalizedDepId, newChain); - }); + // Check each dependency recursively + const newChain = [...chain, taskIdStr]; // Use taskIdStr for consistency + return task.dependencies.some((depId) => { + let normalizedDepId = String(depId); + // Normalize relative subtask dependencies + if (typeof depId === 'number' && parentIdForSubtask !== null) { + // If the current task is a subtask AND the dependency is a number, + // assume it refers to a sibling subtask. + normalizedDepId = `${parentIdForSubtask}.${depId}`; + } + // Pass the normalized ID to the recursive call + return isCircularDependency(tasks, normalizedDepId, newChain); + }); } /** @@ -401,96 +401,96 @@ function isCircularDependency(tasks, taskId, chain = []) { * @returns {Object} Validation result with valid flag and issues array */ function validateTaskDependencies(tasks) { - const issues = []; + const issues = []; - // Check each task's dependencies - tasks.forEach((task) => { - if (!task.dependencies) { - return; // No dependencies to validate - } + // Check each task's dependencies + tasks.forEach((task) => { + if (!task.dependencies) { + return; // No dependencies to validate + } - task.dependencies.forEach((depId) => { - // Check for self-dependencies - if (String(depId) === String(task.id)) { - issues.push({ - type: "self", - taskId: task.id, - message: `Task ${task.id} depends on itself`, - }); - return; - } + task.dependencies.forEach((depId) => { + // Check for self-dependencies + if (String(depId) === String(task.id)) { + issues.push({ + type: 'self', + taskId: task.id, + message: `Task ${task.id} depends on itself` + }); + return; + } - // Check if dependency exists - if (!taskExists(tasks, depId)) { - issues.push({ - type: "missing", - taskId: task.id, - dependencyId: depId, - message: `Task ${task.id} depends on non-existent task ${depId}`, - }); - } - }); + // Check if dependency exists + if (!taskExists(tasks, depId)) { + issues.push({ + type: 'missing', + taskId: task.id, + dependencyId: depId, + message: `Task ${task.id} depends on non-existent task ${depId}` + }); + } + }); - // Check for circular dependencies - if (isCircularDependency(tasks, task.id)) { - issues.push({ - type: "circular", - taskId: task.id, - message: `Task ${task.id} is part of a circular dependency chain`, - }); - } + // Check for circular dependencies + if (isCircularDependency(tasks, task.id)) { + issues.push({ + type: 'circular', + taskId: task.id, + message: `Task ${task.id} is part of a circular dependency chain` + }); + } - // Check subtask dependencies if they exist - if (task.subtasks && task.subtasks.length > 0) { - task.subtasks.forEach((subtask) => { - if (!subtask.dependencies) { - return; // No dependencies to validate - } + // Check subtask dependencies if they exist + if (task.subtasks && task.subtasks.length > 0) { + task.subtasks.forEach((subtask) => { + if (!subtask.dependencies) { + return; // No dependencies to validate + } - // Create a full subtask ID for reference - const fullSubtaskId = `${task.id}.${subtask.id}`; + // Create a full subtask ID for reference + const fullSubtaskId = `${task.id}.${subtask.id}`; - subtask.dependencies.forEach((depId) => { - // Check for self-dependencies in subtasks - if ( - String(depId) === String(fullSubtaskId) || - (typeof depId === "number" && depId === subtask.id) - ) { - issues.push({ - type: "self", - taskId: fullSubtaskId, - message: `Subtask ${fullSubtaskId} depends on itself`, - }); - return; - } + subtask.dependencies.forEach((depId) => { + // Check for self-dependencies in subtasks + if ( + String(depId) === String(fullSubtaskId) || + (typeof depId === 'number' && depId === subtask.id) + ) { + issues.push({ + type: 'self', + taskId: fullSubtaskId, + message: `Subtask ${fullSubtaskId} depends on itself` + }); + return; + } - // Check if dependency exists - if (!taskExists(tasks, depId)) { - issues.push({ - type: "missing", - taskId: fullSubtaskId, - dependencyId: depId, - message: `Subtask ${fullSubtaskId} depends on non-existent task/subtask ${depId}`, - }); - } - }); + // Check if dependency exists + if (!taskExists(tasks, depId)) { + issues.push({ + type: 'missing', + taskId: fullSubtaskId, + dependencyId: depId, + message: `Subtask ${fullSubtaskId} depends on non-existent task/subtask ${depId}` + }); + } + }); - // Check for circular dependencies in subtasks - if (isCircularDependency(tasks, fullSubtaskId)) { - issues.push({ - type: "circular", - taskId: fullSubtaskId, - message: `Subtask ${fullSubtaskId} is part of a circular dependency chain`, - }); - } - }); - } - }); + // Check for circular dependencies in subtasks + if (isCircularDependency(tasks, fullSubtaskId)) { + issues.push({ + type: 'circular', + taskId: fullSubtaskId, + message: `Subtask ${fullSubtaskId} is part of a circular dependency chain` + }); + } + }); + } + }); - return { - valid: issues.length === 0, - issues, - }; + return { + valid: issues.length === 0, + issues + }; } /** @@ -499,23 +499,23 @@ function validateTaskDependencies(tasks) { * @returns {Object} Updated tasks data with duplicates removed */ function removeDuplicateDependencies(tasksData) { - const tasks = tasksData.tasks.map((task) => { - if (!task.dependencies) { - return task; - } + const tasks = tasksData.tasks.map((task) => { + if (!task.dependencies) { + return task; + } - // Convert to Set and back to array to remove duplicates - const uniqueDeps = [...new Set(task.dependencies)]; - return { - ...task, - dependencies: uniqueDeps, - }; - }); + // Convert to Set and back to array to remove duplicates + const uniqueDeps = [...new Set(task.dependencies)]; + return { + ...task, + dependencies: uniqueDeps + }; + }); - return { - ...tasksData, - tasks, - }; + return { + ...tasksData, + tasks + }; } /** @@ -524,38 +524,38 @@ function removeDuplicateDependencies(tasksData) { * @returns {Object} Updated tasks data with invalid subtask dependencies removed */ function cleanupSubtaskDependencies(tasksData) { - const tasks = tasksData.tasks.map((task) => { - // Handle task's own dependencies - if (task.dependencies) { - task.dependencies = task.dependencies.filter((depId) => { - // Keep only dependencies that exist - return taskExists(tasksData.tasks, depId); - }); - } + const tasks = tasksData.tasks.map((task) => { + // Handle task's own dependencies + if (task.dependencies) { + task.dependencies = task.dependencies.filter((depId) => { + // Keep only dependencies that exist + return taskExists(tasksData.tasks, depId); + }); + } - // Handle subtask dependencies - if (task.subtasks) { - task.subtasks = task.subtasks.map((subtask) => { - if (!subtask.dependencies) { - return subtask; - } + // Handle subtask dependencies + if (task.subtasks) { + task.subtasks = task.subtasks.map((subtask) => { + if (!subtask.dependencies) { + return subtask; + } - // Filter out dependencies to non-existent subtasks - subtask.dependencies = subtask.dependencies.filter((depId) => { - return taskExists(tasksData.tasks, depId); - }); + // Filter out dependencies to non-existent subtasks + subtask.dependencies = subtask.dependencies.filter((depId) => { + return taskExists(tasksData.tasks, depId); + }); - return subtask; - }); - } + return subtask; + }); + } - return task; - }); + return task; + }); - return { - ...tasksData, - tasks, - }; + return { + ...tasksData, + tasks + }; } /** @@ -563,94 +563,94 @@ function cleanupSubtaskDependencies(tasksData) { * @param {string} tasksPath - Path to tasks.json */ async function validateDependenciesCommand(tasksPath, options = {}) { - log("info", "Checking for invalid dependencies in task files..."); + log('info', 'Checking for invalid dependencies in task files...'); - // Read tasks data - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log("error", "No valid tasks found in tasks.json"); - process.exit(1); - } + // Read tasks data + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log('error', 'No valid tasks found in tasks.json'); + process.exit(1); + } - // Count of tasks and subtasks for reporting - const taskCount = data.tasks.length; - let subtaskCount = 0; - data.tasks.forEach((task) => { - if (task.subtasks && Array.isArray(task.subtasks)) { - subtaskCount += task.subtasks.length; - } - }); + // Count of tasks and subtasks for reporting + const taskCount = data.tasks.length; + let subtaskCount = 0; + data.tasks.forEach((task) => { + if (task.subtasks && Array.isArray(task.subtasks)) { + subtaskCount += task.subtasks.length; + } + }); - log( - "info", - `Analyzing dependencies for ${taskCount} tasks and ${subtaskCount} subtasks...` - ); + log( + 'info', + `Analyzing dependencies for ${taskCount} tasks and ${subtaskCount} subtasks...` + ); - try { - // Directly call the validation function - const validationResult = validateTaskDependencies(data.tasks); + try { + // Directly call the validation function + const validationResult = validateTaskDependencies(data.tasks); - if (!validationResult.valid) { - log( - "error", - `Dependency validation failed. Found ${validationResult.issues.length} issue(s):` - ); - validationResult.issues.forEach((issue) => { - let errorMsg = ` [${issue.type.toUpperCase()}] Task ${issue.taskId}: ${issue.message}`; - if (issue.dependencyId) { - errorMsg += ` (Dependency: ${issue.dependencyId})`; - } - log("error", errorMsg); // Log each issue as an error - }); + if (!validationResult.valid) { + log( + 'error', + `Dependency validation failed. Found ${validationResult.issues.length} issue(s):` + ); + validationResult.issues.forEach((issue) => { + let errorMsg = ` [${issue.type.toUpperCase()}] Task ${issue.taskId}: ${issue.message}`; + if (issue.dependencyId) { + errorMsg += ` (Dependency: ${issue.dependencyId})`; + } + log('error', errorMsg); // Log each issue as an error + }); - // Optionally exit if validation fails, depending on desired behavior - // process.exit(1); // Uncomment if validation failure should stop the process + // Optionally exit if validation fails, depending on desired behavior + // process.exit(1); // Uncomment if validation failure should stop the process - // Display summary box even on failure, showing issues found - if (!isSilentMode()) { - console.log( - boxen( - chalk.red(`Dependency Validation FAILED\n\n`) + - `${chalk.cyan("Tasks checked:")} ${taskCount}\n` + - `${chalk.cyan("Subtasks checked:")} ${subtaskCount}\n` + - `${chalk.red("Issues found:")} ${validationResult.issues.length}`, // Display count from result - { - padding: 1, - borderColor: "red", - borderStyle: "round", - margin: { top: 1, bottom: 1 }, - } - ) - ); - } - } else { - log( - "success", - "No invalid dependencies found - all dependencies are valid" - ); + // Display summary box even on failure, showing issues found + if (!isSilentMode()) { + console.log( + boxen( + chalk.red(`Dependency Validation FAILED\n\n`) + + `${chalk.cyan('Tasks checked:')} ${taskCount}\n` + + `${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` + + `${chalk.red('Issues found:')} ${validationResult.issues.length}`, // Display count from result + { + padding: 1, + borderColor: 'red', + borderStyle: 'round', + margin: { top: 1, bottom: 1 } + } + ) + ); + } + } else { + log( + 'success', + 'No invalid dependencies found - all dependencies are valid' + ); - // Show validation summary - only if not in silent mode - if (!isSilentMode()) { - console.log( - boxen( - chalk.green(`All Dependencies Are Valid\n\n`) + - `${chalk.cyan("Tasks checked:")} ${taskCount}\n` + - `${chalk.cyan("Subtasks checked:")} ${subtaskCount}\n` + - `${chalk.cyan("Total dependencies verified:")} ${countAllDependencies(data.tasks)}`, - { - padding: 1, - borderColor: "green", - borderStyle: "round", - margin: { top: 1, bottom: 1 }, - } - ) - ); - } - } - } catch (error) { - log("error", "Error validating dependencies:", error); - process.exit(1); - } + // Show validation summary - only if not in silent mode + if (!isSilentMode()) { + console.log( + boxen( + chalk.green(`All Dependencies Are Valid\n\n`) + + `${chalk.cyan('Tasks checked:')} ${taskCount}\n` + + `${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` + + `${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`, + { + padding: 1, + borderColor: 'green', + borderStyle: 'round', + margin: { top: 1, bottom: 1 } + } + ) + ); + } + } + } catch (error) { + log('error', 'Error validating dependencies:', error); + process.exit(1); + } } /** @@ -659,25 +659,25 @@ async function validateDependenciesCommand(tasksPath, options = {}) { * @returns {number} - Total number of dependencies */ function countAllDependencies(tasks) { - let count = 0; + let count = 0; - tasks.forEach((task) => { - // Count main task dependencies - if (task.dependencies && Array.isArray(task.dependencies)) { - count += task.dependencies.length; - } + tasks.forEach((task) => { + // Count main task dependencies + if (task.dependencies && Array.isArray(task.dependencies)) { + count += task.dependencies.length; + } - // Count subtask dependencies - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach((subtask) => { - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - count += subtask.dependencies.length; - } - }); - } - }); + // Count subtask dependencies + if (task.subtasks && Array.isArray(task.subtasks)) { + task.subtasks.forEach((subtask) => { + if (subtask.dependencies && Array.isArray(subtask.dependencies)) { + count += subtask.dependencies.length; + } + }); + } + }); - return count; + return count; } /** @@ -686,387 +686,387 @@ function countAllDependencies(tasks) { * @param {Object} options - Options object */ async function fixDependenciesCommand(tasksPath, options = {}) { - log("info", "Checking for and fixing invalid dependencies in tasks.json..."); + log('info', 'Checking for and fixing invalid dependencies in tasks.json...'); - try { - // Read tasks data - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log("error", "No valid tasks found in tasks.json"); - process.exit(1); - } + try { + // Read tasks data + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log('error', 'No valid tasks found in tasks.json'); + process.exit(1); + } - // Create a deep copy of the original data for comparison - const originalData = JSON.parse(JSON.stringify(data)); + // Create a deep copy of the original data for comparison + const originalData = JSON.parse(JSON.stringify(data)); - // Track fixes for reporting - const stats = { - nonExistentDependenciesRemoved: 0, - selfDependenciesRemoved: 0, - duplicateDependenciesRemoved: 0, - circularDependenciesFixed: 0, - tasksFixed: 0, - subtasksFixed: 0, - }; + // Track fixes for reporting + const stats = { + nonExistentDependenciesRemoved: 0, + selfDependenciesRemoved: 0, + duplicateDependenciesRemoved: 0, + circularDependenciesFixed: 0, + tasksFixed: 0, + subtasksFixed: 0 + }; - // First phase: Remove duplicate dependencies in tasks - data.tasks.forEach((task) => { - if (task.dependencies && Array.isArray(task.dependencies)) { - const uniqueDeps = new Set(); - const originalLength = task.dependencies.length; - task.dependencies = task.dependencies.filter((depId) => { - const depIdStr = String(depId); - if (uniqueDeps.has(depIdStr)) { - log( - "info", - `Removing duplicate dependency from task ${task.id}: ${depId}` - ); - stats.duplicateDependenciesRemoved++; - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - if (task.dependencies.length < originalLength) { - stats.tasksFixed++; - } - } + // First phase: Remove duplicate dependencies in tasks + data.tasks.forEach((task) => { + if (task.dependencies && Array.isArray(task.dependencies)) { + const uniqueDeps = new Set(); + const originalLength = task.dependencies.length; + task.dependencies = task.dependencies.filter((depId) => { + const depIdStr = String(depId); + if (uniqueDeps.has(depIdStr)) { + log( + 'info', + `Removing duplicate dependency from task ${task.id}: ${depId}` + ); + stats.duplicateDependenciesRemoved++; + return false; + } + uniqueDeps.add(depIdStr); + return true; + }); + if (task.dependencies.length < originalLength) { + stats.tasksFixed++; + } + } - // Check for duplicates in subtasks - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach((subtask) => { - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - const uniqueDeps = new Set(); - const originalLength = subtask.dependencies.length; - subtask.dependencies = subtask.dependencies.filter((depId) => { - let depIdStr = String(depId); - if (typeof depId === "number" && depId < 100) { - depIdStr = `${task.id}.${depId}`; - } - if (uniqueDeps.has(depIdStr)) { - log( - "info", - `Removing duplicate dependency from subtask ${task.id}.${subtask.id}: ${depId}` - ); - stats.duplicateDependenciesRemoved++; - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - if (subtask.dependencies.length < originalLength) { - stats.subtasksFixed++; - } - } - }); - } - }); + // Check for duplicates in subtasks + if (task.subtasks && Array.isArray(task.subtasks)) { + task.subtasks.forEach((subtask) => { + if (subtask.dependencies && Array.isArray(subtask.dependencies)) { + const uniqueDeps = new Set(); + const originalLength = subtask.dependencies.length; + subtask.dependencies = subtask.dependencies.filter((depId) => { + let depIdStr = String(depId); + if (typeof depId === 'number' && depId < 100) { + depIdStr = `${task.id}.${depId}`; + } + if (uniqueDeps.has(depIdStr)) { + log( + 'info', + `Removing duplicate dependency from subtask ${task.id}.${subtask.id}: ${depId}` + ); + stats.duplicateDependenciesRemoved++; + return false; + } + uniqueDeps.add(depIdStr); + return true; + }); + if (subtask.dependencies.length < originalLength) { + stats.subtasksFixed++; + } + } + }); + } + }); - // Create validity maps for tasks and subtasks - const validTaskIds = new Set(data.tasks.map((t) => t.id)); - const validSubtaskIds = new Set(); - data.tasks.forEach((task) => { - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach((subtask) => { - validSubtaskIds.add(`${task.id}.${subtask.id}`); - }); - } - }); + // Create validity maps for tasks and subtasks + const validTaskIds = new Set(data.tasks.map((t) => t.id)); + const validSubtaskIds = new Set(); + data.tasks.forEach((task) => { + if (task.subtasks && Array.isArray(task.subtasks)) { + task.subtasks.forEach((subtask) => { + validSubtaskIds.add(`${task.id}.${subtask.id}`); + }); + } + }); - // Second phase: Remove invalid task dependencies (non-existent tasks) - data.tasks.forEach((task) => { - if (task.dependencies && Array.isArray(task.dependencies)) { - const originalLength = task.dependencies.length; - task.dependencies = task.dependencies.filter((depId) => { - const isSubtask = typeof depId === "string" && depId.includes("."); + // Second phase: Remove invalid task dependencies (non-existent tasks) + data.tasks.forEach((task) => { + if (task.dependencies && Array.isArray(task.dependencies)) { + const originalLength = task.dependencies.length; + task.dependencies = task.dependencies.filter((depId) => { + const isSubtask = typeof depId === 'string' && depId.includes('.'); - if (isSubtask) { - // Check if the subtask exists - if (!validSubtaskIds.has(depId)) { - log( - "info", - `Removing invalid subtask dependency from task ${task.id}: ${depId} (subtask does not exist)` - ); - stats.nonExistentDependenciesRemoved++; - return false; - } - return true; - } else { - // Check if the task exists - const numericId = - typeof depId === "string" ? parseInt(depId, 10) : depId; - if (!validTaskIds.has(numericId)) { - log( - "info", - `Removing invalid task dependency from task ${task.id}: ${depId} (task does not exist)` - ); - stats.nonExistentDependenciesRemoved++; - return false; - } - return true; - } - }); + if (isSubtask) { + // Check if the subtask exists + if (!validSubtaskIds.has(depId)) { + log( + 'info', + `Removing invalid subtask dependency from task ${task.id}: ${depId} (subtask does not exist)` + ); + stats.nonExistentDependenciesRemoved++; + return false; + } + return true; + } else { + // Check if the task exists + const numericId = + typeof depId === 'string' ? parseInt(depId, 10) : depId; + if (!validTaskIds.has(numericId)) { + log( + 'info', + `Removing invalid task dependency from task ${task.id}: ${depId} (task does not exist)` + ); + stats.nonExistentDependenciesRemoved++; + return false; + } + return true; + } + }); - if (task.dependencies.length < originalLength) { - stats.tasksFixed++; - } - } + if (task.dependencies.length < originalLength) { + stats.tasksFixed++; + } + } - // Check subtask dependencies for invalid references - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach((subtask) => { - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - const originalLength = subtask.dependencies.length; - const subtaskId = `${task.id}.${subtask.id}`; + // Check subtask dependencies for invalid references + if (task.subtasks && Array.isArray(task.subtasks)) { + task.subtasks.forEach((subtask) => { + if (subtask.dependencies && Array.isArray(subtask.dependencies)) { + const originalLength = subtask.dependencies.length; + const subtaskId = `${task.id}.${subtask.id}`; - // First check for self-dependencies - const hasSelfDependency = subtask.dependencies.some((depId) => { - if (typeof depId === "string" && depId.includes(".")) { - return depId === subtaskId; - } else if (typeof depId === "number" && depId < 100) { - return depId === subtask.id; - } - return false; - }); + // First check for self-dependencies + const hasSelfDependency = subtask.dependencies.some((depId) => { + if (typeof depId === 'string' && depId.includes('.')) { + return depId === subtaskId; + } else if (typeof depId === 'number' && depId < 100) { + return depId === subtask.id; + } + return false; + }); - if (hasSelfDependency) { - subtask.dependencies = subtask.dependencies.filter((depId) => { - const normalizedDepId = - typeof depId === "number" && depId < 100 - ? `${task.id}.${depId}` - : String(depId); + if (hasSelfDependency) { + subtask.dependencies = subtask.dependencies.filter((depId) => { + const normalizedDepId = + typeof depId === 'number' && depId < 100 + ? `${task.id}.${depId}` + : String(depId); - if (normalizedDepId === subtaskId) { - log( - "info", - `Removing self-dependency from subtask ${subtaskId}` - ); - stats.selfDependenciesRemoved++; - return false; - } - return true; - }); - } + if (normalizedDepId === subtaskId) { + log( + 'info', + `Removing self-dependency from subtask ${subtaskId}` + ); + stats.selfDependenciesRemoved++; + return false; + } + return true; + }); + } - // Then check for non-existent dependencies - subtask.dependencies = subtask.dependencies.filter((depId) => { - if (typeof depId === "string" && depId.includes(".")) { - if (!validSubtaskIds.has(depId)) { - log( - "info", - `Removing invalid subtask dependency from subtask ${subtaskId}: ${depId} (subtask does not exist)` - ); - stats.nonExistentDependenciesRemoved++; - return false; - } - return true; - } + // Then check for non-existent dependencies + subtask.dependencies = subtask.dependencies.filter((depId) => { + if (typeof depId === 'string' && depId.includes('.')) { + if (!validSubtaskIds.has(depId)) { + log( + 'info', + `Removing invalid subtask dependency from subtask ${subtaskId}: ${depId} (subtask does not exist)` + ); + stats.nonExistentDependenciesRemoved++; + return false; + } + return true; + } - // Handle numeric dependencies - const numericId = - typeof depId === "number" ? depId : parseInt(depId, 10); + // Handle numeric dependencies + const numericId = + typeof depId === 'number' ? depId : parseInt(depId, 10); - // Small numbers likely refer to subtasks in the same task - if (numericId < 100) { - const fullSubtaskId = `${task.id}.${numericId}`; + // Small numbers likely refer to subtasks in the same task + if (numericId < 100) { + const fullSubtaskId = `${task.id}.${numericId}`; - if (!validSubtaskIds.has(fullSubtaskId)) { - log( - "info", - `Removing invalid subtask dependency from subtask ${subtaskId}: ${numericId}` - ); - stats.nonExistentDependenciesRemoved++; - return false; - } + if (!validSubtaskIds.has(fullSubtaskId)) { + log( + 'info', + `Removing invalid subtask dependency from subtask ${subtaskId}: ${numericId}` + ); + stats.nonExistentDependenciesRemoved++; + return false; + } - return true; - } + return true; + } - // Otherwise it's a task reference - if (!validTaskIds.has(numericId)) { - log( - "info", - `Removing invalid task dependency from subtask ${subtaskId}: ${numericId}` - ); - stats.nonExistentDependenciesRemoved++; - return false; - } + // Otherwise it's a task reference + if (!validTaskIds.has(numericId)) { + log( + 'info', + `Removing invalid task dependency from subtask ${subtaskId}: ${numericId}` + ); + stats.nonExistentDependenciesRemoved++; + return false; + } - return true; - }); + return true; + }); - if (subtask.dependencies.length < originalLength) { - stats.subtasksFixed++; - } - } - }); - } - }); + if (subtask.dependencies.length < originalLength) { + stats.subtasksFixed++; + } + } + }); + } + }); - // Third phase: Check for circular dependencies - log("info", "Checking for circular dependencies..."); + // Third phase: Check for circular dependencies + log('info', 'Checking for circular dependencies...'); - // Build the dependency map for subtasks - const subtaskDependencyMap = new Map(); - data.tasks.forEach((task) => { - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach((subtask) => { - const subtaskId = `${task.id}.${subtask.id}`; + // Build the dependency map for subtasks + const subtaskDependencyMap = new Map(); + data.tasks.forEach((task) => { + if (task.subtasks && Array.isArray(task.subtasks)) { + task.subtasks.forEach((subtask) => { + const subtaskId = `${task.id}.${subtask.id}`; - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - const normalizedDeps = subtask.dependencies.map((depId) => { - if (typeof depId === "string" && depId.includes(".")) { - return depId; - } else if (typeof depId === "number" && depId < 100) { - return `${task.id}.${depId}`; - } - return String(depId); - }); - subtaskDependencyMap.set(subtaskId, normalizedDeps); - } else { - subtaskDependencyMap.set(subtaskId, []); - } - }); - } - }); + if (subtask.dependencies && Array.isArray(subtask.dependencies)) { + const normalizedDeps = subtask.dependencies.map((depId) => { + if (typeof depId === 'string' && depId.includes('.')) { + return depId; + } else if (typeof depId === 'number' && depId < 100) { + return `${task.id}.${depId}`; + } + return String(depId); + }); + subtaskDependencyMap.set(subtaskId, normalizedDeps); + } else { + subtaskDependencyMap.set(subtaskId, []); + } + }); + } + }); - // Check for and fix circular dependencies - for (const [subtaskId, dependencies] of subtaskDependencyMap.entries()) { - const visited = new Set(); - const recursionStack = new Set(); + // Check for and fix circular dependencies + for (const [subtaskId, dependencies] of subtaskDependencyMap.entries()) { + const visited = new Set(); + const recursionStack = new Set(); - // Detect cycles - const cycleEdges = findCycles( - subtaskId, - subtaskDependencyMap, - visited, - recursionStack - ); + // Detect cycles + const cycleEdges = findCycles( + subtaskId, + subtaskDependencyMap, + visited, + recursionStack + ); - if (cycleEdges.length > 0) { - const [taskId, subtaskNum] = subtaskId - .split(".") - .map((part) => Number(part)); - const task = data.tasks.find((t) => t.id === taskId); + if (cycleEdges.length > 0) { + const [taskId, subtaskNum] = subtaskId + .split('.') + .map((part) => Number(part)); + const task = data.tasks.find((t) => t.id === taskId); - if (task && task.subtasks) { - const subtask = task.subtasks.find((st) => st.id === subtaskNum); + if (task && task.subtasks) { + const subtask = task.subtasks.find((st) => st.id === subtaskNum); - if (subtask && subtask.dependencies) { - const originalLength = subtask.dependencies.length; + if (subtask && subtask.dependencies) { + const originalLength = subtask.dependencies.length; - const edgesToRemove = cycleEdges.map((edge) => { - if (edge.includes(".")) { - const [depTaskId, depSubtaskId] = edge - .split(".") - .map((part) => Number(part)); + const edgesToRemove = cycleEdges.map((edge) => { + if (edge.includes('.')) { + const [depTaskId, depSubtaskId] = edge + .split('.') + .map((part) => Number(part)); - if (depTaskId === taskId) { - return depSubtaskId; - } + if (depTaskId === taskId) { + return depSubtaskId; + } - return edge; - } + return edge; + } - return Number(edge); - }); + return Number(edge); + }); - subtask.dependencies = subtask.dependencies.filter((depId) => { - const normalizedDepId = - typeof depId === "number" && depId < 100 - ? `${taskId}.${depId}` - : String(depId); + subtask.dependencies = subtask.dependencies.filter((depId) => { + const normalizedDepId = + typeof depId === 'number' && depId < 100 + ? `${taskId}.${depId}` + : String(depId); - if ( - edgesToRemove.includes(depId) || - edgesToRemove.includes(normalizedDepId) - ) { - log( - "info", - `Breaking circular dependency: Removing ${normalizedDepId} from subtask ${subtaskId}` - ); - stats.circularDependenciesFixed++; - return false; - } - return true; - }); + if ( + edgesToRemove.includes(depId) || + edgesToRemove.includes(normalizedDepId) + ) { + log( + 'info', + `Breaking circular dependency: Removing ${normalizedDepId} from subtask ${subtaskId}` + ); + stats.circularDependenciesFixed++; + return false; + } + return true; + }); - if (subtask.dependencies.length < originalLength) { - stats.subtasksFixed++; - } - } - } - } - } + if (subtask.dependencies.length < originalLength) { + stats.subtasksFixed++; + } + } + } + } + } - // Check if any changes were made by comparing with original data - const dataChanged = JSON.stringify(data) !== JSON.stringify(originalData); + // Check if any changes were made by comparing with original data + const dataChanged = JSON.stringify(data) !== JSON.stringify(originalData); - if (dataChanged) { - // Save the changes - writeJSON(tasksPath, data); - log("success", "Fixed dependency issues in tasks.json"); + if (dataChanged) { + // Save the changes + writeJSON(tasksPath, data); + log('success', 'Fixed dependency issues in tasks.json'); - // Regenerate task files - log("info", "Regenerating task files to reflect dependency changes..."); - await generateTaskFiles(tasksPath, path.dirname(tasksPath)); - } else { - log("info", "No changes needed to fix dependencies"); - } + // Regenerate task files + log('info', 'Regenerating task files to reflect dependency changes...'); + await generateTaskFiles(tasksPath, path.dirname(tasksPath)); + } else { + log('info', 'No changes needed to fix dependencies'); + } - // Show detailed statistics report - const totalFixedAll = - stats.nonExistentDependenciesRemoved + - stats.selfDependenciesRemoved + - stats.duplicateDependenciesRemoved + - stats.circularDependenciesFixed; + // Show detailed statistics report + const totalFixedAll = + stats.nonExistentDependenciesRemoved + + stats.selfDependenciesRemoved + + stats.duplicateDependenciesRemoved + + stats.circularDependenciesFixed; - if (!isSilentMode()) { - if (totalFixedAll > 0) { - log("success", `Fixed ${totalFixedAll} dependency issues in total!`); + if (!isSilentMode()) { + if (totalFixedAll > 0) { + log('success', `Fixed ${totalFixedAll} dependency issues in total!`); - console.log( - boxen( - chalk.green(`Dependency Fixes Summary:\n\n`) + - `${chalk.cyan("Invalid dependencies removed:")} ${stats.nonExistentDependenciesRemoved}\n` + - `${chalk.cyan("Self-dependencies removed:")} ${stats.selfDependenciesRemoved}\n` + - `${chalk.cyan("Duplicate dependencies removed:")} ${stats.duplicateDependenciesRemoved}\n` + - `${chalk.cyan("Circular dependencies fixed:")} ${stats.circularDependenciesFixed}\n\n` + - `${chalk.cyan("Tasks fixed:")} ${stats.tasksFixed}\n` + - `${chalk.cyan("Subtasks fixed:")} ${stats.subtasksFixed}\n`, - { - padding: 1, - borderColor: "green", - borderStyle: "round", - margin: { top: 1, bottom: 1 }, - } - ) - ); - } else { - log( - "success", - "No dependency issues found - all dependencies are valid" - ); + console.log( + boxen( + chalk.green(`Dependency Fixes Summary:\n\n`) + + `${chalk.cyan('Invalid dependencies removed:')} ${stats.nonExistentDependenciesRemoved}\n` + + `${chalk.cyan('Self-dependencies removed:')} ${stats.selfDependenciesRemoved}\n` + + `${chalk.cyan('Duplicate dependencies removed:')} ${stats.duplicateDependenciesRemoved}\n` + + `${chalk.cyan('Circular dependencies fixed:')} ${stats.circularDependenciesFixed}\n\n` + + `${chalk.cyan('Tasks fixed:')} ${stats.tasksFixed}\n` + + `${chalk.cyan('Subtasks fixed:')} ${stats.subtasksFixed}\n`, + { + padding: 1, + borderColor: 'green', + borderStyle: 'round', + margin: { top: 1, bottom: 1 } + } + ) + ); + } else { + log( + 'success', + 'No dependency issues found - all dependencies are valid' + ); - console.log( - boxen( - chalk.green(`All Dependencies Are Valid\n\n`) + - `${chalk.cyan("Tasks checked:")} ${data.tasks.length}\n` + - `${chalk.cyan("Total dependencies verified:")} ${countAllDependencies(data.tasks)}`, - { - padding: 1, - borderColor: "green", - borderStyle: "round", - margin: { top: 1, bottom: 1 }, - } - ) - ); - } - } - } catch (error) { - log("error", "Error in fix-dependencies command:", error); - process.exit(1); - } + console.log( + boxen( + chalk.green(`All Dependencies Are Valid\n\n`) + + `${chalk.cyan('Tasks checked:')} ${data.tasks.length}\n` + + `${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`, + { + padding: 1, + borderColor: 'green', + borderStyle: 'round', + margin: { top: 1, bottom: 1 } + } + ) + ); + } + } + } catch (error) { + log('error', 'Error in fix-dependencies command:', error); + process.exit(1); + } } /** @@ -1075,44 +1075,44 @@ async function fixDependenciesCommand(tasksPath, options = {}) { * @returns {boolean} - True if any changes were made */ function ensureAtLeastOneIndependentSubtask(tasksData) { - if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { - return false; - } + if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { + return false; + } - let changesDetected = false; + let changesDetected = false; - tasksData.tasks.forEach((task) => { - if ( - !task.subtasks || - !Array.isArray(task.subtasks) || - task.subtasks.length === 0 - ) { - return; - } + tasksData.tasks.forEach((task) => { + if ( + !task.subtasks || + !Array.isArray(task.subtasks) || + task.subtasks.length === 0 + ) { + return; + } - // Check if any subtask has no dependencies - const hasIndependentSubtask = task.subtasks.some( - (st) => - !st.dependencies || - !Array.isArray(st.dependencies) || - st.dependencies.length === 0 - ); + // Check if any subtask has no dependencies + const hasIndependentSubtask = task.subtasks.some( + (st) => + !st.dependencies || + !Array.isArray(st.dependencies) || + st.dependencies.length === 0 + ); - if (!hasIndependentSubtask) { - // Find the first subtask and clear its dependencies - if (task.subtasks.length > 0) { - const firstSubtask = task.subtasks[0]; - log( - "debug", - `Ensuring at least one independent subtask: Clearing dependencies for subtask ${task.id}.${firstSubtask.id}` - ); - firstSubtask.dependencies = []; - changesDetected = true; - } - } - }); + if (!hasIndependentSubtask) { + // Find the first subtask and clear its dependencies + if (task.subtasks.length > 0) { + const firstSubtask = task.subtasks[0]; + log( + 'debug', + `Ensuring at least one independent subtask: Clearing dependencies for subtask ${task.id}.${firstSubtask.id}` + ); + firstSubtask.dependencies = []; + changesDetected = true; + } + } + }); - return changesDetected; + return changesDetected; } /** @@ -1123,111 +1123,111 @@ function ensureAtLeastOneIndependentSubtask(tasksData) { * @returns {boolean} - True if any changes were made */ function validateAndFixDependencies(tasksData, tasksPath = null) { - if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { - log("error", "Invalid tasks data"); - return false; - } + if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { + log('error', 'Invalid tasks data'); + return false; + } - log("debug", "Validating and fixing dependencies..."); + log('debug', 'Validating and fixing dependencies...'); - // Create a deep copy for comparison - const originalData = JSON.parse(JSON.stringify(tasksData)); + // Create a deep copy for comparison + const originalData = JSON.parse(JSON.stringify(tasksData)); - // 1. Remove duplicate dependencies from tasks and subtasks - tasksData.tasks = tasksData.tasks.map((task) => { - // Handle task dependencies - if (task.dependencies) { - const uniqueDeps = [...new Set(task.dependencies)]; - task.dependencies = uniqueDeps; - } + // 1. Remove duplicate dependencies from tasks and subtasks + tasksData.tasks = tasksData.tasks.map((task) => { + // Handle task dependencies + if (task.dependencies) { + const uniqueDeps = [...new Set(task.dependencies)]; + task.dependencies = uniqueDeps; + } - // Handle subtask dependencies - if (task.subtasks) { - task.subtasks = task.subtasks.map((subtask) => { - if (subtask.dependencies) { - const uniqueDeps = [...new Set(subtask.dependencies)]; - subtask.dependencies = uniqueDeps; - } - return subtask; - }); - } - return task; - }); + // Handle subtask dependencies + if (task.subtasks) { + task.subtasks = task.subtasks.map((subtask) => { + if (subtask.dependencies) { + const uniqueDeps = [...new Set(subtask.dependencies)]; + subtask.dependencies = uniqueDeps; + } + return subtask; + }); + } + return task; + }); - // 2. Remove invalid task dependencies (non-existent tasks) - tasksData.tasks.forEach((task) => { - // Clean up task dependencies - if (task.dependencies) { - task.dependencies = task.dependencies.filter((depId) => { - // Remove self-dependencies - if (String(depId) === String(task.id)) { - return false; - } - // Remove non-existent dependencies - return taskExists(tasksData.tasks, depId); - }); - } + // 2. Remove invalid task dependencies (non-existent tasks) + tasksData.tasks.forEach((task) => { + // Clean up task dependencies + if (task.dependencies) { + task.dependencies = task.dependencies.filter((depId) => { + // Remove self-dependencies + if (String(depId) === String(task.id)) { + return false; + } + // Remove non-existent dependencies + return taskExists(tasksData.tasks, depId); + }); + } - // Clean up subtask dependencies - if (task.subtasks) { - task.subtasks.forEach((subtask) => { - if (subtask.dependencies) { - subtask.dependencies = subtask.dependencies.filter((depId) => { - // Handle numeric subtask references - if (typeof depId === "number" && depId < 100) { - const fullSubtaskId = `${task.id}.${depId}`; - return taskExists(tasksData.tasks, fullSubtaskId); - } - // Handle full task/subtask references - return taskExists(tasksData.tasks, depId); - }); - } - }); - } - }); + // Clean up subtask dependencies + if (task.subtasks) { + task.subtasks.forEach((subtask) => { + if (subtask.dependencies) { + subtask.dependencies = subtask.dependencies.filter((depId) => { + // Handle numeric subtask references + if (typeof depId === 'number' && depId < 100) { + const fullSubtaskId = `${task.id}.${depId}`; + return taskExists(tasksData.tasks, fullSubtaskId); + } + // Handle full task/subtask references + return taskExists(tasksData.tasks, depId); + }); + } + }); + } + }); - // 3. Ensure at least one subtask has no dependencies in each task - tasksData.tasks.forEach((task) => { - if (task.subtasks && task.subtasks.length > 0) { - const hasIndependentSubtask = task.subtasks.some( - (st) => - !st.dependencies || - !Array.isArray(st.dependencies) || - st.dependencies.length === 0 - ); + // 3. Ensure at least one subtask has no dependencies in each task + tasksData.tasks.forEach((task) => { + if (task.subtasks && task.subtasks.length > 0) { + const hasIndependentSubtask = task.subtasks.some( + (st) => + !st.dependencies || + !Array.isArray(st.dependencies) || + st.dependencies.length === 0 + ); - if (!hasIndependentSubtask) { - task.subtasks[0].dependencies = []; - } - } - }); + if (!hasIndependentSubtask) { + task.subtasks[0].dependencies = []; + } + } + }); - // Check if any changes were made by comparing with original data - const changesDetected = - JSON.stringify(tasksData) !== JSON.stringify(originalData); + // Check if any changes were made by comparing with original data + const changesDetected = + JSON.stringify(tasksData) !== JSON.stringify(originalData); - // Save changes if needed - if (tasksPath && changesDetected) { - try { - writeJSON(tasksPath, tasksData); - log("debug", "Saved dependency fixes to tasks.json"); - } catch (error) { - log("error", "Failed to save dependency fixes to tasks.json", error); - } - } + // Save changes if needed + if (tasksPath && changesDetected) { + try { + writeJSON(tasksPath, tasksData); + log('debug', 'Saved dependency fixes to tasks.json'); + } catch (error) { + log('error', 'Failed to save dependency fixes to tasks.json', error); + } + } - return changesDetected; + return changesDetected; } export { - addDependency, - removeDependency, - isCircularDependency, - validateTaskDependencies, - validateDependenciesCommand, - fixDependenciesCommand, - removeDuplicateDependencies, - cleanupSubtaskDependencies, - ensureAtLeastOneIndependentSubtask, - validateAndFixDependencies, + addDependency, + removeDependency, + isCircularDependency, + validateTaskDependencies, + validateDependenciesCommand, + fixDependenciesCommand, + removeDuplicateDependencies, + cleanupSubtaskDependencies, + ensureAtLeastOneIndependentSubtask, + validateAndFixDependencies }; diff --git a/scripts/modules/supported-models.json b/scripts/modules/supported-models.json index fac3da0d..bccdb96e 100644 --- a/scripts/modules/supported-models.json +++ b/scripts/modules/supported-models.json @@ -1,427 +1,427 @@ { - "anthropic": [ - { - "id": "claude-sonnet-4-20250514", - "swe_score": 0.727, - "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 64000 - }, - { - "id": "claude-opus-4-20250514", - "swe_score": 0.725, - "cost_per_1m_tokens": { "input": 15.0, "output": 75.0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 32000 - }, - { - "id": "claude-3-7-sonnet-20250219", - "swe_score": 0.623, - "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 120000 - }, - { - "id": "claude-3-5-sonnet-20241022", - "swe_score": 0.49, - "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 64000 - } - ], - "openai": [ - { - "id": "gpt-4o", - "swe_score": 0.332, - "cost_per_1m_tokens": { "input": 2.5, "output": 10.0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 16384 - }, - { - "id": "o1", - "swe_score": 0.489, - "cost_per_1m_tokens": { "input": 15.0, "output": 60.0 }, - "allowed_roles": ["main"] - }, - { - "id": "o3", - "swe_score": 0.5, - "cost_per_1m_tokens": { "input": 10.0, "output": 40.0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "o3-mini", - "swe_score": 0.493, - "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, - "allowed_roles": ["main"], - "max_tokens": 100000 - }, - { - "id": "o4-mini", - "swe_score": 0.45, - "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "o1-mini", - "swe_score": 0.4, - "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, - "allowed_roles": ["main"] - }, - { - "id": "o1-pro", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 150.0, "output": 600.0 }, - "allowed_roles": ["main"] - }, - { - "id": "gpt-4-5-preview", - "swe_score": 0.38, - "cost_per_1m_tokens": { "input": 75.0, "output": 150.0 }, - "allowed_roles": ["main"] - }, - { - "id": "gpt-4-1-mini", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.4, "output": 1.6 }, - "allowed_roles": ["main"] - }, - { - "id": "gpt-4-1-nano", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.1, "output": 0.4 }, - "allowed_roles": ["main"] - }, - { - "id": "gpt-4o-mini", - "swe_score": 0.3, - "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, - "allowed_roles": ["main"] - }, - { - "id": "gpt-4o-search-preview", - "swe_score": 0.33, - "cost_per_1m_tokens": { "input": 2.5, "output": 10.0 }, - "allowed_roles": ["research"] - }, - { - "id": "gpt-4o-mini-search-preview", - "swe_score": 0.3, - "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, - "allowed_roles": ["research"] - } - ], - "google": [ - { - "id": "gemini-2.5-pro-preview-05-06", - "swe_score": 0.638, - "cost_per_1m_tokens": null, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048000 - }, - { - "id": "gemini-2.5-pro-preview-03-25", - "swe_score": 0.638, - "cost_per_1m_tokens": null, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048000 - }, - { - "id": "gemini-2.5-flash-preview-04-17", - "swe_score": 0, - "cost_per_1m_tokens": null, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048000 - }, - { - "id": "gemini-2.0-flash", - "swe_score": 0.754, - "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048000 - }, - { - "id": "gemini-2.0-flash-lite", - "swe_score": 0, - "cost_per_1m_tokens": null, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048000 - } - ], - "perplexity": [ - { - "id": "sonar-pro", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 3, "output": 15 }, - "allowed_roles": ["main", "research"], - "max_tokens": 8700 - }, - { - "id": "sonar", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 1, "output": 1 }, - "allowed_roles": ["research"], - "max_tokens": 8700 - }, - { - "id": "deep-research", - "swe_score": 0.211, - "cost_per_1m_tokens": { "input": 2, "output": 8 }, - "allowed_roles": ["research"], - "max_tokens": 8700 - }, - { - "id": "sonar-reasoning-pro", - "swe_score": 0.211, - "cost_per_1m_tokens": { "input": 2, "output": 8 }, - "allowed_roles": ["main", "research", "fallback"], - "max_tokens": 8700 - }, - { - "id": "sonar-reasoning", - "swe_score": 0.211, - "cost_per_1m_tokens": { "input": 1, "output": 5 }, - "allowed_roles": ["main", "research", "fallback"], - "max_tokens": 8700 - } - ], - "xai": [ - { - "id": "grok-3", - "name": "Grok 3", - "swe_score": null, - "cost_per_1m_tokens": { "input": 3, "output": 15 }, - "allowed_roles": ["main", "fallback", "research"], - "max_tokens": 131072 - }, - { - "id": "grok-3-fast", - "name": "Grok 3 Fast", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 5, "output": 25 }, - "allowed_roles": ["main", "fallback", "research"], - "max_tokens": 131072 - } - ], - "ollama": [ - { - "id": "devstral:latest", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "qwen3:latest", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "qwen3:14b", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "qwen3:32b", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "mistral-small3.1:latest", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "llama3.3:latest", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - }, - { - "id": "phi4:latest", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"] - } - ], - "openrouter": [ - { - "id": "google/gemini-2.5-flash-preview-05-20", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048576 - }, - { - "id": "google/gemini-2.5-flash-preview-05-20:thinking", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.15, "output": 3.5 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048576 - }, - { - "id": "google/gemini-2.5-pro-exp-03-25", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "deepseek/deepseek-chat-v3-0324:free", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 163840 - }, - { - "id": "deepseek/deepseek-chat-v3-0324", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.27, "output": 1.1 }, - "allowed_roles": ["main"], - "max_tokens": 64000 - }, - { - "id": "openai/gpt-4.1", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 2, "output": 8 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "openai/gpt-4.1-mini", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.4, "output": 1.6 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "openai/gpt-4.1-nano", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.1, "output": 0.4 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "openai/o3", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 10, "output": 40 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 200000 - }, - { - "id": "openai/codex-mini", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 1.5, "output": 6 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 100000 - }, - { - "id": "openai/gpt-4o-mini", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 100000 - }, - { - "id": "openai/o4-mini", - "swe_score": 0.45, - "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 100000 - }, - { - "id": "openai/o4-mini-high", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 100000 - }, - { - "id": "openai/o1-pro", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 150, "output": 600 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 100000 - }, - { - "id": "meta-llama/llama-3.3-70b-instruct", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 120, "output": 600 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1048576 - }, - { - "id": "meta-llama/llama-4-maverick", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.18, "output": 0.6 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "meta-llama/llama-4-scout", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.08, "output": 0.3 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "qwen/qwen-max", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 1.6, "output": 6.4 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 32768 - }, - { - "id": "qwen/qwen-turbo", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.05, "output": 0.2 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 1000000 - }, - { - "id": "qwen/qwen3-235b-a22b", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.14, "output": 2 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 24000 - }, - { - "id": "mistralai/mistral-small-3.1-24b-instruct:free", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 96000 - }, - { - "id": "mistralai/mistral-small-3.1-24b-instruct", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.1, "output": 0.3 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 128000 - }, - { - "id": "mistralai/devstral-small", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.1, "output": 0.3 }, - "allowed_roles": ["main"], - "max_tokens": 110000 - }, - { - "id": "mistralai/mistral-nemo", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.03, "output": 0.07 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 100000 - }, - { - "id": "thudm/glm-4-32b:free", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 32768 - } - ] + "anthropic": [ + { + "id": "claude-sonnet-4-20250514", + "swe_score": 0.727, + "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 64000 + }, + { + "id": "claude-opus-4-20250514", + "swe_score": 0.725, + "cost_per_1m_tokens": { "input": 15.0, "output": 75.0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 32000 + }, + { + "id": "claude-3-7-sonnet-20250219", + "swe_score": 0.623, + "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 120000 + }, + { + "id": "claude-3-5-sonnet-20241022", + "swe_score": 0.49, + "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 64000 + } + ], + "openai": [ + { + "id": "gpt-4o", + "swe_score": 0.332, + "cost_per_1m_tokens": { "input": 2.5, "output": 10.0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 16384 + }, + { + "id": "o1", + "swe_score": 0.489, + "cost_per_1m_tokens": { "input": 15.0, "output": 60.0 }, + "allowed_roles": ["main"] + }, + { + "id": "o3", + "swe_score": 0.5, + "cost_per_1m_tokens": { "input": 10.0, "output": 40.0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "o3-mini", + "swe_score": 0.493, + "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, + "allowed_roles": ["main"], + "max_tokens": 100000 + }, + { + "id": "o4-mini", + "swe_score": 0.45, + "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "o1-mini", + "swe_score": 0.4, + "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, + "allowed_roles": ["main"] + }, + { + "id": "o1-pro", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 150.0, "output": 600.0 }, + "allowed_roles": ["main"] + }, + { + "id": "gpt-4-5-preview", + "swe_score": 0.38, + "cost_per_1m_tokens": { "input": 75.0, "output": 150.0 }, + "allowed_roles": ["main"] + }, + { + "id": "gpt-4-1-mini", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.4, "output": 1.6 }, + "allowed_roles": ["main"] + }, + { + "id": "gpt-4-1-nano", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.1, "output": 0.4 }, + "allowed_roles": ["main"] + }, + { + "id": "gpt-4o-mini", + "swe_score": 0.3, + "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, + "allowed_roles": ["main"] + }, + { + "id": "gpt-4o-search-preview", + "swe_score": 0.33, + "cost_per_1m_tokens": { "input": 2.5, "output": 10.0 }, + "allowed_roles": ["research"] + }, + { + "id": "gpt-4o-mini-search-preview", + "swe_score": 0.3, + "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, + "allowed_roles": ["research"] + } + ], + "google": [ + { + "id": "gemini-2.5-pro-preview-05-06", + "swe_score": 0.638, + "cost_per_1m_tokens": null, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048000 + }, + { + "id": "gemini-2.5-pro-preview-03-25", + "swe_score": 0.638, + "cost_per_1m_tokens": null, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048000 + }, + { + "id": "gemini-2.5-flash-preview-04-17", + "swe_score": 0, + "cost_per_1m_tokens": null, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048000 + }, + { + "id": "gemini-2.0-flash", + "swe_score": 0.754, + "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048000 + }, + { + "id": "gemini-2.0-flash-lite", + "swe_score": 0, + "cost_per_1m_tokens": null, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048000 + } + ], + "perplexity": [ + { + "id": "sonar-pro", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 3, "output": 15 }, + "allowed_roles": ["main", "research"], + "max_tokens": 8700 + }, + { + "id": "sonar", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 1, "output": 1 }, + "allowed_roles": ["research"], + "max_tokens": 8700 + }, + { + "id": "deep-research", + "swe_score": 0.211, + "cost_per_1m_tokens": { "input": 2, "output": 8 }, + "allowed_roles": ["research"], + "max_tokens": 8700 + }, + { + "id": "sonar-reasoning-pro", + "swe_score": 0.211, + "cost_per_1m_tokens": { "input": 2, "output": 8 }, + "allowed_roles": ["main", "research", "fallback"], + "max_tokens": 8700 + }, + { + "id": "sonar-reasoning", + "swe_score": 0.211, + "cost_per_1m_tokens": { "input": 1, "output": 5 }, + "allowed_roles": ["main", "research", "fallback"], + "max_tokens": 8700 + } + ], + "xai": [ + { + "id": "grok-3", + "name": "Grok 3", + "swe_score": null, + "cost_per_1m_tokens": { "input": 3, "output": 15 }, + "allowed_roles": ["main", "fallback", "research"], + "max_tokens": 131072 + }, + { + "id": "grok-3-fast", + "name": "Grok 3 Fast", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 5, "output": 25 }, + "allowed_roles": ["main", "fallback", "research"], + "max_tokens": 131072 + } + ], + "ollama": [ + { + "id": "devstral:latest", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "qwen3:latest", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "qwen3:14b", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "qwen3:32b", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "mistral-small3.1:latest", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "llama3.3:latest", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + }, + { + "id": "phi4:latest", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"] + } + ], + "openrouter": [ + { + "id": "google/gemini-2.5-flash-preview-05-20", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048576 + }, + { + "id": "google/gemini-2.5-flash-preview-05-20:thinking", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.15, "output": 3.5 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048576 + }, + { + "id": "google/gemini-2.5-pro-exp-03-25", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "deepseek/deepseek-chat-v3-0324:free", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 163840 + }, + { + "id": "deepseek/deepseek-chat-v3-0324", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.27, "output": 1.1 }, + "allowed_roles": ["main"], + "max_tokens": 64000 + }, + { + "id": "openai/gpt-4.1", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 2, "output": 8 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "openai/gpt-4.1-mini", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.4, "output": 1.6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "openai/gpt-4.1-nano", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.1, "output": 0.4 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "openai/o3", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 10, "output": 40 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 200000 + }, + { + "id": "openai/codex-mini", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 1.5, "output": 6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "openai/gpt-4o-mini", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "openai/o4-mini", + "swe_score": 0.45, + "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "openai/o4-mini-high", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 1.1, "output": 4.4 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "openai/o1-pro", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 150, "output": 600 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "meta-llama/llama-3.3-70b-instruct", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 120, "output": 600 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048576 + }, + { + "id": "meta-llama/llama-4-maverick", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.18, "output": 0.6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "meta-llama/llama-4-scout", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.08, "output": 0.3 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "qwen/qwen-max", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 1.6, "output": 6.4 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 32768 + }, + { + "id": "qwen/qwen-turbo", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.05, "output": 0.2 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1000000 + }, + { + "id": "qwen/qwen3-235b-a22b", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.14, "output": 2 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 24000 + }, + { + "id": "mistralai/mistral-small-3.1-24b-instruct:free", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 96000 + }, + { + "id": "mistralai/mistral-small-3.1-24b-instruct", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.1, "output": 0.3 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 128000 + }, + { + "id": "mistralai/devstral-small", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.1, "output": 0.3 }, + "allowed_roles": ["main"], + "max_tokens": 110000 + }, + { + "id": "mistralai/mistral-nemo", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.03, "output": 0.07 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "thudm/glm-4-32b:free", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 32768 + } + ] } diff --git a/scripts/modules/task-manager/add-task.js b/scripts/modules/task-manager/add-task.js index e51e0884..57d00172 100644 --- a/scripts/modules/task-manager/add-task.js +++ b/scripts/modules/task-manager/add-task.js @@ -1,42 +1,42 @@ -import path from "path"; -import chalk from "chalk"; -import boxen from "boxen"; -import Table from "cli-table3"; -import { z } from "zod"; -import Fuse from "fuse.js"; // Import Fuse.js for advanced fuzzy search +import path from 'path'; +import chalk from 'chalk'; +import boxen from 'boxen'; +import Table from 'cli-table3'; +import { z } from 'zod'; +import Fuse from 'fuse.js'; // Import Fuse.js for advanced fuzzy search import { - displayBanner, - getStatusWithColor, - startLoadingIndicator, - stopLoadingIndicator, - succeedLoadingIndicator, - failLoadingIndicator, - displayAiUsageSummary, -} from "../ui.js"; -import { readJSON, writeJSON, log as consoleLog, truncate } from "../utils.js"; -import { generateObjectService } from "../ai-services-unified.js"; -import { getDefaultPriority } from "../config-manager.js"; -import generateTaskFiles from "./generate-task-files.js"; + displayBanner, + getStatusWithColor, + startLoadingIndicator, + stopLoadingIndicator, + succeedLoadingIndicator, + failLoadingIndicator, + displayAiUsageSummary +} from '../ui.js'; +import { readJSON, writeJSON, log as consoleLog, truncate } from '../utils.js'; +import { generateObjectService } from '../ai-services-unified.js'; +import { getDefaultPriority } from '../config-manager.js'; +import generateTaskFiles from './generate-task-files.js'; // Define Zod schema for the expected AI output object const AiTaskDataSchema = z.object({ - title: z.string().describe("Clear, concise title for the task"), - description: z - .string() - .describe("A one or two sentence description of the task"), - details: z - .string() - .describe("In-depth implementation details, considerations, and guidance"), - testStrategy: z - .string() - .describe("Detailed approach for verifying task completion"), - dependencies: z - .array(z.number()) - .optional() - .describe( - "Array of task IDs that this task depends on (must be completed before this task can start)" - ), + title: z.string().describe('Clear, concise title for the task'), + description: z + .string() + .describe('A one or two sentence description of the task'), + details: z + .string() + .describe('In-depth implementation details, considerations, and guidance'), + testStrategy: z + .string() + .describe('Detailed approach for verifying task completion'), + dependencies: z + .array(z.number()) + .optional() + .describe( + 'Array of task IDs that this task depends on (must be completed before this task can start)' + ) }); /** @@ -59,790 +59,790 @@ const AiTaskDataSchema = z.object({ * @returns {Promise<object>} An object containing newTaskId and telemetryData */ async function addTask( - tasksPath, - prompt, - dependencies = [], - priority = null, - context = {}, - outputFormat = "text", // Default to text for CLI - manualTaskData = null, - useResearch = false + tasksPath, + prompt, + dependencies = [], + priority = null, + context = {}, + outputFormat = 'text', // Default to text for CLI + manualTaskData = null, + useResearch = false ) { - const { session, mcpLog, projectRoot, commandName, outputType } = context; - const isMCP = !!mcpLog; - - // Create a consistent logFn object regardless of context - const logFn = isMCP - ? mcpLog // Use MCP logger if provided - : { - // Create a wrapper around consoleLog for CLI - info: (...args) => consoleLog("info", ...args), - warn: (...args) => consoleLog("warn", ...args), - error: (...args) => consoleLog("error", ...args), - debug: (...args) => consoleLog("debug", ...args), - success: (...args) => consoleLog("success", ...args), - }; - - const effectivePriority = priority || getDefaultPriority(projectRoot); - - logFn.info( - `Adding new task with prompt: "${prompt}", Priority: ${effectivePriority}, Dependencies: ${dependencies.join(", ") || "None"}, Research: ${useResearch}, ProjectRoot: ${projectRoot}` - ); - - let loadingIndicator = null; - let aiServiceResponse = null; // To store the full response from AI service - - // Create custom reporter that checks for MCP log - const report = (message, level = "info") => { - if (mcpLog) { - mcpLog[level](message); - } else if (outputFormat === "text") { - consoleLog(level, message); - } - }; - - /** - * Recursively builds a dependency graph for a given task - * @param {Array} tasks - All tasks from tasks.json - * @param {number} taskId - ID of the task to analyze - * @param {Set} visited - Set of already visited task IDs - * @param {Map} depthMap - Map of task ID to its depth in the graph - * @param {number} depth - Current depth in the recursion - * @return {Object} Dependency graph data - */ - function buildDependencyGraph( - tasks, - taskId, - visited = new Set(), - depthMap = new Map(), - depth = 0 - ) { - // Skip if we've already visited this task or it doesn't exist - if (visited.has(taskId)) { - return null; - } - - // Find the task - const task = tasks.find((t) => t.id === taskId); - if (!task) { - return null; - } - - // Mark as visited - visited.add(taskId); - - // Update depth if this is a deeper path to this task - if (!depthMap.has(taskId) || depth < depthMap.get(taskId)) { - depthMap.set(taskId, depth); - } - - // Process dependencies - const dependencyData = []; - if (task.dependencies && task.dependencies.length > 0) { - for (const depId of task.dependencies) { - const depData = buildDependencyGraph( - tasks, - depId, - visited, - depthMap, - depth + 1 - ); - if (depData) { - dependencyData.push(depData); - } - } - } - - return { - id: task.id, - title: task.title, - description: task.description, - status: task.status, - dependencies: dependencyData, - }; - } - - try { - // Read the existing tasks - let data = readJSON(tasksPath); - - // If tasks.json doesn't exist or is invalid, create a new one - if (!data || !data.tasks) { - report("tasks.json not found or invalid. Creating a new one.", "info"); - // Create default tasks data structure - data = { - tasks: [], - }; - // Ensure the directory exists and write the new file - writeJSON(tasksPath, data); - report("Created new tasks.json file with empty tasks array.", "info"); - } - - // Find the highest task ID to determine the next ID - const highestId = - data.tasks.length > 0 ? Math.max(...data.tasks.map((t) => t.id)) : 0; - const newTaskId = highestId + 1; - - // Only show UI box for CLI mode - if (outputFormat === "text") { - console.log( - boxen(chalk.white.bold(`Creating New Task #${newTaskId}`), { - padding: 1, - borderColor: "blue", - borderStyle: "round", - margin: { top: 1, bottom: 1 }, - }) - ); - } - - // Validate dependencies before proceeding - const invalidDeps = dependencies.filter((depId) => { - // Ensure depId is parsed as a number for comparison - const numDepId = parseInt(depId, 10); - return isNaN(numDepId) || !data.tasks.some((t) => t.id === numDepId); - }); - - if (invalidDeps.length > 0) { - report( - `The following dependencies do not exist or are invalid: ${invalidDeps.join(", ")}`, - "warn" - ); - report("Removing invalid dependencies...", "info"); - dependencies = dependencies.filter( - (depId) => !invalidDeps.includes(depId) - ); - } - // Ensure dependencies are numbers - const numericDependencies = dependencies.map((dep) => parseInt(dep, 10)); - - // Build dependency graphs for explicitly specified dependencies - const dependencyGraphs = []; - const allRelatedTaskIds = new Set(); - const depthMap = new Map(); - - // First pass: build a complete dependency graph for each specified dependency - for (const depId of numericDependencies) { - const graph = buildDependencyGraph( - data.tasks, - depId, - new Set(), - depthMap - ); - if (graph) { - dependencyGraphs.push(graph); - } - } - - // Second pass: build a set of all related task IDs for flat analysis - for (const [taskId, depth] of depthMap.entries()) { - allRelatedTaskIds.add(taskId); - } - - let taskData; - - // Check if manual task data is provided - if (manualTaskData) { - report("Using manually provided task data", "info"); - taskData = manualTaskData; - report("DEBUG: Taking MANUAL task data path.", "debug"); - - // Basic validation for manual data - if ( - !taskData.title || - typeof taskData.title !== "string" || - !taskData.description || - typeof taskData.description !== "string" - ) { - throw new Error( - "Manual task data must include at least a title and description." - ); - } - } else { - report("DEBUG: Taking AI task generation path.", "debug"); - // --- Refactored AI Interaction --- - report(`Generating task data with AI with prompt:\n${prompt}`, "info"); - - // Create context string for task creation prompt - let contextTasks = ""; - - // Create a dependency map for better understanding of the task relationships - const taskMap = {}; - data.tasks.forEach((t) => { - // For each task, only include id, title, description, and dependencies - taskMap[t.id] = { - id: t.id, - title: t.title, - description: t.description, - dependencies: t.dependencies || [], - status: t.status, - }; - }); - - // CLI-only feedback for the dependency analysis - if (outputFormat === "text") { - console.log( - boxen(chalk.cyan.bold("Task Context Analysis"), { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - margin: { top: 0, bottom: 0 }, - borderColor: "cyan", - borderStyle: "round", - }) - ); - } - - // Initialize variables that will be used in either branch - let uniqueDetailedTasks = []; - let dependentTasks = []; - let promptCategory = null; - - if (numericDependencies.length > 0) { - // If specific dependencies were provided, focus on them - // Get all tasks that were found in the dependency graph - dependentTasks = Array.from(allRelatedTaskIds) - .map((id) => data.tasks.find((t) => t.id === id)) - .filter(Boolean); - - // Sort by depth in the dependency chain - dependentTasks.sort((a, b) => { - const depthA = depthMap.get(a.id) || 0; - const depthB = depthMap.get(b.id) || 0; - return depthA - depthB; // Lowest depth (root dependencies) first - }); - - // Limit the number of detailed tasks to avoid context explosion - uniqueDetailedTasks = dependentTasks.slice(0, 8); - - contextTasks = `\nThis task relates to a dependency structure with ${dependentTasks.length} related tasks in the chain.\n\nDirect dependencies:`; - const directDeps = data.tasks.filter((t) => - numericDependencies.includes(t.id) - ); - contextTasks += `\n${directDeps.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`).join("\n")}`; - - // Add an overview of indirect dependencies if present - const indirectDeps = dependentTasks.filter( - (t) => !numericDependencies.includes(t.id) - ); - if (indirectDeps.length > 0) { - contextTasks += `\n\nIndirect dependencies (dependencies of dependencies):`; - contextTasks += `\n${indirectDeps - .slice(0, 5) - .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) - .join("\n")}`; - if (indirectDeps.length > 5) { - contextTasks += `\n- ... and ${indirectDeps.length - 5} more indirect dependencies`; - } - } - - // Add more details about each dependency, prioritizing direct dependencies - contextTasks += `\n\nDetailed information about dependencies:`; - for (const depTask of uniqueDetailedTasks) { - const depthInfo = depthMap.get(depTask.id) - ? ` (depth: ${depthMap.get(depTask.id)})` - : ""; - const isDirect = numericDependencies.includes(depTask.id) - ? " [DIRECT DEPENDENCY]" - : ""; - - contextTasks += `\n\n------ Task ${depTask.id}${isDirect}${depthInfo}: ${depTask.title} ------\n`; - contextTasks += `Description: ${depTask.description}\n`; - contextTasks += `Status: ${depTask.status || "pending"}\n`; - contextTasks += `Priority: ${depTask.priority || "medium"}\n`; - - // List its dependencies - if (depTask.dependencies && depTask.dependencies.length > 0) { - const depDeps = depTask.dependencies.map((dId) => { - const depDepTask = data.tasks.find((t) => t.id === dId); - return depDepTask - ? `Task ${dId}: ${depDepTask.title}` - : `Task ${dId}`; - }); - contextTasks += `Dependencies: ${depDeps.join(", ")}\n`; - } else { - contextTasks += `Dependencies: None\n`; - } - - // Add implementation details but truncate if too long - if (depTask.details) { - const truncatedDetails = - depTask.details.length > 400 - ? depTask.details.substring(0, 400) + "... (truncated)" - : depTask.details; - contextTasks += `Implementation Details: ${truncatedDetails}\n`; - } - } - - // Add dependency chain visualization - if (dependencyGraphs.length > 0) { - contextTasks += "\n\nDependency Chain Visualization:"; - - // Helper function to format dependency chain as text - function formatDependencyChain( - node, - prefix = "", - isLast = true, - depth = 0 - ) { - if (depth > 3) return ""; // Limit depth to avoid excessive nesting - - const connector = isLast ? "โ””โ”€โ”€ " : "โ”œโ”€โ”€ "; - const childPrefix = isLast ? " " : "โ”‚ "; - - let result = `\n${prefix}${connector}Task ${node.id}: ${node.title}`; - - if (node.dependencies && node.dependencies.length > 0) { - for (let i = 0; i < node.dependencies.length; i++) { - const isLastChild = i === node.dependencies.length - 1; - result += formatDependencyChain( - node.dependencies[i], - prefix + childPrefix, - isLastChild, - depth + 1 - ); - } - } - - return result; - } - - // Format each dependency graph - for (const graph of dependencyGraphs) { - contextTasks += formatDependencyChain(graph); - } - } - - // Show dependency analysis in CLI mode - if (outputFormat === "text") { - if (directDeps.length > 0) { - console.log(chalk.gray(` Explicitly specified dependencies:`)); - directDeps.forEach((t) => { - console.log( - chalk.yellow(` โ€ข Task ${t.id}: ${truncate(t.title, 50)}`) - ); - }); - } - - if (indirectDeps.length > 0) { - console.log( - chalk.gray( - `\n Indirect dependencies (${indirectDeps.length} total):` - ) - ); - indirectDeps.slice(0, 3).forEach((t) => { - const depth = depthMap.get(t.id) || 0; - console.log( - chalk.cyan( - ` โ€ข Task ${t.id} [depth ${depth}]: ${truncate(t.title, 45)}` - ) - ); - }); - if (indirectDeps.length > 3) { - console.log( - chalk.cyan( - ` โ€ข ... and ${indirectDeps.length - 3} more indirect dependencies` - ) - ); - } - } - - // Visualize the dependency chain - if (dependencyGraphs.length > 0) { - console.log(chalk.gray(`\n Dependency chain visualization:`)); - - // Convert dependency graph to ASCII art for terminal - function visualizeDependencyGraph( - node, - prefix = "", - isLast = true, - depth = 0 - ) { - if (depth > 2) return; // Limit depth for display - - const connector = isLast ? "โ””โ”€โ”€ " : "โ”œโ”€โ”€ "; - const childPrefix = isLast ? " " : "โ”‚ "; - - console.log( - chalk.blue( - ` ${prefix}${connector}Task ${node.id}: ${truncate(node.title, 40)}` - ) - ); - - if (node.dependencies && node.dependencies.length > 0) { - for (let i = 0; i < node.dependencies.length; i++) { - const isLastChild = i === node.dependencies.length - 1; - visualizeDependencyGraph( - node.dependencies[i], - prefix + childPrefix, - isLastChild, - depth + 1 - ); - } - } - } - - // Visualize each dependency graph - for (const graph of dependencyGraphs) { - visualizeDependencyGraph(graph); - } - } - - console.log(); // Add spacing - } - } else { - // If no dependencies provided, use Fuse.js to find semantically related tasks - // Create fuzzy search index for all tasks - const searchOptions = { - includeScore: true, // Return match scores - threshold: 0.4, // Lower threshold = stricter matching (range 0-1) - keys: [ - { name: "title", weight: 1.5 }, // Title is most important - { name: "description", weight: 2 }, // Description is very important - { name: "details", weight: 3 }, // Details is most important - // Search dependencies to find tasks that depend on similar things - { name: "dependencyTitles", weight: 0.5 }, - ], - // Sort matches by score (lower is better) - shouldSort: true, - // Allow searching in nested properties - useExtendedSearch: true, - // Return up to 50 matches - limit: 50, - }; - - // Prepare task data with dependencies expanded as titles for better semantic search - const searchableTasks = data.tasks.map((task) => { - // Get titles of this task's dependencies if they exist - const dependencyTitles = - task.dependencies?.length > 0 - ? task.dependencies - .map((depId) => { - const depTask = data.tasks.find((t) => t.id === depId); - return depTask ? depTask.title : ""; - }) - .filter((title) => title) - .join(" ") - : ""; - - return { - ...task, - dependencyTitles, - }; - }); - - // Create search index using Fuse.js - const fuse = new Fuse(searchableTasks, searchOptions); - - // Extract significant words and phrases from the prompt - const promptWords = prompt - .toLowerCase() - .replace(/[^\w\s-]/g, " ") // Replace non-alphanumeric chars with spaces - .split(/\s+/) - .filter((word) => word.length > 3); // Words at least 4 chars - - // Use the user's prompt for fuzzy search - const fuzzyResults = fuse.search(prompt); - - // Also search for each significant word to catch different aspects - let wordResults = []; - for (const word of promptWords) { - if (word.length > 5) { - // Only use significant words - const results = fuse.search(word); - if (results.length > 0) { - wordResults.push(...results); - } - } - } - - // Merge and deduplicate results - const mergedResults = [...fuzzyResults]; - - // Add word results that aren't already in fuzzyResults - for (const wordResult of wordResults) { - if (!mergedResults.some((r) => r.item.id === wordResult.item.id)) { - mergedResults.push(wordResult); - } - } - - // Group search results by relevance - const highRelevance = mergedResults - .filter((result) => result.score < 0.25) - .map((result) => result.item); - - const mediumRelevance = mergedResults - .filter((result) => result.score >= 0.25 && result.score < 0.4) - .map((result) => result.item); - - // Get recent tasks (newest first) - const recentTasks = [...data.tasks] - .sort((a, b) => b.id - a.id) - .slice(0, 5); - - // Combine high relevance, medium relevance, and recent tasks - // Prioritize high relevance first - const allRelevantTasks = [...highRelevance]; - - // Add medium relevance if not already included - for (const task of mediumRelevance) { - if (!allRelevantTasks.some((t) => t.id === task.id)) { - allRelevantTasks.push(task); - } - } - - // Add recent tasks if not already included - for (const task of recentTasks) { - if (!allRelevantTasks.some((t) => t.id === task.id)) { - allRelevantTasks.push(task); - } - } - - // Get top N results for context - const relatedTasks = allRelevantTasks.slice(0, 8); - - // Format basic task overviews - if (relatedTasks.length > 0) { - contextTasks = `\nRelevant tasks identified by semantic similarity:\n${relatedTasks - .map((t, i) => { - const relevanceMarker = i < highRelevance.length ? "โญ " : ""; - return `- ${relevanceMarker}Task ${t.id}: ${t.title} - ${t.description}`; - }) - .join("\n")}`; - } - - if ( - recentTasks.length > 0 && - !contextTasks.includes("Recently created tasks") - ) { - contextTasks += `\n\nRecently created tasks:\n${recentTasks - .filter((t) => !relatedTasks.some((rt) => rt.id === t.id)) - .slice(0, 3) - .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) - .join("\n")}`; - } - - // Add detailed information about the most relevant tasks - const allDetailedTasks = [...relatedTasks.slice(0, 25)]; - uniqueDetailedTasks = Array.from( - new Map(allDetailedTasks.map((t) => [t.id, t])).values() - ).slice(0, 20); - - if (uniqueDetailedTasks.length > 0) { - contextTasks += `\n\nDetailed information about relevant tasks:`; - for (const task of uniqueDetailedTasks) { - contextTasks += `\n\n------ Task ${task.id}: ${task.title} ------\n`; - contextTasks += `Description: ${task.description}\n`; - contextTasks += `Status: ${task.status || "pending"}\n`; - contextTasks += `Priority: ${task.priority || "medium"}\n`; - if (task.dependencies && task.dependencies.length > 0) { - // Format dependency list with titles - const depList = task.dependencies.map((depId) => { - const depTask = data.tasks.find((t) => t.id === depId); - return depTask - ? `Task ${depId} (${depTask.title})` - : `Task ${depId}`; - }); - contextTasks += `Dependencies: ${depList.join(", ")}\n`; - } - // Add implementation details but truncate if too long - if (task.details) { - const truncatedDetails = - task.details.length > 400 - ? task.details.substring(0, 400) + "... (truncated)" - : task.details; - contextTasks += `Implementation Details: ${truncatedDetails}\n`; - } - } - } - - // Add a concise view of the task dependency structure - contextTasks += "\n\nSummary of task dependencies in the project:"; - - // Get pending/in-progress tasks that might be most relevant based on fuzzy search - // Prioritize tasks from our similarity search - const relevantTaskIds = new Set(uniqueDetailedTasks.map((t) => t.id)); - const relevantPendingTasks = data.tasks - .filter( - (t) => - (t.status === "pending" || t.status === "in-progress") && - // Either in our relevant set OR has relevant words in title/description - (relevantTaskIds.has(t.id) || - promptWords.some( - (word) => - t.title.toLowerCase().includes(word) || - t.description.toLowerCase().includes(word) - )) - ) - .slice(0, 10); - - for (const task of relevantPendingTasks) { - const depsStr = - task.dependencies && task.dependencies.length > 0 - ? task.dependencies.join(", ") - : "None"; - contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`; - } - - // Additional analysis of common patterns - const similarPurposeTasks = data.tasks.filter((t) => - prompt.toLowerCase().includes(t.title.toLowerCase()) - ); - - let commonDeps = []; // Initialize commonDeps - - if (similarPurposeTasks.length > 0) { - contextTasks += `\n\nCommon patterns for similar tasks:`; - - // Collect dependencies from similar purpose tasks - const similarDeps = similarPurposeTasks - .filter((t) => t.dependencies && t.dependencies.length > 0) - .map((t) => t.dependencies) - .flat(); - - // Count frequency of each dependency - const depCounts = {}; - similarDeps.forEach((dep) => { - depCounts[dep] = (depCounts[dep] || 0) + 1; - }); - - // Get most common dependencies for similar tasks - commonDeps = Object.entries(depCounts) - .sort((a, b) => b[1] - a[1]) - .slice(0, 10); - - if (commonDeps.length > 0) { - contextTasks += "\nMost common dependencies for similar tasks:"; - commonDeps.forEach(([depId, count]) => { - const depTask = data.tasks.find((t) => t.id === parseInt(depId)); - if (depTask) { - contextTasks += `\n- Task ${depId} (used by ${count} similar tasks): ${depTask.title}`; - } - }); - } - } - - // Show fuzzy search analysis in CLI mode - if (outputFormat === "text") { - console.log( - chalk.gray( - ` Context search across ${data.tasks.length} tasks using full prompt and ${promptWords.length} keywords` - ) - ); - - if (highRelevance.length > 0) { - console.log( - chalk.gray(`\n High relevance matches (score < 0.25):`) - ); - highRelevance.slice(0, 25).forEach((t) => { - console.log( - chalk.yellow(` โ€ข โญ Task ${t.id}: ${truncate(t.title, 50)}`) - ); - }); - } - - if (mediumRelevance.length > 0) { - console.log( - chalk.gray(`\n Medium relevance matches (score < 0.4):`) - ); - mediumRelevance.slice(0, 10).forEach((t) => { - console.log( - chalk.green(` โ€ข Task ${t.id}: ${truncate(t.title, 50)}`) - ); - }); - } - - // Show dependency patterns - if (commonDeps && commonDeps.length > 0) { - console.log( - chalk.gray(`\n Common dependency patterns for similar tasks:`) - ); - commonDeps.slice(0, 3).forEach(([depId, count]) => { - const depTask = data.tasks.find((t) => t.id === parseInt(depId)); - if (depTask) { - console.log( - chalk.blue( - ` โ€ข Task ${depId} (${count}x): ${truncate(depTask.title, 45)}` - ) - ); - } - }); - } - - // Add information about which tasks will be provided in detail - if (uniqueDetailedTasks.length > 0) { - console.log( - chalk.gray( - `\n Providing detailed context for ${uniqueDetailedTasks.length} most relevant tasks:` - ) - ); - uniqueDetailedTasks.forEach((t) => { - const isHighRelevance = highRelevance.some( - (ht) => ht.id === t.id - ); - const relevanceIndicator = isHighRelevance ? "โญ " : ""; - console.log( - chalk.cyan( - ` โ€ข ${relevanceIndicator}Task ${t.id}: ${truncate(t.title, 40)}` - ) - ); - }); - } - - console.log(); // Add spacing - } - } - - // DETERMINE THE ACTUAL COUNT OF DETAILED TASKS BEING USED FOR AI CONTEXT - let actualDetailedTasksCount = 0; - if (numericDependencies.length > 0) { - // In explicit dependency mode, we used 'uniqueDetailedTasks' derived from 'dependentTasks' - // Ensure 'uniqueDetailedTasks' from THAT scope is used or re-evaluate. - // For simplicity, let's assume 'dependentTasks' reflects the detailed tasks. - actualDetailedTasksCount = dependentTasks.length; - } else { - // In fuzzy search mode, 'uniqueDetailedTasks' from THIS scope is correct. - actualDetailedTasksCount = uniqueDetailedTasks - ? uniqueDetailedTasks.length - : 0; - } - - // Add a visual transition to show we're moving to AI generation - only for CLI - if (outputFormat === "text") { - console.log( - boxen( - chalk.white.bold("AI Task Generation") + - `\n\n${chalk.gray("Analyzing context and generating task details using AI...")}` + - `\n${chalk.cyan("Context size: ")}${chalk.yellow(contextTasks.length.toLocaleString())} characters` + - `\n${chalk.cyan("Dependency detection: ")}${chalk.yellow(numericDependencies.length > 0 ? "Explicit dependencies" : "Auto-discovery mode")}` + - `\n${chalk.cyan("Detailed tasks: ")}${chalk.yellow( - numericDependencies.length > 0 - ? dependentTasks.length // Use length of tasks from explicit dependency path - : uniqueDetailedTasks.length // Use length of tasks from fuzzy search path - )}`, - { - padding: { top: 0, bottom: 1, left: 1, right: 1 }, - margin: { top: 1, bottom: 0 }, - borderColor: "white", - borderStyle: "round", - } - ) - ); - console.log(); // Add spacing - } - - // System Prompt - Enhanced for dependency awareness - const systemPrompt = - "You are a helpful assistant that creates well-structured tasks for a software development project. Generate a single new task based on the user's description, adhering strictly to the provided JSON schema. Pay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on.\n\n" + - "When determining dependencies for a new task, follow these principles:\n" + - "1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n" + - "2. Prioritize task dependencies that are semantically related to the functionality being built.\n" + - "3. Consider both direct dependencies (immediately prerequisite) and indirect dependencies.\n" + - "4. Avoid adding unnecessary dependencies - only include tasks that are genuinely prerequisite.\n" + - "5. Consider the current status of tasks - prefer completed tasks as dependencies when possible.\n" + - "6. Pay special attention to foundation tasks (1-5) but don't automatically include them without reason.\n" + - "7. Recent tasks (higher ID numbers) may be more relevant for newer functionality.\n\n" + - "The dependencies array should contain task IDs (numbers) of prerequisite tasks.\n"; - - // Task Structure Description (for user prompt) - const taskStructureDesc = ` + const { session, mcpLog, projectRoot, commandName, outputType } = context; + const isMCP = !!mcpLog; + + // Create a consistent logFn object regardless of context + const logFn = isMCP + ? mcpLog // Use MCP logger if provided + : { + // Create a wrapper around consoleLog for CLI + info: (...args) => consoleLog('info', ...args), + warn: (...args) => consoleLog('warn', ...args), + error: (...args) => consoleLog('error', ...args), + debug: (...args) => consoleLog('debug', ...args), + success: (...args) => consoleLog('success', ...args) + }; + + const effectivePriority = priority || getDefaultPriority(projectRoot); + + logFn.info( + `Adding new task with prompt: "${prompt}", Priority: ${effectivePriority}, Dependencies: ${dependencies.join(', ') || 'None'}, Research: ${useResearch}, ProjectRoot: ${projectRoot}` + ); + + let loadingIndicator = null; + let aiServiceResponse = null; // To store the full response from AI service + + // Create custom reporter that checks for MCP log + const report = (message, level = 'info') => { + if (mcpLog) { + mcpLog[level](message); + } else if (outputFormat === 'text') { + consoleLog(level, message); + } + }; + + /** + * Recursively builds a dependency graph for a given task + * @param {Array} tasks - All tasks from tasks.json + * @param {number} taskId - ID of the task to analyze + * @param {Set} visited - Set of already visited task IDs + * @param {Map} depthMap - Map of task ID to its depth in the graph + * @param {number} depth - Current depth in the recursion + * @return {Object} Dependency graph data + */ + function buildDependencyGraph( + tasks, + taskId, + visited = new Set(), + depthMap = new Map(), + depth = 0 + ) { + // Skip if we've already visited this task or it doesn't exist + if (visited.has(taskId)) { + return null; + } + + // Find the task + const task = tasks.find((t) => t.id === taskId); + if (!task) { + return null; + } + + // Mark as visited + visited.add(taskId); + + // Update depth if this is a deeper path to this task + if (!depthMap.has(taskId) || depth < depthMap.get(taskId)) { + depthMap.set(taskId, depth); + } + + // Process dependencies + const dependencyData = []; + if (task.dependencies && task.dependencies.length > 0) { + for (const depId of task.dependencies) { + const depData = buildDependencyGraph( + tasks, + depId, + visited, + depthMap, + depth + 1 + ); + if (depData) { + dependencyData.push(depData); + } + } + } + + return { + id: task.id, + title: task.title, + description: task.description, + status: task.status, + dependencies: dependencyData + }; + } + + try { + // Read the existing tasks + let data = readJSON(tasksPath); + + // If tasks.json doesn't exist or is invalid, create a new one + if (!data || !data.tasks) { + report('tasks.json not found or invalid. Creating a new one.', 'info'); + // Create default tasks data structure + data = { + tasks: [] + }; + // Ensure the directory exists and write the new file + writeJSON(tasksPath, data); + report('Created new tasks.json file with empty tasks array.', 'info'); + } + + // Find the highest task ID to determine the next ID + const highestId = + data.tasks.length > 0 ? Math.max(...data.tasks.map((t) => t.id)) : 0; + const newTaskId = highestId + 1; + + // Only show UI box for CLI mode + if (outputFormat === 'text') { + console.log( + boxen(chalk.white.bold(`Creating New Task #${newTaskId}`), { + padding: 1, + borderColor: 'blue', + borderStyle: 'round', + margin: { top: 1, bottom: 1 } + }) + ); + } + + // Validate dependencies before proceeding + const invalidDeps = dependencies.filter((depId) => { + // Ensure depId is parsed as a number for comparison + const numDepId = parseInt(depId, 10); + return isNaN(numDepId) || !data.tasks.some((t) => t.id === numDepId); + }); + + if (invalidDeps.length > 0) { + report( + `The following dependencies do not exist or are invalid: ${invalidDeps.join(', ')}`, + 'warn' + ); + report('Removing invalid dependencies...', 'info'); + dependencies = dependencies.filter( + (depId) => !invalidDeps.includes(depId) + ); + } + // Ensure dependencies are numbers + const numericDependencies = dependencies.map((dep) => parseInt(dep, 10)); + + // Build dependency graphs for explicitly specified dependencies + const dependencyGraphs = []; + const allRelatedTaskIds = new Set(); + const depthMap = new Map(); + + // First pass: build a complete dependency graph for each specified dependency + for (const depId of numericDependencies) { + const graph = buildDependencyGraph( + data.tasks, + depId, + new Set(), + depthMap + ); + if (graph) { + dependencyGraphs.push(graph); + } + } + + // Second pass: build a set of all related task IDs for flat analysis + for (const [taskId, depth] of depthMap.entries()) { + allRelatedTaskIds.add(taskId); + } + + let taskData; + + // Check if manual task data is provided + if (manualTaskData) { + report('Using manually provided task data', 'info'); + taskData = manualTaskData; + report('DEBUG: Taking MANUAL task data path.', 'debug'); + + // Basic validation for manual data + if ( + !taskData.title || + typeof taskData.title !== 'string' || + !taskData.description || + typeof taskData.description !== 'string' + ) { + throw new Error( + 'Manual task data must include at least a title and description.' + ); + } + } else { + report('DEBUG: Taking AI task generation path.', 'debug'); + // --- Refactored AI Interaction --- + report(`Generating task data with AI with prompt:\n${prompt}`, 'info'); + + // Create context string for task creation prompt + let contextTasks = ''; + + // Create a dependency map for better understanding of the task relationships + const taskMap = {}; + data.tasks.forEach((t) => { + // For each task, only include id, title, description, and dependencies + taskMap[t.id] = { + id: t.id, + title: t.title, + description: t.description, + dependencies: t.dependencies || [], + status: t.status + }; + }); + + // CLI-only feedback for the dependency analysis + if (outputFormat === 'text') { + console.log( + boxen(chalk.cyan.bold('Task Context Analysis'), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 0, bottom: 0 }, + borderColor: 'cyan', + borderStyle: 'round' + }) + ); + } + + // Initialize variables that will be used in either branch + let uniqueDetailedTasks = []; + let dependentTasks = []; + let promptCategory = null; + + if (numericDependencies.length > 0) { + // If specific dependencies were provided, focus on them + // Get all tasks that were found in the dependency graph + dependentTasks = Array.from(allRelatedTaskIds) + .map((id) => data.tasks.find((t) => t.id === id)) + .filter(Boolean); + + // Sort by depth in the dependency chain + dependentTasks.sort((a, b) => { + const depthA = depthMap.get(a.id) || 0; + const depthB = depthMap.get(b.id) || 0; + return depthA - depthB; // Lowest depth (root dependencies) first + }); + + // Limit the number of detailed tasks to avoid context explosion + uniqueDetailedTasks = dependentTasks.slice(0, 8); + + contextTasks = `\nThis task relates to a dependency structure with ${dependentTasks.length} related tasks in the chain.\n\nDirect dependencies:`; + const directDeps = data.tasks.filter((t) => + numericDependencies.includes(t.id) + ); + contextTasks += `\n${directDeps.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`).join('\n')}`; + + // Add an overview of indirect dependencies if present + const indirectDeps = dependentTasks.filter( + (t) => !numericDependencies.includes(t.id) + ); + if (indirectDeps.length > 0) { + contextTasks += `\n\nIndirect dependencies (dependencies of dependencies):`; + contextTasks += `\n${indirectDeps + .slice(0, 5) + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + if (indirectDeps.length > 5) { + contextTasks += `\n- ... and ${indirectDeps.length - 5} more indirect dependencies`; + } + } + + // Add more details about each dependency, prioritizing direct dependencies + contextTasks += `\n\nDetailed information about dependencies:`; + for (const depTask of uniqueDetailedTasks) { + const depthInfo = depthMap.get(depTask.id) + ? ` (depth: ${depthMap.get(depTask.id)})` + : ''; + const isDirect = numericDependencies.includes(depTask.id) + ? ' [DIRECT DEPENDENCY]' + : ''; + + contextTasks += `\n\n------ Task ${depTask.id}${isDirect}${depthInfo}: ${depTask.title} ------\n`; + contextTasks += `Description: ${depTask.description}\n`; + contextTasks += `Status: ${depTask.status || 'pending'}\n`; + contextTasks += `Priority: ${depTask.priority || 'medium'}\n`; + + // List its dependencies + if (depTask.dependencies && depTask.dependencies.length > 0) { + const depDeps = depTask.dependencies.map((dId) => { + const depDepTask = data.tasks.find((t) => t.id === dId); + return depDepTask + ? `Task ${dId}: ${depDepTask.title}` + : `Task ${dId}`; + }); + contextTasks += `Dependencies: ${depDeps.join(', ')}\n`; + } else { + contextTasks += `Dependencies: None\n`; + } + + // Add implementation details but truncate if too long + if (depTask.details) { + const truncatedDetails = + depTask.details.length > 400 + ? depTask.details.substring(0, 400) + '... (truncated)' + : depTask.details; + contextTasks += `Implementation Details: ${truncatedDetails}\n`; + } + } + + // Add dependency chain visualization + if (dependencyGraphs.length > 0) { + contextTasks += '\n\nDependency Chain Visualization:'; + + // Helper function to format dependency chain as text + function formatDependencyChain( + node, + prefix = '', + isLast = true, + depth = 0 + ) { + if (depth > 3) return ''; // Limit depth to avoid excessive nesting + + const connector = isLast ? 'โ””โ”€โ”€ ' : 'โ”œโ”€โ”€ '; + const childPrefix = isLast ? ' ' : 'โ”‚ '; + + let result = `\n${prefix}${connector}Task ${node.id}: ${node.title}`; + + if (node.dependencies && node.dependencies.length > 0) { + for (let i = 0; i < node.dependencies.length; i++) { + const isLastChild = i === node.dependencies.length - 1; + result += formatDependencyChain( + node.dependencies[i], + prefix + childPrefix, + isLastChild, + depth + 1 + ); + } + } + + return result; + } + + // Format each dependency graph + for (const graph of dependencyGraphs) { + contextTasks += formatDependencyChain(graph); + } + } + + // Show dependency analysis in CLI mode + if (outputFormat === 'text') { + if (directDeps.length > 0) { + console.log(chalk.gray(` Explicitly specified dependencies:`)); + directDeps.forEach((t) => { + console.log( + chalk.yellow(` โ€ข Task ${t.id}: ${truncate(t.title, 50)}`) + ); + }); + } + + if (indirectDeps.length > 0) { + console.log( + chalk.gray( + `\n Indirect dependencies (${indirectDeps.length} total):` + ) + ); + indirectDeps.slice(0, 3).forEach((t) => { + const depth = depthMap.get(t.id) || 0; + console.log( + chalk.cyan( + ` โ€ข Task ${t.id} [depth ${depth}]: ${truncate(t.title, 45)}` + ) + ); + }); + if (indirectDeps.length > 3) { + console.log( + chalk.cyan( + ` โ€ข ... and ${indirectDeps.length - 3} more indirect dependencies` + ) + ); + } + } + + // Visualize the dependency chain + if (dependencyGraphs.length > 0) { + console.log(chalk.gray(`\n Dependency chain visualization:`)); + + // Convert dependency graph to ASCII art for terminal + function visualizeDependencyGraph( + node, + prefix = '', + isLast = true, + depth = 0 + ) { + if (depth > 2) return; // Limit depth for display + + const connector = isLast ? 'โ””โ”€โ”€ ' : 'โ”œโ”€โ”€ '; + const childPrefix = isLast ? ' ' : 'โ”‚ '; + + console.log( + chalk.blue( + ` ${prefix}${connector}Task ${node.id}: ${truncate(node.title, 40)}` + ) + ); + + if (node.dependencies && node.dependencies.length > 0) { + for (let i = 0; i < node.dependencies.length; i++) { + const isLastChild = i === node.dependencies.length - 1; + visualizeDependencyGraph( + node.dependencies[i], + prefix + childPrefix, + isLastChild, + depth + 1 + ); + } + } + } + + // Visualize each dependency graph + for (const graph of dependencyGraphs) { + visualizeDependencyGraph(graph); + } + } + + console.log(); // Add spacing + } + } else { + // If no dependencies provided, use Fuse.js to find semantically related tasks + // Create fuzzy search index for all tasks + const searchOptions = { + includeScore: true, // Return match scores + threshold: 0.4, // Lower threshold = stricter matching (range 0-1) + keys: [ + { name: 'title', weight: 1.5 }, // Title is most important + { name: 'description', weight: 2 }, // Description is very important + { name: 'details', weight: 3 }, // Details is most important + // Search dependencies to find tasks that depend on similar things + { name: 'dependencyTitles', weight: 0.5 } + ], + // Sort matches by score (lower is better) + shouldSort: true, + // Allow searching in nested properties + useExtendedSearch: true, + // Return up to 50 matches + limit: 50 + }; + + // Prepare task data with dependencies expanded as titles for better semantic search + const searchableTasks = data.tasks.map((task) => { + // Get titles of this task's dependencies if they exist + const dependencyTitles = + task.dependencies?.length > 0 + ? task.dependencies + .map((depId) => { + const depTask = data.tasks.find((t) => t.id === depId); + return depTask ? depTask.title : ''; + }) + .filter((title) => title) + .join(' ') + : ''; + + return { + ...task, + dependencyTitles + }; + }); + + // Create search index using Fuse.js + const fuse = new Fuse(searchableTasks, searchOptions); + + // Extract significant words and phrases from the prompt + const promptWords = prompt + .toLowerCase() + .replace(/[^\w\s-]/g, ' ') // Replace non-alphanumeric chars with spaces + .split(/\s+/) + .filter((word) => word.length > 3); // Words at least 4 chars + + // Use the user's prompt for fuzzy search + const fuzzyResults = fuse.search(prompt); + + // Also search for each significant word to catch different aspects + let wordResults = []; + for (const word of promptWords) { + if (word.length > 5) { + // Only use significant words + const results = fuse.search(word); + if (results.length > 0) { + wordResults.push(...results); + } + } + } + + // Merge and deduplicate results + const mergedResults = [...fuzzyResults]; + + // Add word results that aren't already in fuzzyResults + for (const wordResult of wordResults) { + if (!mergedResults.some((r) => r.item.id === wordResult.item.id)) { + mergedResults.push(wordResult); + } + } + + // Group search results by relevance + const highRelevance = mergedResults + .filter((result) => result.score < 0.25) + .map((result) => result.item); + + const mediumRelevance = mergedResults + .filter((result) => result.score >= 0.25 && result.score < 0.4) + .map((result) => result.item); + + // Get recent tasks (newest first) + const recentTasks = [...data.tasks] + .sort((a, b) => b.id - a.id) + .slice(0, 5); + + // Combine high relevance, medium relevance, and recent tasks + // Prioritize high relevance first + const allRelevantTasks = [...highRelevance]; + + // Add medium relevance if not already included + for (const task of mediumRelevance) { + if (!allRelevantTasks.some((t) => t.id === task.id)) { + allRelevantTasks.push(task); + } + } + + // Add recent tasks if not already included + for (const task of recentTasks) { + if (!allRelevantTasks.some((t) => t.id === task.id)) { + allRelevantTasks.push(task); + } + } + + // Get top N results for context + const relatedTasks = allRelevantTasks.slice(0, 8); + + // Format basic task overviews + if (relatedTasks.length > 0) { + contextTasks = `\nRelevant tasks identified by semantic similarity:\n${relatedTasks + .map((t, i) => { + const relevanceMarker = i < highRelevance.length ? 'โญ ' : ''; + return `- ${relevanceMarker}Task ${t.id}: ${t.title} - ${t.description}`; + }) + .join('\n')}`; + } + + if ( + recentTasks.length > 0 && + !contextTasks.includes('Recently created tasks') + ) { + contextTasks += `\n\nRecently created tasks:\n${recentTasks + .filter((t) => !relatedTasks.some((rt) => rt.id === t.id)) + .slice(0, 3) + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + // Add detailed information about the most relevant tasks + const allDetailedTasks = [...relatedTasks.slice(0, 25)]; + uniqueDetailedTasks = Array.from( + new Map(allDetailedTasks.map((t) => [t.id, t])).values() + ).slice(0, 20); + + if (uniqueDetailedTasks.length > 0) { + contextTasks += `\n\nDetailed information about relevant tasks:`; + for (const task of uniqueDetailedTasks) { + contextTasks += `\n\n------ Task ${task.id}: ${task.title} ------\n`; + contextTasks += `Description: ${task.description}\n`; + contextTasks += `Status: ${task.status || 'pending'}\n`; + contextTasks += `Priority: ${task.priority || 'medium'}\n`; + if (task.dependencies && task.dependencies.length > 0) { + // Format dependency list with titles + const depList = task.dependencies.map((depId) => { + const depTask = data.tasks.find((t) => t.id === depId); + return depTask + ? `Task ${depId} (${depTask.title})` + : `Task ${depId}`; + }); + contextTasks += `Dependencies: ${depList.join(', ')}\n`; + } + // Add implementation details but truncate if too long + if (task.details) { + const truncatedDetails = + task.details.length > 400 + ? task.details.substring(0, 400) + '... (truncated)' + : task.details; + contextTasks += `Implementation Details: ${truncatedDetails}\n`; + } + } + } + + // Add a concise view of the task dependency structure + contextTasks += '\n\nSummary of task dependencies in the project:'; + + // Get pending/in-progress tasks that might be most relevant based on fuzzy search + // Prioritize tasks from our similarity search + const relevantTaskIds = new Set(uniqueDetailedTasks.map((t) => t.id)); + const relevantPendingTasks = data.tasks + .filter( + (t) => + (t.status === 'pending' || t.status === 'in-progress') && + // Either in our relevant set OR has relevant words in title/description + (relevantTaskIds.has(t.id) || + promptWords.some( + (word) => + t.title.toLowerCase().includes(word) || + t.description.toLowerCase().includes(word) + )) + ) + .slice(0, 10); + + for (const task of relevantPendingTasks) { + const depsStr = + task.dependencies && task.dependencies.length > 0 + ? task.dependencies.join(', ') + : 'None'; + contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`; + } + + // Additional analysis of common patterns + const similarPurposeTasks = data.tasks.filter((t) => + prompt.toLowerCase().includes(t.title.toLowerCase()) + ); + + let commonDeps = []; // Initialize commonDeps + + if (similarPurposeTasks.length > 0) { + contextTasks += `\n\nCommon patterns for similar tasks:`; + + // Collect dependencies from similar purpose tasks + const similarDeps = similarPurposeTasks + .filter((t) => t.dependencies && t.dependencies.length > 0) + .map((t) => t.dependencies) + .flat(); + + // Count frequency of each dependency + const depCounts = {}; + similarDeps.forEach((dep) => { + depCounts[dep] = (depCounts[dep] || 0) + 1; + }); + + // Get most common dependencies for similar tasks + commonDeps = Object.entries(depCounts) + .sort((a, b) => b[1] - a[1]) + .slice(0, 10); + + if (commonDeps.length > 0) { + contextTasks += '\nMost common dependencies for similar tasks:'; + commonDeps.forEach(([depId, count]) => { + const depTask = data.tasks.find((t) => t.id === parseInt(depId)); + if (depTask) { + contextTasks += `\n- Task ${depId} (used by ${count} similar tasks): ${depTask.title}`; + } + }); + } + } + + // Show fuzzy search analysis in CLI mode + if (outputFormat === 'text') { + console.log( + chalk.gray( + ` Context search across ${data.tasks.length} tasks using full prompt and ${promptWords.length} keywords` + ) + ); + + if (highRelevance.length > 0) { + console.log( + chalk.gray(`\n High relevance matches (score < 0.25):`) + ); + highRelevance.slice(0, 25).forEach((t) => { + console.log( + chalk.yellow(` โ€ข โญ Task ${t.id}: ${truncate(t.title, 50)}`) + ); + }); + } + + if (mediumRelevance.length > 0) { + console.log( + chalk.gray(`\n Medium relevance matches (score < 0.4):`) + ); + mediumRelevance.slice(0, 10).forEach((t) => { + console.log( + chalk.green(` โ€ข Task ${t.id}: ${truncate(t.title, 50)}`) + ); + }); + } + + // Show dependency patterns + if (commonDeps && commonDeps.length > 0) { + console.log( + chalk.gray(`\n Common dependency patterns for similar tasks:`) + ); + commonDeps.slice(0, 3).forEach(([depId, count]) => { + const depTask = data.tasks.find((t) => t.id === parseInt(depId)); + if (depTask) { + console.log( + chalk.blue( + ` โ€ข Task ${depId} (${count}x): ${truncate(depTask.title, 45)}` + ) + ); + } + }); + } + + // Add information about which tasks will be provided in detail + if (uniqueDetailedTasks.length > 0) { + console.log( + chalk.gray( + `\n Providing detailed context for ${uniqueDetailedTasks.length} most relevant tasks:` + ) + ); + uniqueDetailedTasks.forEach((t) => { + const isHighRelevance = highRelevance.some( + (ht) => ht.id === t.id + ); + const relevanceIndicator = isHighRelevance ? 'โญ ' : ''; + console.log( + chalk.cyan( + ` โ€ข ${relevanceIndicator}Task ${t.id}: ${truncate(t.title, 40)}` + ) + ); + }); + } + + console.log(); // Add spacing + } + } + + // DETERMINE THE ACTUAL COUNT OF DETAILED TASKS BEING USED FOR AI CONTEXT + let actualDetailedTasksCount = 0; + if (numericDependencies.length > 0) { + // In explicit dependency mode, we used 'uniqueDetailedTasks' derived from 'dependentTasks' + // Ensure 'uniqueDetailedTasks' from THAT scope is used or re-evaluate. + // For simplicity, let's assume 'dependentTasks' reflects the detailed tasks. + actualDetailedTasksCount = dependentTasks.length; + } else { + // In fuzzy search mode, 'uniqueDetailedTasks' from THIS scope is correct. + actualDetailedTasksCount = uniqueDetailedTasks + ? uniqueDetailedTasks.length + : 0; + } + + // Add a visual transition to show we're moving to AI generation - only for CLI + if (outputFormat === 'text') { + console.log( + boxen( + chalk.white.bold('AI Task Generation') + + `\n\n${chalk.gray('Analyzing context and generating task details using AI...')}` + + `\n${chalk.cyan('Context size: ')}${chalk.yellow(contextTasks.length.toLocaleString())} characters` + + `\n${chalk.cyan('Dependency detection: ')}${chalk.yellow(numericDependencies.length > 0 ? 'Explicit dependencies' : 'Auto-discovery mode')}` + + `\n${chalk.cyan('Detailed tasks: ')}${chalk.yellow( + numericDependencies.length > 0 + ? dependentTasks.length // Use length of tasks from explicit dependency path + : uniqueDetailedTasks.length // Use length of tasks from fuzzy search path + )}`, + { + padding: { top: 0, bottom: 1, left: 1, right: 1 }, + margin: { top: 1, bottom: 0 }, + borderColor: 'white', + borderStyle: 'round' + } + ) + ); + console.log(); // Add spacing + } + + // System Prompt - Enhanced for dependency awareness + const systemPrompt = + "You are a helpful assistant that creates well-structured tasks for a software development project. Generate a single new task based on the user's description, adhering strictly to the provided JSON schema. Pay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on.\n\n" + + 'When determining dependencies for a new task, follow these principles:\n' + + '1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n' + + '2. Prioritize task dependencies that are semantically related to the functionality being built.\n' + + '3. Consider both direct dependencies (immediately prerequisite) and indirect dependencies.\n' + + '4. Avoid adding unnecessary dependencies - only include tasks that are genuinely prerequisite.\n' + + '5. Consider the current status of tasks - prefer completed tasks as dependencies when possible.\n' + + "6. Pay special attention to foundation tasks (1-5) but don't automatically include them without reason.\n" + + '7. Recent tasks (higher ID numbers) may be more relevant for newer functionality.\n\n' + + 'The dependencies array should contain task IDs (numbers) of prerequisite tasks.\n'; + + // Task Structure Description (for user prompt) + const taskStructureDesc = ` { "title": "Task title goes here", "description": "A concise one or two sentence description of what the task involves", @@ -852,22 +852,22 @@ async function addTask( } `; - // Add any manually provided details to the prompt for context - let contextFromArgs = ""; - if (manualTaskData?.title) - contextFromArgs += `\n- Suggested Title: "${manualTaskData.title}"`; - if (manualTaskData?.description) - contextFromArgs += `\n- Suggested Description: "${manualTaskData.description}"`; - if (manualTaskData?.details) - contextFromArgs += `\n- Additional Details Context: "${manualTaskData.details}"`; - if (manualTaskData?.testStrategy) - contextFromArgs += `\n- Additional Test Strategy Context: "${manualTaskData.testStrategy}"`; + // Add any manually provided details to the prompt for context + let contextFromArgs = ''; + if (manualTaskData?.title) + contextFromArgs += `\n- Suggested Title: "${manualTaskData.title}"`; + if (manualTaskData?.description) + contextFromArgs += `\n- Suggested Description: "${manualTaskData.description}"`; + if (manualTaskData?.details) + contextFromArgs += `\n- Additional Details Context: "${manualTaskData.details}"`; + if (manualTaskData?.testStrategy) + contextFromArgs += `\n- Additional Test Strategy Context: "${manualTaskData.testStrategy}"`; - // User Prompt - const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project. + // User Prompt + const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project. ${contextTasks} - ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ""} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} Based on the information about existing tasks provided above, include appropriate dependencies in the "dependencies" array. Only include task IDs that this new task directly depends on. @@ -877,297 +877,297 @@ async function addTask( Make sure the details and test strategy are comprehensive and specific. DO NOT include the task ID in the title. `; - // Start the loading indicator - only for text mode - if (outputFormat === "text") { - loadingIndicator = startLoadingIndicator( - `Generating new task with ${useResearch ? "Research" : "Main"} AI... \n` - ); - } + // Start the loading indicator - only for text mode + if (outputFormat === 'text') { + loadingIndicator = startLoadingIndicator( + `Generating new task with ${useResearch ? 'Research' : 'Main'} AI... \n` + ); + } - try { - const serviceRole = useResearch ? "research" : "main"; - report("DEBUG: Calling generateObjectService...", "debug"); + try { + const serviceRole = useResearch ? 'research' : 'main'; + report('DEBUG: Calling generateObjectService...', 'debug'); - aiServiceResponse = await generateObjectService({ - // Capture the full response - role: serviceRole, - session: session, - projectRoot: projectRoot, - schema: AiTaskDataSchema, - objectName: "newTaskData", - systemPrompt: systemPrompt, - prompt: userPrompt, - commandName: commandName || "add-task", // Use passed commandName or default - outputType: outputType || (isMCP ? "mcp" : "cli"), // Use passed outputType or derive - }); - report("DEBUG: generateObjectService returned successfully.", "debug"); + aiServiceResponse = await generateObjectService({ + // Capture the full response + role: serviceRole, + session: session, + projectRoot: projectRoot, + schema: AiTaskDataSchema, + objectName: 'newTaskData', + systemPrompt: systemPrompt, + prompt: userPrompt, + commandName: commandName || 'add-task', // Use passed commandName or default + outputType: outputType || (isMCP ? 'mcp' : 'cli') // Use passed outputType or derive + }); + report('DEBUG: generateObjectService returned successfully.', 'debug'); - if (!aiServiceResponse || !aiServiceResponse.mainResult) { - throw new Error( - "AI service did not return the expected object structure." - ); - } + if (!aiServiceResponse || !aiServiceResponse.mainResult) { + throw new Error( + 'AI service did not return the expected object structure.' + ); + } - // Prefer mainResult if it looks like a valid task object, otherwise try mainResult.object - if ( - aiServiceResponse.mainResult.title && - aiServiceResponse.mainResult.description - ) { - taskData = aiServiceResponse.mainResult; - } else if ( - aiServiceResponse.mainResult.object && - aiServiceResponse.mainResult.object.title && - aiServiceResponse.mainResult.object.description - ) { - taskData = aiServiceResponse.mainResult.object; - } else { - throw new Error("AI service did not return a valid task object."); - } + // Prefer mainResult if it looks like a valid task object, otherwise try mainResult.object + if ( + aiServiceResponse.mainResult.title && + aiServiceResponse.mainResult.description + ) { + taskData = aiServiceResponse.mainResult; + } else if ( + aiServiceResponse.mainResult.object && + aiServiceResponse.mainResult.object.title && + aiServiceResponse.mainResult.object.description + ) { + taskData = aiServiceResponse.mainResult.object; + } else { + throw new Error('AI service did not return a valid task object.'); + } - report("Successfully generated task data from AI.", "success"); + report('Successfully generated task data from AI.', 'success'); - // Success! Show checkmark - if (loadingIndicator) { - succeedLoadingIndicator( - loadingIndicator, - "Task generated successfully" - ); - loadingIndicator = null; // Clear it - } - } catch (error) { - // Failure! Show X - if (loadingIndicator) { - failLoadingIndicator(loadingIndicator, "AI generation failed"); - loadingIndicator = null; - } - report( - `DEBUG: generateObjectService caught error: ${error.message}`, - "debug" - ); - report(`Error generating task with AI: ${error.message}`, "error"); - throw error; // Re-throw error after logging - } finally { - report("DEBUG: generateObjectService finally block reached.", "debug"); - // Clean up if somehow still running - if (loadingIndicator) { - stopLoadingIndicator(loadingIndicator); - } - } - // --- End Refactored AI Interaction --- - } + // Success! Show checkmark + if (loadingIndicator) { + succeedLoadingIndicator( + loadingIndicator, + 'Task generated successfully' + ); + loadingIndicator = null; // Clear it + } + } catch (error) { + // Failure! Show X + if (loadingIndicator) { + failLoadingIndicator(loadingIndicator, 'AI generation failed'); + loadingIndicator = null; + } + report( + `DEBUG: generateObjectService caught error: ${error.message}`, + 'debug' + ); + report(`Error generating task with AI: ${error.message}`, 'error'); + throw error; // Re-throw error after logging + } finally { + report('DEBUG: generateObjectService finally block reached.', 'debug'); + // Clean up if somehow still running + if (loadingIndicator) { + stopLoadingIndicator(loadingIndicator); + } + } + // --- End Refactored AI Interaction --- + } - // Create the new task object - const newTask = { - id: newTaskId, - title: taskData.title, - description: taskData.description, - details: taskData.details || "", - testStrategy: taskData.testStrategy || "", - status: "pending", - dependencies: taskData.dependencies?.length - ? taskData.dependencies - : numericDependencies, // Use AI-suggested dependencies if available, fallback to manually specified - priority: effectivePriority, - subtasks: [], // Initialize with empty subtasks array - }; + // Create the new task object + const newTask = { + id: newTaskId, + title: taskData.title, + description: taskData.description, + details: taskData.details || '', + testStrategy: taskData.testStrategy || '', + status: 'pending', + dependencies: taskData.dependencies?.length + ? taskData.dependencies + : numericDependencies, // Use AI-suggested dependencies if available, fallback to manually specified + priority: effectivePriority, + subtasks: [] // Initialize with empty subtasks array + }; - // Additional check: validate all dependencies in the AI response - if (taskData.dependencies?.length) { - const allValidDeps = taskData.dependencies.every((depId) => { - const numDepId = parseInt(depId, 10); - return !isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId); - }); + // Additional check: validate all dependencies in the AI response + if (taskData.dependencies?.length) { + const allValidDeps = taskData.dependencies.every((depId) => { + const numDepId = parseInt(depId, 10); + return !isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId); + }); - if (!allValidDeps) { - report( - "AI suggested invalid dependencies. Filtering them out...", - "warn" - ); - newTask.dependencies = taskData.dependencies.filter((depId) => { - const numDepId = parseInt(depId, 10); - return !isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId); - }); - } - } + if (!allValidDeps) { + report( + 'AI suggested invalid dependencies. Filtering them out...', + 'warn' + ); + newTask.dependencies = taskData.dependencies.filter((depId) => { + const numDepId = parseInt(depId, 10); + return !isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId); + }); + } + } - // Add the task to the tasks array - data.tasks.push(newTask); + // Add the task to the tasks array + data.tasks.push(newTask); - report("DEBUG: Writing tasks.json...", "debug"); - // Write the updated tasks to the file - writeJSON(tasksPath, data); - report("DEBUG: tasks.json written.", "debug"); + report('DEBUG: Writing tasks.json...', 'debug'); + // Write the updated tasks to the file + writeJSON(tasksPath, data); + report('DEBUG: tasks.json written.', 'debug'); - // Generate markdown task files - report("Generating task files...", "info"); - report("DEBUG: Calling generateTaskFiles...", "debug"); - // Pass mcpLog if available to generateTaskFiles - await generateTaskFiles(tasksPath, path.dirname(tasksPath), { mcpLog }); - report("DEBUG: generateTaskFiles finished.", "debug"); + // Generate markdown task files + report('Generating task files...', 'info'); + report('DEBUG: Calling generateTaskFiles...', 'debug'); + // Pass mcpLog if available to generateTaskFiles + await generateTaskFiles(tasksPath, path.dirname(tasksPath), { mcpLog }); + report('DEBUG: generateTaskFiles finished.', 'debug'); - // Show success message - only for text output (CLI) - if (outputFormat === "text") { - const table = new Table({ - head: [ - chalk.cyan.bold("ID"), - chalk.cyan.bold("Title"), - chalk.cyan.bold("Description"), - ], - colWidths: [5, 30, 50], // Adjust widths as needed - }); + // Show success message - only for text output (CLI) + if (outputFormat === 'text') { + const table = new Table({ + head: [ + chalk.cyan.bold('ID'), + chalk.cyan.bold('Title'), + chalk.cyan.bold('Description') + ], + colWidths: [5, 30, 50] // Adjust widths as needed + }); - table.push([ - newTask.id, - truncate(newTask.title, 27), - truncate(newTask.description, 47), - ]); + table.push([ + newTask.id, + truncate(newTask.title, 27), + truncate(newTask.description, 47) + ]); - console.log(chalk.green("โœ“ New task created successfully:")); - console.log(table.toString()); + console.log(chalk.green('โœ“ New task created successfully:')); + console.log(table.toString()); - // Helper to get priority color - const getPriorityColor = (p) => { - switch (p?.toLowerCase()) { - case "high": - return "red"; - case "low": - return "gray"; - case "medium": - default: - return "yellow"; - } - }; + // Helper to get priority color + const getPriorityColor = (p) => { + switch (p?.toLowerCase()) { + case 'high': + return 'red'; + case 'low': + return 'gray'; + case 'medium': + default: + return 'yellow'; + } + }; - // Check if AI added new dependencies that weren't explicitly provided - const aiAddedDeps = newTask.dependencies.filter( - (dep) => !numericDependencies.includes(dep) - ); + // Check if AI added new dependencies that weren't explicitly provided + const aiAddedDeps = newTask.dependencies.filter( + (dep) => !numericDependencies.includes(dep) + ); - // Check if AI removed any dependencies that were explicitly provided - const aiRemovedDeps = numericDependencies.filter( - (dep) => !newTask.dependencies.includes(dep) - ); + // Check if AI removed any dependencies that were explicitly provided + const aiRemovedDeps = numericDependencies.filter( + (dep) => !newTask.dependencies.includes(dep) + ); - // Get task titles for dependencies to display - const depTitles = {}; - newTask.dependencies.forEach((dep) => { - const depTask = data.tasks.find((t) => t.id === dep); - if (depTask) { - depTitles[dep] = truncate(depTask.title, 30); - } - }); + // Get task titles for dependencies to display + const depTitles = {}; + newTask.dependencies.forEach((dep) => { + const depTask = data.tasks.find((t) => t.id === dep); + if (depTask) { + depTitles[dep] = truncate(depTask.title, 30); + } + }); - // Prepare dependency display string - let dependencyDisplay = ""; - if (newTask.dependencies.length > 0) { - dependencyDisplay = chalk.white("Dependencies:") + "\n"; - newTask.dependencies.forEach((dep) => { - const isAiAdded = aiAddedDeps.includes(dep); - const depType = isAiAdded ? chalk.yellow(" (AI suggested)") : ""; - dependencyDisplay += - chalk.white( - ` - ${dep}: ${depTitles[dep] || "Unknown task"}${depType}` - ) + "\n"; - }); - } else { - dependencyDisplay = chalk.white("Dependencies: None") + "\n"; - } + // Prepare dependency display string + let dependencyDisplay = ''; + if (newTask.dependencies.length > 0) { + dependencyDisplay = chalk.white('Dependencies:') + '\n'; + newTask.dependencies.forEach((dep) => { + const isAiAdded = aiAddedDeps.includes(dep); + const depType = isAiAdded ? chalk.yellow(' (AI suggested)') : ''; + dependencyDisplay += + chalk.white( + ` - ${dep}: ${depTitles[dep] || 'Unknown task'}${depType}` + ) + '\n'; + }); + } else { + dependencyDisplay = chalk.white('Dependencies: None') + '\n'; + } - // Add info about removed dependencies if any - if (aiRemovedDeps.length > 0) { - dependencyDisplay += - chalk.gray("\nUser-specified dependencies that were not used:") + - "\n"; - aiRemovedDeps.forEach((dep) => { - const depTask = data.tasks.find((t) => t.id === dep); - const title = depTask ? truncate(depTask.title, 30) : "Unknown task"; - dependencyDisplay += chalk.gray(` - ${dep}: ${title}`) + "\n"; - }); - } + // Add info about removed dependencies if any + if (aiRemovedDeps.length > 0) { + dependencyDisplay += + chalk.gray('\nUser-specified dependencies that were not used:') + + '\n'; + aiRemovedDeps.forEach((dep) => { + const depTask = data.tasks.find((t) => t.id === dep); + const title = depTask ? truncate(depTask.title, 30) : 'Unknown task'; + dependencyDisplay += chalk.gray(` - ${dep}: ${title}`) + '\n'; + }); + } - // Add dependency analysis summary - let dependencyAnalysis = ""; - if (aiAddedDeps.length > 0 || aiRemovedDeps.length > 0) { - dependencyAnalysis = - "\n" + chalk.white.bold("Dependency Analysis:") + "\n"; - if (aiAddedDeps.length > 0) { - dependencyAnalysis += - chalk.green( - `AI identified ${aiAddedDeps.length} additional dependencies` - ) + "\n"; - } - if (aiRemovedDeps.length > 0) { - dependencyAnalysis += - chalk.yellow( - `AI excluded ${aiRemovedDeps.length} user-provided dependencies` - ) + "\n"; - } - } + // Add dependency analysis summary + let dependencyAnalysis = ''; + if (aiAddedDeps.length > 0 || aiRemovedDeps.length > 0) { + dependencyAnalysis = + '\n' + chalk.white.bold('Dependency Analysis:') + '\n'; + if (aiAddedDeps.length > 0) { + dependencyAnalysis += + chalk.green( + `AI identified ${aiAddedDeps.length} additional dependencies` + ) + '\n'; + } + if (aiRemovedDeps.length > 0) { + dependencyAnalysis += + chalk.yellow( + `AI excluded ${aiRemovedDeps.length} user-provided dependencies` + ) + '\n'; + } + } - // Show success message box - console.log( - boxen( - chalk.white.bold(`Task ${newTaskId} Created Successfully`) + - "\n\n" + - chalk.white(`Title: ${newTask.title}`) + - "\n" + - chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) + - "\n" + - chalk.white( - `Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}` - ) + - "\n\n" + - dependencyDisplay + - dependencyAnalysis + - "\n" + - chalk.white.bold("Next Steps:") + - "\n" + - chalk.cyan( - `1. Run ${chalk.yellow(`task-master show ${newTaskId}`)} to see complete task details` - ) + - "\n" + - chalk.cyan( - `2. Run ${chalk.yellow(`task-master set-status --id=${newTaskId} --status=in-progress`)} to start working on it` - ) + - "\n" + - chalk.cyan( - `3. Run ${chalk.yellow(`task-master expand --id=${newTaskId}`)} to break it down into subtasks` - ), - { padding: 1, borderColor: "green", borderStyle: "round" } - ) - ); + // Show success message box + console.log( + boxen( + chalk.white.bold(`Task ${newTaskId} Created Successfully`) + + '\n\n' + + chalk.white(`Title: ${newTask.title}`) + + '\n' + + chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) + + '\n' + + chalk.white( + `Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}` + ) + + '\n\n' + + dependencyDisplay + + dependencyAnalysis + + '\n' + + chalk.white.bold('Next Steps:') + + '\n' + + chalk.cyan( + `1. Run ${chalk.yellow(`task-master show ${newTaskId}`)} to see complete task details` + ) + + '\n' + + chalk.cyan( + `2. Run ${chalk.yellow(`task-master set-status --id=${newTaskId} --status=in-progress`)} to start working on it` + ) + + '\n' + + chalk.cyan( + `3. Run ${chalk.yellow(`task-master expand --id=${newTaskId}`)} to break it down into subtasks` + ), + { padding: 1, borderColor: 'green', borderStyle: 'round' } + ) + ); - // Display AI Usage Summary if telemetryData is available - if ( - aiServiceResponse && - aiServiceResponse.telemetryData && - (outputType === "cli" || outputType === "text") - ) { - displayAiUsageSummary(aiServiceResponse.telemetryData, "cli"); - } - } + // Display AI Usage Summary if telemetryData is available + if ( + aiServiceResponse && + aiServiceResponse.telemetryData && + (outputType === 'cli' || outputType === 'text') + ) { + displayAiUsageSummary(aiServiceResponse.telemetryData, 'cli'); + } + } - report( - `DEBUG: Returning new task ID: ${newTaskId} and telemetry.`, - "debug" - ); - return { - newTaskId: newTaskId, - telemetryData: aiServiceResponse ? aiServiceResponse.telemetryData : null, - }; - } catch (error) { - // Stop any loading indicator on error - if (loadingIndicator) { - stopLoadingIndicator(loadingIndicator); - } + report( + `DEBUG: Returning new task ID: ${newTaskId} and telemetry.`, + 'debug' + ); + return { + newTaskId: newTaskId, + telemetryData: aiServiceResponse ? aiServiceResponse.telemetryData : null + }; + } catch (error) { + // Stop any loading indicator on error + if (loadingIndicator) { + stopLoadingIndicator(loadingIndicator); + } - report(`Error adding task: ${error.message}`, "error"); - if (outputFormat === "text") { - console.error(chalk.red(`Error: ${error.message}`)); - } - // In MCP mode, we let the direct function handler catch and format - throw error; - } + report(`Error adding task: ${error.message}`, 'error'); + if (outputFormat === 'text') { + console.error(chalk.red(`Error: ${error.message}`)); + } + // In MCP mode, we let the direct function handler catch and format + throw error; + } } export default addTask; diff --git a/scripts/modules/task-manager/clear-subtasks.js b/scripts/modules/task-manager/clear-subtasks.js index f07dd897..e69f8442 100644 --- a/scripts/modules/task-manager/clear-subtasks.js +++ b/scripts/modules/task-manager/clear-subtasks.js @@ -1,11 +1,11 @@ -import path from "path"; -import chalk from "chalk"; -import boxen from "boxen"; -import Table from "cli-table3"; +import path from 'path'; +import chalk from 'chalk'; +import boxen from 'boxen'; +import Table from 'cli-table3'; -import { log, readJSON, writeJSON, truncate, isSilentMode } from "../utils.js"; -import { displayBanner } from "../ui.js"; -import generateTaskFiles from "./generate-task-files.js"; +import { log, readJSON, writeJSON, truncate, isSilentMode } from '../utils.js'; +import { displayBanner } from '../ui.js'; +import generateTaskFiles from './generate-task-files.js'; /** * Clear subtasks from specified tasks @@ -13,138 +13,138 @@ import generateTaskFiles from "./generate-task-files.js"; * @param {string} taskIds - Task IDs to clear subtasks from */ function clearSubtasks(tasksPath, taskIds) { - log("info", `Reading tasks from ${tasksPath}...`); - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log("error", "No valid tasks found."); - process.exit(1); - } + log('info', `Reading tasks from ${tasksPath}...`); + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log('error', 'No valid tasks found.'); + process.exit(1); + } - if (!isSilentMode()) { - console.log( - boxen(chalk.white.bold("Clearing Subtasks"), { - padding: 1, - borderColor: "blue", - borderStyle: "round", - margin: { top: 1, bottom: 1 }, - }) - ); - } + if (!isSilentMode()) { + console.log( + boxen(chalk.white.bold('Clearing Subtasks'), { + padding: 1, + borderColor: 'blue', + borderStyle: 'round', + margin: { top: 1, bottom: 1 } + }) + ); + } - // Handle multiple task IDs (comma-separated) - const taskIdArray = taskIds.split(",").map((id) => id.trim()); - let clearedCount = 0; + // Handle multiple task IDs (comma-separated) + const taskIdArray = taskIds.split(',').map((id) => id.trim()); + let clearedCount = 0; - // Create a summary table for the cleared subtasks - const summaryTable = new Table({ - head: [ - chalk.cyan.bold("Task ID"), - chalk.cyan.bold("Task Title"), - chalk.cyan.bold("Subtasks Cleared"), - ], - colWidths: [10, 50, 20], - style: { head: [], border: [] }, - }); + // Create a summary table for the cleared subtasks + const summaryTable = new Table({ + head: [ + chalk.cyan.bold('Task ID'), + chalk.cyan.bold('Task Title'), + chalk.cyan.bold('Subtasks Cleared') + ], + colWidths: [10, 50, 20], + style: { head: [], border: [] } + }); - taskIdArray.forEach((taskId) => { - const id = parseInt(taskId, 10); - if (isNaN(id)) { - log("error", `Invalid task ID: ${taskId}`); - return; - } + taskIdArray.forEach((taskId) => { + const id = parseInt(taskId, 10); + if (isNaN(id)) { + log('error', `Invalid task ID: ${taskId}`); + return; + } - const task = data.tasks.find((t) => t.id === id); - if (!task) { - log("error", `Task ${id} not found`); - return; - } + const task = data.tasks.find((t) => t.id === id); + if (!task) { + log('error', `Task ${id} not found`); + return; + } - if (!task.subtasks || task.subtasks.length === 0) { - log("info", `Task ${id} has no subtasks to clear`); - summaryTable.push([ - id.toString(), - truncate(task.title, 47), - chalk.yellow("No subtasks"), - ]); - return; - } + if (!task.subtasks || task.subtasks.length === 0) { + log('info', `Task ${id} has no subtasks to clear`); + summaryTable.push([ + id.toString(), + truncate(task.title, 47), + chalk.yellow('No subtasks') + ]); + return; + } - const subtaskCount = task.subtasks.length; - task.subtasks = []; - clearedCount++; - log("info", `Cleared ${subtaskCount} subtasks from task ${id}`); + const subtaskCount = task.subtasks.length; + task.subtasks = []; + clearedCount++; + log('info', `Cleared ${subtaskCount} subtasks from task ${id}`); - summaryTable.push([ - id.toString(), - truncate(task.title, 47), - chalk.green(`${subtaskCount} subtasks cleared`), - ]); - }); + summaryTable.push([ + id.toString(), + truncate(task.title, 47), + chalk.green(`${subtaskCount} subtasks cleared`) + ]); + }); - if (clearedCount > 0) { - writeJSON(tasksPath, data); + if (clearedCount > 0) { + writeJSON(tasksPath, data); - // Show summary table - if (!isSilentMode()) { - console.log( - boxen(chalk.white.bold("Subtask Clearing Summary:"), { - padding: { left: 2, right: 2, top: 0, bottom: 0 }, - margin: { top: 1, bottom: 0 }, - borderColor: "blue", - borderStyle: "round", - }) - ); - console.log(summaryTable.toString()); - } + // Show summary table + if (!isSilentMode()) { + console.log( + boxen(chalk.white.bold('Subtask Clearing Summary:'), { + padding: { left: 2, right: 2, top: 0, bottom: 0 }, + margin: { top: 1, bottom: 0 }, + borderColor: 'blue', + borderStyle: 'round' + }) + ); + console.log(summaryTable.toString()); + } - // Regenerate task files to reflect changes - log("info", "Regenerating task files..."); - generateTaskFiles(tasksPath, path.dirname(tasksPath)); + // Regenerate task files to reflect changes + log('info', 'Regenerating task files...'); + generateTaskFiles(tasksPath, path.dirname(tasksPath)); - // Success message - if (!isSilentMode()) { - console.log( - boxen( - chalk.green( - `Successfully cleared subtasks from ${chalk.bold(clearedCount)} task(s)` - ), - { - padding: 1, - borderColor: "green", - borderStyle: "round", - margin: { top: 1 }, - } - ) - ); + // Success message + if (!isSilentMode()) { + console.log( + boxen( + chalk.green( + `Successfully cleared subtasks from ${chalk.bold(clearedCount)} task(s)` + ), + { + padding: 1, + borderColor: 'green', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); - // Next steps suggestion - console.log( - boxen( - chalk.white.bold("Next Steps:") + - "\n\n" + - `${chalk.cyan("1.")} Run ${chalk.yellow("task-master expand --id=<id>")} to generate new subtasks\n` + - `${chalk.cyan("2.")} Run ${chalk.yellow("task-master list --with-subtasks")} to verify changes`, - { - padding: 1, - borderColor: "cyan", - borderStyle: "round", - margin: { top: 1 }, - } - ) - ); - } - } else { - if (!isSilentMode()) { - console.log( - boxen(chalk.yellow("No subtasks were cleared"), { - padding: 1, - borderColor: "yellow", - borderStyle: "round", - margin: { top: 1 }, - }) - ); - } - } + // Next steps suggestion + console.log( + boxen( + chalk.white.bold('Next Steps:') + + '\n\n' + + `${chalk.cyan('1.')} Run ${chalk.yellow('task-master expand --id=<id>')} to generate new subtasks\n` + + `${chalk.cyan('2.')} Run ${chalk.yellow('task-master list --with-subtasks')} to verify changes`, + { + padding: 1, + borderColor: 'cyan', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); + } + } else { + if (!isSilentMode()) { + console.log( + boxen(chalk.yellow('No subtasks were cleared'), { + padding: 1, + borderColor: 'yellow', + borderStyle: 'round', + margin: { top: 1 } + }) + ); + } + } } export default clearSubtasks; diff --git a/scripts/modules/task-manager/list-tasks.js b/scripts/modules/task-manager/list-tasks.js index d82768cd..51f37460 100644 --- a/scripts/modules/task-manager/list-tasks.js +++ b/scripts/modules/task-manager/list-tasks.js @@ -1,23 +1,23 @@ -import chalk from "chalk"; -import boxen from "boxen"; -import Table from "cli-table3"; +import chalk from 'chalk'; +import boxen from 'boxen'; +import Table from 'cli-table3'; import { - log, - readJSON, - truncate, - readComplexityReport, - addComplexityToTask, -} from "../utils.js"; -import findNextTask from "./find-next-task.js"; + log, + readJSON, + truncate, + readComplexityReport, + addComplexityToTask +} from '../utils.js'; +import findNextTask from './find-next-task.js'; import { - displayBanner, - getStatusWithColor, - formatDependenciesWithStatus, - getComplexityWithColor, - createProgressBar, -} from "../ui.js"; + displayBanner, + getStatusWithColor, + formatDependenciesWithStatus, + getComplexityWithColor, + createProgressBar +} from '../ui.js'; /** * List all tasks @@ -29,734 +29,734 @@ import { * @returns {Object} - Task list result for json format */ function listTasks( - tasksPath, - statusFilter, - reportPath = null, - withSubtasks = false, - outputFormat = "text" + tasksPath, + statusFilter, + reportPath = null, + withSubtasks = false, + outputFormat = 'text' ) { - try { - const data = readJSON(tasksPath); // Reads the whole tasks.json - if (!data || !data.tasks) { - throw new Error(`No valid tasks found in ${tasksPath}`); - } + try { + const data = readJSON(tasksPath); // Reads the whole tasks.json + if (!data || !data.tasks) { + throw new Error(`No valid tasks found in ${tasksPath}`); + } - // Add complexity scores to tasks if report exists - const complexityReport = readComplexityReport(reportPath); - // Apply complexity scores to tasks - if (complexityReport && complexityReport.complexityAnalysis) { - data.tasks.forEach((task) => addComplexityToTask(task, complexityReport)); - } + // Add complexity scores to tasks if report exists + const complexityReport = readComplexityReport(reportPath); + // Apply complexity scores to tasks + if (complexityReport && complexityReport.complexityAnalysis) { + data.tasks.forEach((task) => addComplexityToTask(task, complexityReport)); + } - // Filter tasks by status if specified - const filteredTasks = - statusFilter && statusFilter.toLowerCase() !== "all" // <-- Added check for 'all' - ? data.tasks.filter( - (task) => - task.status && - task.status.toLowerCase() === statusFilter.toLowerCase() - ) - : data.tasks; // Default to all tasks if no filter or filter is 'all' + // Filter tasks by status if specified + const filteredTasks = + statusFilter && statusFilter.toLowerCase() !== 'all' // <-- Added check for 'all' + ? data.tasks.filter( + (task) => + task.status && + task.status.toLowerCase() === statusFilter.toLowerCase() + ) + : data.tasks; // Default to all tasks if no filter or filter is 'all' - // Calculate completion statistics - const totalTasks = data.tasks.length; - const completedTasks = data.tasks.filter( - (task) => task.status === "done" || task.status === "completed" - ).length; - const completionPercentage = - totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0; + // Calculate completion statistics + const totalTasks = data.tasks.length; + const completedTasks = data.tasks.filter( + (task) => task.status === 'done' || task.status === 'completed' + ).length; + const completionPercentage = + totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0; - // Count statuses for tasks - const doneCount = completedTasks; - const inProgressCount = data.tasks.filter( - (task) => task.status === "in-progress" - ).length; - const pendingCount = data.tasks.filter( - (task) => task.status === "pending" - ).length; - const blockedCount = data.tasks.filter( - (task) => task.status === "blocked" - ).length; - const deferredCount = data.tasks.filter( - (task) => task.status === "deferred" - ).length; - const cancelledCount = data.tasks.filter( - (task) => task.status === "cancelled" - ).length; + // Count statuses for tasks + const doneCount = completedTasks; + const inProgressCount = data.tasks.filter( + (task) => task.status === 'in-progress' + ).length; + const pendingCount = data.tasks.filter( + (task) => task.status === 'pending' + ).length; + const blockedCount = data.tasks.filter( + (task) => task.status === 'blocked' + ).length; + const deferredCount = data.tasks.filter( + (task) => task.status === 'deferred' + ).length; + const cancelledCount = data.tasks.filter( + (task) => task.status === 'cancelled' + ).length; - // Count subtasks and their statuses - let totalSubtasks = 0; - let completedSubtasks = 0; - let inProgressSubtasks = 0; - let pendingSubtasks = 0; - let blockedSubtasks = 0; - let deferredSubtasks = 0; - let cancelledSubtasks = 0; + // Count subtasks and their statuses + let totalSubtasks = 0; + let completedSubtasks = 0; + let inProgressSubtasks = 0; + let pendingSubtasks = 0; + let blockedSubtasks = 0; + let deferredSubtasks = 0; + let cancelledSubtasks = 0; - data.tasks.forEach((task) => { - if (task.subtasks && task.subtasks.length > 0) { - totalSubtasks += task.subtasks.length; - completedSubtasks += task.subtasks.filter( - (st) => st.status === "done" || st.status === "completed" - ).length; - inProgressSubtasks += task.subtasks.filter( - (st) => st.status === "in-progress" - ).length; - pendingSubtasks += task.subtasks.filter( - (st) => st.status === "pending" - ).length; - blockedSubtasks += task.subtasks.filter( - (st) => st.status === "blocked" - ).length; - deferredSubtasks += task.subtasks.filter( - (st) => st.status === "deferred" - ).length; - cancelledSubtasks += task.subtasks.filter( - (st) => st.status === "cancelled" - ).length; - } - }); + data.tasks.forEach((task) => { + if (task.subtasks && task.subtasks.length > 0) { + totalSubtasks += task.subtasks.length; + completedSubtasks += task.subtasks.filter( + (st) => st.status === 'done' || st.status === 'completed' + ).length; + inProgressSubtasks += task.subtasks.filter( + (st) => st.status === 'in-progress' + ).length; + pendingSubtasks += task.subtasks.filter( + (st) => st.status === 'pending' + ).length; + blockedSubtasks += task.subtasks.filter( + (st) => st.status === 'blocked' + ).length; + deferredSubtasks += task.subtasks.filter( + (st) => st.status === 'deferred' + ).length; + cancelledSubtasks += task.subtasks.filter( + (st) => st.status === 'cancelled' + ).length; + } + }); - const subtaskCompletionPercentage = - totalSubtasks > 0 ? (completedSubtasks / totalSubtasks) * 100 : 0; + const subtaskCompletionPercentage = + totalSubtasks > 0 ? (completedSubtasks / totalSubtasks) * 100 : 0; - // For JSON output, return structured data - if (outputFormat === "json") { - // *** Modification: Remove 'details' field for JSON output *** - const tasksWithoutDetails = filteredTasks.map((task) => { - // <-- USES filteredTasks! - // Omit 'details' from the parent task - const { details, ...taskRest } = task; + // For JSON output, return structured data + if (outputFormat === 'json') { + // *** Modification: Remove 'details' field for JSON output *** + const tasksWithoutDetails = filteredTasks.map((task) => { + // <-- USES filteredTasks! + // Omit 'details' from the parent task + const { details, ...taskRest } = task; - // If subtasks exist, omit 'details' from them too - if (taskRest.subtasks && Array.isArray(taskRest.subtasks)) { - taskRest.subtasks = taskRest.subtasks.map((subtask) => { - const { details: subtaskDetails, ...subtaskRest } = subtask; - return subtaskRest; - }); - } - return taskRest; - }); - // *** End of Modification *** + // If subtasks exist, omit 'details' from them too + if (taskRest.subtasks && Array.isArray(taskRest.subtasks)) { + taskRest.subtasks = taskRest.subtasks.map((subtask) => { + const { details: subtaskDetails, ...subtaskRest } = subtask; + return subtaskRest; + }); + } + return taskRest; + }); + // *** End of Modification *** - return { - tasks: tasksWithoutDetails, // <--- THIS IS THE ARRAY BEING RETURNED - filter: statusFilter || "all", // Return the actual filter used - stats: { - total: totalTasks, - completed: doneCount, - inProgress: inProgressCount, - pending: pendingCount, - blocked: blockedCount, - deferred: deferredCount, - cancelled: cancelledCount, - completionPercentage, - subtasks: { - total: totalSubtasks, - completed: completedSubtasks, - inProgress: inProgressSubtasks, - pending: pendingSubtasks, - blocked: blockedSubtasks, - deferred: deferredSubtasks, - cancelled: cancelledSubtasks, - completionPercentage: subtaskCompletionPercentage, - }, - }, - }; - } + return { + tasks: tasksWithoutDetails, // <--- THIS IS THE ARRAY BEING RETURNED + filter: statusFilter || 'all', // Return the actual filter used + stats: { + total: totalTasks, + completed: doneCount, + inProgress: inProgressCount, + pending: pendingCount, + blocked: blockedCount, + deferred: deferredCount, + cancelled: cancelledCount, + completionPercentage, + subtasks: { + total: totalSubtasks, + completed: completedSubtasks, + inProgress: inProgressSubtasks, + pending: pendingSubtasks, + blocked: blockedSubtasks, + deferred: deferredSubtasks, + cancelled: cancelledSubtasks, + completionPercentage: subtaskCompletionPercentage + } + } + }; + } - // ... existing code for text output ... + // ... existing code for text output ... - // Calculate status breakdowns as percentages of total - const taskStatusBreakdown = { - "in-progress": totalTasks > 0 ? (inProgressCount / totalTasks) * 100 : 0, - pending: totalTasks > 0 ? (pendingCount / totalTasks) * 100 : 0, - blocked: totalTasks > 0 ? (blockedCount / totalTasks) * 100 : 0, - deferred: totalTasks > 0 ? (deferredCount / totalTasks) * 100 : 0, - cancelled: totalTasks > 0 ? (cancelledCount / totalTasks) * 100 : 0, - }; + // Calculate status breakdowns as percentages of total + const taskStatusBreakdown = { + 'in-progress': totalTasks > 0 ? (inProgressCount / totalTasks) * 100 : 0, + pending: totalTasks > 0 ? (pendingCount / totalTasks) * 100 : 0, + blocked: totalTasks > 0 ? (blockedCount / totalTasks) * 100 : 0, + deferred: totalTasks > 0 ? (deferredCount / totalTasks) * 100 : 0, + cancelled: totalTasks > 0 ? (cancelledCount / totalTasks) * 100 : 0 + }; - const subtaskStatusBreakdown = { - "in-progress": - totalSubtasks > 0 ? (inProgressSubtasks / totalSubtasks) * 100 : 0, - pending: totalSubtasks > 0 ? (pendingSubtasks / totalSubtasks) * 100 : 0, - blocked: totalSubtasks > 0 ? (blockedSubtasks / totalSubtasks) * 100 : 0, - deferred: - totalSubtasks > 0 ? (deferredSubtasks / totalSubtasks) * 100 : 0, - cancelled: - totalSubtasks > 0 ? (cancelledSubtasks / totalSubtasks) * 100 : 0, - }; + const subtaskStatusBreakdown = { + 'in-progress': + totalSubtasks > 0 ? (inProgressSubtasks / totalSubtasks) * 100 : 0, + pending: totalSubtasks > 0 ? (pendingSubtasks / totalSubtasks) * 100 : 0, + blocked: totalSubtasks > 0 ? (blockedSubtasks / totalSubtasks) * 100 : 0, + deferred: + totalSubtasks > 0 ? (deferredSubtasks / totalSubtasks) * 100 : 0, + cancelled: + totalSubtasks > 0 ? (cancelledSubtasks / totalSubtasks) * 100 : 0 + }; - // Create progress bars with status breakdowns - const taskProgressBar = createProgressBar( - completionPercentage, - 30, - taskStatusBreakdown - ); - const subtaskProgressBar = createProgressBar( - subtaskCompletionPercentage, - 30, - subtaskStatusBreakdown - ); + // Create progress bars with status breakdowns + const taskProgressBar = createProgressBar( + completionPercentage, + 30, + taskStatusBreakdown + ); + const subtaskProgressBar = createProgressBar( + subtaskCompletionPercentage, + 30, + subtaskStatusBreakdown + ); - // Calculate dependency statistics - const completedTaskIds = new Set( - data.tasks - .filter((t) => t.status === "done" || t.status === "completed") - .map((t) => t.id) - ); + // Calculate dependency statistics + const completedTaskIds = new Set( + data.tasks + .filter((t) => t.status === 'done' || t.status === 'completed') + .map((t) => t.id) + ); - const tasksWithNoDeps = data.tasks.filter( - (t) => - t.status !== "done" && - t.status !== "completed" && - (!t.dependencies || t.dependencies.length === 0) - ).length; + const tasksWithNoDeps = data.tasks.filter( + (t) => + t.status !== 'done' && + t.status !== 'completed' && + (!t.dependencies || t.dependencies.length === 0) + ).length; - const tasksWithAllDepsSatisfied = data.tasks.filter( - (t) => - t.status !== "done" && - t.status !== "completed" && - t.dependencies && - t.dependencies.length > 0 && - t.dependencies.every((depId) => completedTaskIds.has(depId)) - ).length; + const tasksWithAllDepsSatisfied = data.tasks.filter( + (t) => + t.status !== 'done' && + t.status !== 'completed' && + t.dependencies && + t.dependencies.length > 0 && + t.dependencies.every((depId) => completedTaskIds.has(depId)) + ).length; - const tasksWithUnsatisfiedDeps = data.tasks.filter( - (t) => - t.status !== "done" && - t.status !== "completed" && - t.dependencies && - t.dependencies.length > 0 && - !t.dependencies.every((depId) => completedTaskIds.has(depId)) - ).length; + const tasksWithUnsatisfiedDeps = data.tasks.filter( + (t) => + t.status !== 'done' && + t.status !== 'completed' && + t.dependencies && + t.dependencies.length > 0 && + !t.dependencies.every((depId) => completedTaskIds.has(depId)) + ).length; - // Calculate total tasks ready to work on (no deps + satisfied deps) - const tasksReadyToWork = tasksWithNoDeps + tasksWithAllDepsSatisfied; + // Calculate total tasks ready to work on (no deps + satisfied deps) + const tasksReadyToWork = tasksWithNoDeps + tasksWithAllDepsSatisfied; - // Calculate most depended-on tasks - const dependencyCount = {}; - data.tasks.forEach((task) => { - if (task.dependencies && task.dependencies.length > 0) { - task.dependencies.forEach((depId) => { - dependencyCount[depId] = (dependencyCount[depId] || 0) + 1; - }); - } - }); + // Calculate most depended-on tasks + const dependencyCount = {}; + data.tasks.forEach((task) => { + if (task.dependencies && task.dependencies.length > 0) { + task.dependencies.forEach((depId) => { + dependencyCount[depId] = (dependencyCount[depId] || 0) + 1; + }); + } + }); - // Find the most depended-on task - let mostDependedOnTaskId = null; - let maxDependents = 0; + // Find the most depended-on task + let mostDependedOnTaskId = null; + let maxDependents = 0; - for (const [taskId, count] of Object.entries(dependencyCount)) { - if (count > maxDependents) { - maxDependents = count; - mostDependedOnTaskId = parseInt(taskId); - } - } + for (const [taskId, count] of Object.entries(dependencyCount)) { + if (count > maxDependents) { + maxDependents = count; + mostDependedOnTaskId = parseInt(taskId); + } + } - // Get the most depended-on task - const mostDependedOnTask = - mostDependedOnTaskId !== null - ? data.tasks.find((t) => t.id === mostDependedOnTaskId) - : null; + // Get the most depended-on task + const mostDependedOnTask = + mostDependedOnTaskId !== null + ? data.tasks.find((t) => t.id === mostDependedOnTaskId) + : null; - // Calculate average dependencies per task - const totalDependencies = data.tasks.reduce( - (sum, task) => sum + (task.dependencies ? task.dependencies.length : 0), - 0 - ); - const avgDependenciesPerTask = totalDependencies / data.tasks.length; + // Calculate average dependencies per task + const totalDependencies = data.tasks.reduce( + (sum, task) => sum + (task.dependencies ? task.dependencies.length : 0), + 0 + ); + const avgDependenciesPerTask = totalDependencies / data.tasks.length; - // Find next task to work on, passing the complexity report - const nextItem = findNextTask(data.tasks, complexityReport); + // Find next task to work on, passing the complexity report + const nextItem = findNextTask(data.tasks, complexityReport); - // Get terminal width - more reliable method - let terminalWidth; - try { - // Try to get the actual terminal columns - terminalWidth = process.stdout.columns; - } catch (e) { - // Fallback if columns cannot be determined - log("debug", "Could not determine terminal width, using default"); - } - // Ensure we have a reasonable default if detection fails - terminalWidth = terminalWidth || 80; + // Get terminal width - more reliable method + let terminalWidth; + try { + // Try to get the actual terminal columns + terminalWidth = process.stdout.columns; + } catch (e) { + // Fallback if columns cannot be determined + log('debug', 'Could not determine terminal width, using default'); + } + // Ensure we have a reasonable default if detection fails + terminalWidth = terminalWidth || 80; - // Ensure terminal width is at least a minimum value to prevent layout issues - terminalWidth = Math.max(terminalWidth, 80); + // Ensure terminal width is at least a minimum value to prevent layout issues + terminalWidth = Math.max(terminalWidth, 80); - // Create dashboard content - const projectDashboardContent = - chalk.white.bold("Project Dashboard") + - "\n" + - `Tasks Progress: ${chalk.greenBright(taskProgressBar)} ${completionPercentage.toFixed(0)}%\n` + - `Done: ${chalk.green(doneCount)} In Progress: ${chalk.blue(inProgressCount)} Pending: ${chalk.yellow(pendingCount)} Blocked: ${chalk.red(blockedCount)} Deferred: ${chalk.gray(deferredCount)} Cancelled: ${chalk.gray(cancelledCount)}\n\n` + - `Subtasks Progress: ${chalk.cyan(subtaskProgressBar)} ${subtaskCompletionPercentage.toFixed(0)}%\n` + - `Completed: ${chalk.green(completedSubtasks)}/${totalSubtasks} In Progress: ${chalk.blue(inProgressSubtasks)} Pending: ${chalk.yellow(pendingSubtasks)} Blocked: ${chalk.red(blockedSubtasks)} Deferred: ${chalk.gray(deferredSubtasks)} Cancelled: ${chalk.gray(cancelledSubtasks)}\n\n` + - chalk.cyan.bold("Priority Breakdown:") + - "\n" + - `${chalk.red("โ€ข")} ${chalk.white("High priority:")} ${data.tasks.filter((t) => t.priority === "high").length}\n` + - `${chalk.yellow("โ€ข")} ${chalk.white("Medium priority:")} ${data.tasks.filter((t) => t.priority === "medium").length}\n` + - `${chalk.green("โ€ข")} ${chalk.white("Low priority:")} ${data.tasks.filter((t) => t.priority === "low").length}`; + // Create dashboard content + const projectDashboardContent = + chalk.white.bold('Project Dashboard') + + '\n' + + `Tasks Progress: ${chalk.greenBright(taskProgressBar)} ${completionPercentage.toFixed(0)}%\n` + + `Done: ${chalk.green(doneCount)} In Progress: ${chalk.blue(inProgressCount)} Pending: ${chalk.yellow(pendingCount)} Blocked: ${chalk.red(blockedCount)} Deferred: ${chalk.gray(deferredCount)} Cancelled: ${chalk.gray(cancelledCount)}\n\n` + + `Subtasks Progress: ${chalk.cyan(subtaskProgressBar)} ${subtaskCompletionPercentage.toFixed(0)}%\n` + + `Completed: ${chalk.green(completedSubtasks)}/${totalSubtasks} In Progress: ${chalk.blue(inProgressSubtasks)} Pending: ${chalk.yellow(pendingSubtasks)} Blocked: ${chalk.red(blockedSubtasks)} Deferred: ${chalk.gray(deferredSubtasks)} Cancelled: ${chalk.gray(cancelledSubtasks)}\n\n` + + chalk.cyan.bold('Priority Breakdown:') + + '\n' + + `${chalk.red('โ€ข')} ${chalk.white('High priority:')} ${data.tasks.filter((t) => t.priority === 'high').length}\n` + + `${chalk.yellow('โ€ข')} ${chalk.white('Medium priority:')} ${data.tasks.filter((t) => t.priority === 'medium').length}\n` + + `${chalk.green('โ€ข')} ${chalk.white('Low priority:')} ${data.tasks.filter((t) => t.priority === 'low').length}`; - const dependencyDashboardContent = - chalk.white.bold("Dependency Status & Next Task") + - "\n" + - chalk.cyan.bold("Dependency Metrics:") + - "\n" + - `${chalk.green("โ€ข")} ${chalk.white("Tasks with no dependencies:")} ${tasksWithNoDeps}\n` + - `${chalk.green("โ€ข")} ${chalk.white("Tasks ready to work on:")} ${tasksReadyToWork}\n` + - `${chalk.yellow("โ€ข")} ${chalk.white("Tasks blocked by dependencies:")} ${tasksWithUnsatisfiedDeps}\n` + - `${chalk.magenta("โ€ข")} ${chalk.white("Most depended-on task:")} ${mostDependedOnTask ? chalk.cyan(`#${mostDependedOnTaskId} (${maxDependents} dependents)`) : chalk.gray("None")}\n` + - `${chalk.blue("โ€ข")} ${chalk.white("Avg dependencies per task:")} ${avgDependenciesPerTask.toFixed(1)}\n\n` + - chalk.cyan.bold("Next Task to Work On:") + - "\n" + - `ID: ${chalk.cyan(nextItem ? nextItem.id : "N/A")} - ${nextItem ? chalk.white.bold(truncate(nextItem.title, 40)) : chalk.yellow("No task available")} + const dependencyDashboardContent = + chalk.white.bold('Dependency Status & Next Task') + + '\n' + + chalk.cyan.bold('Dependency Metrics:') + + '\n' + + `${chalk.green('โ€ข')} ${chalk.white('Tasks with no dependencies:')} ${tasksWithNoDeps}\n` + + `${chalk.green('โ€ข')} ${chalk.white('Tasks ready to work on:')} ${tasksReadyToWork}\n` + + `${chalk.yellow('โ€ข')} ${chalk.white('Tasks blocked by dependencies:')} ${tasksWithUnsatisfiedDeps}\n` + + `${chalk.magenta('โ€ข')} ${chalk.white('Most depended-on task:')} ${mostDependedOnTask ? chalk.cyan(`#${mostDependedOnTaskId} (${maxDependents} dependents)`) : chalk.gray('None')}\n` + + `${chalk.blue('โ€ข')} ${chalk.white('Avg dependencies per task:')} ${avgDependenciesPerTask.toFixed(1)}\n\n` + + chalk.cyan.bold('Next Task to Work On:') + + '\n' + + `ID: ${chalk.cyan(nextItem ? nextItem.id : 'N/A')} - ${nextItem ? chalk.white.bold(truncate(nextItem.title, 40)) : chalk.yellow('No task available')} ` + - `Priority: ${nextItem ? chalk.white(nextItem.priority || "medium") : ""} Dependencies: ${nextItem ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : ""} + `Priority: ${nextItem ? chalk.white(nextItem.priority || 'medium') : ''} Dependencies: ${nextItem ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : ''} ` + - `Complexity: ${nextItem && nextItem.complexityScore ? getComplexityWithColor(nextItem.complexityScore) : chalk.gray("N/A")}`; + `Complexity: ${nextItem && nextItem.complexityScore ? getComplexityWithColor(nextItem.complexityScore) : chalk.gray('N/A')}`; - // Calculate width for side-by-side display - // Box borders, padding take approximately 4 chars on each side - const minDashboardWidth = 50; // Minimum width for dashboard - const minDependencyWidth = 50; // Minimum width for dependency dashboard - const totalMinWidth = minDashboardWidth + minDependencyWidth + 4; // Extra 4 chars for spacing + // Calculate width for side-by-side display + // Box borders, padding take approximately 4 chars on each side + const minDashboardWidth = 50; // Minimum width for dashboard + const minDependencyWidth = 50; // Minimum width for dependency dashboard + const totalMinWidth = minDashboardWidth + minDependencyWidth + 4; // Extra 4 chars for spacing - // If terminal is wide enough, show boxes side by side with responsive widths - if (terminalWidth >= totalMinWidth) { - // Calculate widths proportionally for each box - use exact 50% width each - const availableWidth = terminalWidth; - const halfWidth = Math.floor(availableWidth / 2); + // If terminal is wide enough, show boxes side by side with responsive widths + if (terminalWidth >= totalMinWidth) { + // Calculate widths proportionally for each box - use exact 50% width each + const availableWidth = terminalWidth; + const halfWidth = Math.floor(availableWidth / 2); - // Account for border characters (2 chars on each side) - const boxContentWidth = halfWidth - 4; + // Account for border characters (2 chars on each side) + const boxContentWidth = halfWidth - 4; - // Create boxen options with precise widths - const dashboardBox = boxen(projectDashboardContent, { - padding: 1, - borderColor: "blue", - borderStyle: "round", - width: boxContentWidth, - dimBorder: false, - }); + // Create boxen options with precise widths + const dashboardBox = boxen(projectDashboardContent, { + padding: 1, + borderColor: 'blue', + borderStyle: 'round', + width: boxContentWidth, + dimBorder: false + }); - const dependencyBox = boxen(dependencyDashboardContent, { - padding: 1, - borderColor: "magenta", - borderStyle: "round", - width: boxContentWidth, - dimBorder: false, - }); + const dependencyBox = boxen(dependencyDashboardContent, { + padding: 1, + borderColor: 'magenta', + borderStyle: 'round', + width: boxContentWidth, + dimBorder: false + }); - // Create a better side-by-side layout with exact spacing - const dashboardLines = dashboardBox.split("\n"); - const dependencyLines = dependencyBox.split("\n"); + // Create a better side-by-side layout with exact spacing + const dashboardLines = dashboardBox.split('\n'); + const dependencyLines = dependencyBox.split('\n'); - // Make sure both boxes have the same height - const maxHeight = Math.max(dashboardLines.length, dependencyLines.length); + // Make sure both boxes have the same height + const maxHeight = Math.max(dashboardLines.length, dependencyLines.length); - // For each line of output, pad the dashboard line to exactly halfWidth chars - // This ensures the dependency box starts at exactly the right position - const combinedLines = []; - for (let i = 0; i < maxHeight; i++) { - // Get the dashboard line (or empty string if we've run out of lines) - const dashLine = i < dashboardLines.length ? dashboardLines[i] : ""; - // Get the dependency line (or empty string if we've run out of lines) - const depLine = i < dependencyLines.length ? dependencyLines[i] : ""; + // For each line of output, pad the dashboard line to exactly halfWidth chars + // This ensures the dependency box starts at exactly the right position + const combinedLines = []; + for (let i = 0; i < maxHeight; i++) { + // Get the dashboard line (or empty string if we've run out of lines) + const dashLine = i < dashboardLines.length ? dashboardLines[i] : ''; + // Get the dependency line (or empty string if we've run out of lines) + const depLine = i < dependencyLines.length ? dependencyLines[i] : ''; - // Remove any trailing spaces from dashLine before padding to exact width - const trimmedDashLine = dashLine.trimEnd(); - // Pad the dashboard line to exactly halfWidth chars with no extra spaces - const paddedDashLine = trimmedDashLine.padEnd(halfWidth, " "); + // Remove any trailing spaces from dashLine before padding to exact width + const trimmedDashLine = dashLine.trimEnd(); + // Pad the dashboard line to exactly halfWidth chars with no extra spaces + const paddedDashLine = trimmedDashLine.padEnd(halfWidth, ' '); - // Join the lines with no space in between - combinedLines.push(paddedDashLine + depLine); - } + // Join the lines with no space in between + combinedLines.push(paddedDashLine + depLine); + } - // Join all lines and output - console.log(combinedLines.join("\n")); - } else { - // Terminal too narrow, show boxes stacked vertically - const dashboardBox = boxen(projectDashboardContent, { - padding: 1, - borderColor: "blue", - borderStyle: "round", - margin: { top: 0, bottom: 1 }, - }); + // Join all lines and output + console.log(combinedLines.join('\n')); + } else { + // Terminal too narrow, show boxes stacked vertically + const dashboardBox = boxen(projectDashboardContent, { + padding: 1, + borderColor: 'blue', + borderStyle: 'round', + margin: { top: 0, bottom: 1 } + }); - const dependencyBox = boxen(dependencyDashboardContent, { - padding: 1, - borderColor: "magenta", - borderStyle: "round", - margin: { top: 0, bottom: 1 }, - }); + const dependencyBox = boxen(dependencyDashboardContent, { + padding: 1, + borderColor: 'magenta', + borderStyle: 'round', + margin: { top: 0, bottom: 1 } + }); - // Display stacked vertically - console.log(dashboardBox); - console.log(dependencyBox); - } + // Display stacked vertically + console.log(dashboardBox); + console.log(dependencyBox); + } - if (filteredTasks.length === 0) { - console.log( - boxen( - statusFilter - ? chalk.yellow(`No tasks with status '${statusFilter}' found`) - : chalk.yellow("No tasks found"), - { padding: 1, borderColor: "yellow", borderStyle: "round" } - ) - ); - return; - } + if (filteredTasks.length === 0) { + console.log( + boxen( + statusFilter + ? chalk.yellow(`No tasks with status '${statusFilter}' found`) + : chalk.yellow('No tasks found'), + { padding: 1, borderColor: 'yellow', borderStyle: 'round' } + ) + ); + return; + } - // COMPLETELY REVISED TABLE APPROACH - // Define percentage-based column widths and calculate actual widths - // Adjust percentages based on content type and user requirements + // COMPLETELY REVISED TABLE APPROACH + // Define percentage-based column widths and calculate actual widths + // Adjust percentages based on content type and user requirements - // Adjust ID width if showing subtasks (subtask IDs are longer: e.g., "1.2") - const idWidthPct = withSubtasks ? 10 : 7; + // Adjust ID width if showing subtasks (subtask IDs are longer: e.g., "1.2") + const idWidthPct = withSubtasks ? 10 : 7; - // Calculate max status length to accommodate "in-progress" - const statusWidthPct = 15; + // Calculate max status length to accommodate "in-progress" + const statusWidthPct = 15; - // Increase priority column width as requested - const priorityWidthPct = 12; + // Increase priority column width as requested + const priorityWidthPct = 12; - // Make dependencies column smaller as requested (-20%) - const depsWidthPct = 20; + // Make dependencies column smaller as requested (-20%) + const depsWidthPct = 20; - const complexityWidthPct = 10; + const complexityWidthPct = 10; - // Calculate title/description width as remaining space (+20% from dependencies reduction) - const titleWidthPct = - 100 - - idWidthPct - - statusWidthPct - - priorityWidthPct - - depsWidthPct - - complexityWidthPct; + // Calculate title/description width as remaining space (+20% from dependencies reduction) + const titleWidthPct = + 100 - + idWidthPct - + statusWidthPct - + priorityWidthPct - + depsWidthPct - + complexityWidthPct; - // Allow 10 characters for borders and padding - const availableWidth = terminalWidth - 10; + // Allow 10 characters for borders and padding + const availableWidth = terminalWidth - 10; - // Calculate actual column widths based on percentages - const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); - const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); - const priorityWidth = Math.floor(availableWidth * (priorityWidthPct / 100)); - const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); - const complexityWidth = Math.floor( - availableWidth * (complexityWidthPct / 100) - ); - const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); + // Calculate actual column widths based on percentages + const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); + const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); + const priorityWidth = Math.floor(availableWidth * (priorityWidthPct / 100)); + const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); + const complexityWidth = Math.floor( + availableWidth * (complexityWidthPct / 100) + ); + const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); - // Create a table with correct borders and spacing - const table = new Table({ - head: [ - chalk.cyan.bold("ID"), - chalk.cyan.bold("Title"), - chalk.cyan.bold("Status"), - chalk.cyan.bold("Priority"), - chalk.cyan.bold("Dependencies"), - chalk.cyan.bold("Complexity"), - ], - colWidths: [ - idWidth, - titleWidth, - statusWidth, - priorityWidth, - depsWidth, - complexityWidth, // Added complexity column width - ], - style: { - head: [], // No special styling for header - border: [], // No special styling for border - compact: false, // Use default spacing - }, - wordWrap: true, - wrapOnWordBoundary: true, - }); + // Create a table with correct borders and spacing + const table = new Table({ + head: [ + chalk.cyan.bold('ID'), + chalk.cyan.bold('Title'), + chalk.cyan.bold('Status'), + chalk.cyan.bold('Priority'), + chalk.cyan.bold('Dependencies'), + chalk.cyan.bold('Complexity') + ], + colWidths: [ + idWidth, + titleWidth, + statusWidth, + priorityWidth, + depsWidth, + complexityWidth // Added complexity column width + ], + style: { + head: [], // No special styling for header + border: [], // No special styling for border + compact: false // Use default spacing + }, + wordWrap: true, + wrapOnWordBoundary: true + }); - // Process tasks for the table - filteredTasks.forEach((task) => { - // Format dependencies with status indicators (colored) - let depText = "None"; - if (task.dependencies && task.dependencies.length > 0) { - // Use the proper formatDependenciesWithStatus function for colored status - depText = formatDependenciesWithStatus( - task.dependencies, - data.tasks, - true, - complexityReport - ); - } else { - depText = chalk.gray("None"); - } + // Process tasks for the table + filteredTasks.forEach((task) => { + // Format dependencies with status indicators (colored) + let depText = 'None'; + if (task.dependencies && task.dependencies.length > 0) { + // Use the proper formatDependenciesWithStatus function for colored status + depText = formatDependenciesWithStatus( + task.dependencies, + data.tasks, + true, + complexityReport + ); + } else { + depText = chalk.gray('None'); + } - // Clean up any ANSI codes or confusing characters - const cleanTitle = task.title.replace(/\n/g, " "); + // Clean up any ANSI codes or confusing characters + const cleanTitle = task.title.replace(/\n/g, ' '); - // Get priority color - const priorityColor = - { - high: chalk.red, - medium: chalk.yellow, - low: chalk.gray, - }[task.priority || "medium"] || chalk.white; + // Get priority color + const priorityColor = + { + high: chalk.red, + medium: chalk.yellow, + low: chalk.gray + }[task.priority || 'medium'] || chalk.white; - // Format status - const status = getStatusWithColor(task.status, true); + // Format status + const status = getStatusWithColor(task.status, true); - // Add the row without truncating dependencies - table.push([ - task.id.toString(), - truncate(cleanTitle, titleWidth - 3), - status, - priorityColor(truncate(task.priority || "medium", priorityWidth - 2)), - depText, - task.complexityScore - ? getComplexityWithColor(task.complexityScore) - : chalk.gray("N/A"), - ]); + // Add the row without truncating dependencies + table.push([ + task.id.toString(), + truncate(cleanTitle, titleWidth - 3), + status, + priorityColor(truncate(task.priority || 'medium', priorityWidth - 2)), + depText, + task.complexityScore + ? getComplexityWithColor(task.complexityScore) + : chalk.gray('N/A') + ]); - // Add subtasks if requested - if (withSubtasks && task.subtasks && task.subtasks.length > 0) { - task.subtasks.forEach((subtask) => { - // Format subtask dependencies with status indicators - let subtaskDepText = "None"; - if (subtask.dependencies && subtask.dependencies.length > 0) { - // Handle both subtask-to-subtask and subtask-to-task dependencies - const formattedDeps = subtask.dependencies - .map((depId) => { - // Check if it's a dependency on another subtask - if (typeof depId === "number" && depId < 100) { - const foundSubtask = task.subtasks.find( - (st) => st.id === depId - ); - if (foundSubtask) { - const isDone = - foundSubtask.status === "done" || - foundSubtask.status === "completed"; - const isInProgress = foundSubtask.status === "in-progress"; + // Add subtasks if requested + if (withSubtasks && task.subtasks && task.subtasks.length > 0) { + task.subtasks.forEach((subtask) => { + // Format subtask dependencies with status indicators + let subtaskDepText = 'None'; + if (subtask.dependencies && subtask.dependencies.length > 0) { + // Handle both subtask-to-subtask and subtask-to-task dependencies + const formattedDeps = subtask.dependencies + .map((depId) => { + // Check if it's a dependency on another subtask + if (typeof depId === 'number' && depId < 100) { + const foundSubtask = task.subtasks.find( + (st) => st.id === depId + ); + if (foundSubtask) { + const isDone = + foundSubtask.status === 'done' || + foundSubtask.status === 'completed'; + const isInProgress = foundSubtask.status === 'in-progress'; - // Use consistent color formatting instead of emojis - if (isDone) { - return chalk.green.bold(`${task.id}.${depId}`); - } else if (isInProgress) { - return chalk.hex("#FFA500").bold(`${task.id}.${depId}`); - } else { - return chalk.red.bold(`${task.id}.${depId}`); - } - } - } - // Default to regular task dependency - const depTask = data.tasks.find((t) => t.id === depId); - if (depTask) { - // Add complexity to depTask before checking status - addComplexityToTask(depTask, complexityReport); - const isDone = - depTask.status === "done" || depTask.status === "completed"; - const isInProgress = depTask.status === "in-progress"; - // Use the same color scheme as in formatDependenciesWithStatus - if (isDone) { - return chalk.green.bold(`${depId}`); - } else if (isInProgress) { - return chalk.hex("#FFA500").bold(`${depId}`); - } else { - return chalk.red.bold(`${depId}`); - } - } - return chalk.cyan(depId.toString()); - }) - .join(", "); + // Use consistent color formatting instead of emojis + if (isDone) { + return chalk.green.bold(`${task.id}.${depId}`); + } else if (isInProgress) { + return chalk.hex('#FFA500').bold(`${task.id}.${depId}`); + } else { + return chalk.red.bold(`${task.id}.${depId}`); + } + } + } + // Default to regular task dependency + const depTask = data.tasks.find((t) => t.id === depId); + if (depTask) { + // Add complexity to depTask before checking status + addComplexityToTask(depTask, complexityReport); + const isDone = + depTask.status === 'done' || depTask.status === 'completed'; + const isInProgress = depTask.status === 'in-progress'; + // Use the same color scheme as in formatDependenciesWithStatus + if (isDone) { + return chalk.green.bold(`${depId}`); + } else if (isInProgress) { + return chalk.hex('#FFA500').bold(`${depId}`); + } else { + return chalk.red.bold(`${depId}`); + } + } + return chalk.cyan(depId.toString()); + }) + .join(', '); - subtaskDepText = formattedDeps || chalk.gray("None"); - } + subtaskDepText = formattedDeps || chalk.gray('None'); + } - // Add the subtask row without truncating dependencies - table.push([ - `${task.id}.${subtask.id}`, - chalk.dim(`โ””โ”€ ${truncate(subtask.title, titleWidth - 5)}`), - getStatusWithColor(subtask.status, true), - chalk.dim("-"), - subtaskDepText, - subtask.complexityScore - ? chalk.gray(`${subtask.complexityScore}`) - : chalk.gray("N/A"), - ]); - }); - } - }); + // Add the subtask row without truncating dependencies + table.push([ + `${task.id}.${subtask.id}`, + chalk.dim(`โ””โ”€ ${truncate(subtask.title, titleWidth - 5)}`), + getStatusWithColor(subtask.status, true), + chalk.dim('-'), + subtaskDepText, + subtask.complexityScore + ? chalk.gray(`${subtask.complexityScore}`) + : chalk.gray('N/A') + ]); + }); + } + }); - // Ensure we output the table even if it had to wrap - try { - console.log(table.toString()); - } catch (err) { - log("error", `Error rendering table: ${err.message}`); + // Ensure we output the table even if it had to wrap + try { + console.log(table.toString()); + } catch (err) { + log('error', `Error rendering table: ${err.message}`); - // Fall back to simpler output - console.log( - chalk.yellow( - "\nFalling back to simple task list due to terminal width constraints:" - ) - ); - filteredTasks.forEach((task) => { - console.log( - `${chalk.cyan(task.id)}: ${chalk.white(task.title)} - ${getStatusWithColor(task.status)}` - ); - }); - } + // Fall back to simpler output + console.log( + chalk.yellow( + '\nFalling back to simple task list due to terminal width constraints:' + ) + ); + filteredTasks.forEach((task) => { + console.log( + `${chalk.cyan(task.id)}: ${chalk.white(task.title)} - ${getStatusWithColor(task.status)}` + ); + }); + } - // Show filter info if applied - if (statusFilter) { - console.log(chalk.yellow(`\nFiltered by status: ${statusFilter}`)); - console.log( - chalk.yellow(`Showing ${filteredTasks.length} of ${totalTasks} tasks`) - ); - } + // Show filter info if applied + if (statusFilter) { + console.log(chalk.yellow(`\nFiltered by status: ${statusFilter}`)); + console.log( + chalk.yellow(`Showing ${filteredTasks.length} of ${totalTasks} tasks`) + ); + } - // Define priority colors - const priorityColors = { - high: chalk.red.bold, - medium: chalk.yellow, - low: chalk.gray, - }; + // Define priority colors + const priorityColors = { + high: chalk.red.bold, + medium: chalk.yellow, + low: chalk.gray + }; - // Show next task box in a prominent color - if (nextItem) { - // Prepare subtasks section if they exist (Only tasks have .subtasks property) - let subtasksSection = ""; - // Check if the nextItem is a top-level task before looking for subtasks - const parentTaskForSubtasks = data.tasks.find( - (t) => String(t.id) === String(nextItem.id) - ); // Find the original task object - if ( - parentTaskForSubtasks && - parentTaskForSubtasks.subtasks && - parentTaskForSubtasks.subtasks.length > 0 - ) { - subtasksSection = `\n\n${chalk.white.bold("Subtasks:")}\n`; - subtasksSection += parentTaskForSubtasks.subtasks - .map((subtask) => { - // Add complexity to subtask before display - addComplexityToTask(subtask, complexityReport); - // Using a more simplified format for subtask status display - const status = subtask.status || "pending"; - const statusColors = { - done: chalk.green, - completed: chalk.green, - pending: chalk.yellow, - "in-progress": chalk.blue, - deferred: chalk.gray, - blocked: chalk.red, - cancelled: chalk.gray, - }; - const statusColor = - statusColors[status.toLowerCase()] || chalk.white; - // Ensure subtask ID is displayed correctly using parent ID from the original task object - return `${chalk.cyan(`${parentTaskForSubtasks.id}.${subtask.id}`)} [${statusColor(status)}] ${subtask.title}`; - }) - .join("\n"); - } + // Show next task box in a prominent color + if (nextItem) { + // Prepare subtasks section if they exist (Only tasks have .subtasks property) + let subtasksSection = ''; + // Check if the nextItem is a top-level task before looking for subtasks + const parentTaskForSubtasks = data.tasks.find( + (t) => String(t.id) === String(nextItem.id) + ); // Find the original task object + if ( + parentTaskForSubtasks && + parentTaskForSubtasks.subtasks && + parentTaskForSubtasks.subtasks.length > 0 + ) { + subtasksSection = `\n\n${chalk.white.bold('Subtasks:')}\n`; + subtasksSection += parentTaskForSubtasks.subtasks + .map((subtask) => { + // Add complexity to subtask before display + addComplexityToTask(subtask, complexityReport); + // Using a more simplified format for subtask status display + const status = subtask.status || 'pending'; + const statusColors = { + done: chalk.green, + completed: chalk.green, + pending: chalk.yellow, + 'in-progress': chalk.blue, + deferred: chalk.gray, + blocked: chalk.red, + cancelled: chalk.gray + }; + const statusColor = + statusColors[status.toLowerCase()] || chalk.white; + // Ensure subtask ID is displayed correctly using parent ID from the original task object + return `${chalk.cyan(`${parentTaskForSubtasks.id}.${subtask.id}`)} [${statusColor(status)}] ${subtask.title}`; + }) + .join('\n'); + } - console.log( - boxen( - chalk.hex("#FF8800").bold( - // Use nextItem.id and nextItem.title - `๐Ÿ”ฅ Next Task to Work On: #${nextItem.id} - ${nextItem.title}` - ) + - "\n\n" + - // Use nextItem.priority, nextItem.status, nextItem.dependencies - `${chalk.white("Priority:")} ${priorityColors[nextItem.priority || "medium"](nextItem.priority || "medium")} ${chalk.white("Status:")} ${getStatusWithColor(nextItem.status, true)}\n` + - `${chalk.white("Dependencies:")} ${nextItem.dependencies && nextItem.dependencies.length > 0 ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : chalk.gray("None")}\n\n` + - // Use nextTask.description (Note: findNextTask doesn't return description, need to fetch original task/subtask for this) - // *** Fetching original item for description and details *** - `${chalk.white("Description:")} ${getWorkItemDescription(nextItem, data.tasks)}` + - subtasksSection + // <-- Subtasks are handled above now - "\n\n" + - // Use nextItem.id - `${chalk.cyan("Start working:")} ${chalk.yellow(`task-master set-status --id=${nextItem.id} --status=in-progress`)}\n` + - // Use nextItem.id - `${chalk.cyan("View details:")} ${chalk.yellow(`task-master show ${nextItem.id}`)}`, - { - padding: { left: 2, right: 2, top: 1, bottom: 1 }, - borderColor: "#FF8800", - borderStyle: "round", - margin: { top: 1, bottom: 1 }, - title: "โšก RECOMMENDED NEXT TASK โšก", - titleAlignment: "center", - width: terminalWidth - 4, - fullscreen: false, - } - ) - ); - } else { - console.log( - boxen( - chalk.hex("#FF8800").bold("No eligible next task found") + - "\n\n" + - "All pending tasks have dependencies that are not yet completed, or all tasks are done.", - { - padding: 1, - borderColor: "#FF8800", - borderStyle: "round", - margin: { top: 1, bottom: 1 }, - title: "โšก NEXT TASK โšก", - titleAlignment: "center", - width: terminalWidth - 4, // Use full terminal width minus a small margin - } - ) - ); - } + console.log( + boxen( + chalk.hex('#FF8800').bold( + // Use nextItem.id and nextItem.title + `๐Ÿ”ฅ Next Task to Work On: #${nextItem.id} - ${nextItem.title}` + ) + + '\n\n' + + // Use nextItem.priority, nextItem.status, nextItem.dependencies + `${chalk.white('Priority:')} ${priorityColors[nextItem.priority || 'medium'](nextItem.priority || 'medium')} ${chalk.white('Status:')} ${getStatusWithColor(nextItem.status, true)}\n` + + `${chalk.white('Dependencies:')} ${nextItem.dependencies && nextItem.dependencies.length > 0 ? formatDependenciesWithStatus(nextItem.dependencies, data.tasks, true, complexityReport) : chalk.gray('None')}\n\n` + + // Use nextTask.description (Note: findNextTask doesn't return description, need to fetch original task/subtask for this) + // *** Fetching original item for description and details *** + `${chalk.white('Description:')} ${getWorkItemDescription(nextItem, data.tasks)}` + + subtasksSection + // <-- Subtasks are handled above now + '\n\n' + + // Use nextItem.id + `${chalk.cyan('Start working:')} ${chalk.yellow(`task-master set-status --id=${nextItem.id} --status=in-progress`)}\n` + + // Use nextItem.id + `${chalk.cyan('View details:')} ${chalk.yellow(`task-master show ${nextItem.id}`)}`, + { + padding: { left: 2, right: 2, top: 1, bottom: 1 }, + borderColor: '#FF8800', + borderStyle: 'round', + margin: { top: 1, bottom: 1 }, + title: 'โšก RECOMMENDED NEXT TASK โšก', + titleAlignment: 'center', + width: terminalWidth - 4, + fullscreen: false + } + ) + ); + } else { + console.log( + boxen( + chalk.hex('#FF8800').bold('No eligible next task found') + + '\n\n' + + 'All pending tasks have dependencies that are not yet completed, or all tasks are done.', + { + padding: 1, + borderColor: '#FF8800', + borderStyle: 'round', + margin: { top: 1, bottom: 1 }, + title: 'โšก NEXT TASK โšก', + titleAlignment: 'center', + width: terminalWidth - 4 // Use full terminal width minus a small margin + } + ) + ); + } - // Show next steps - console.log( - boxen( - chalk.white.bold("Suggested Next Steps:") + - "\n\n" + - `${chalk.cyan("1.")} Run ${chalk.yellow("task-master next")} to see what to work on next\n` + - `${chalk.cyan("2.")} Run ${chalk.yellow("task-master expand --id=<id>")} to break down a task into subtasks\n` + - `${chalk.cyan("3.")} Run ${chalk.yellow("task-master set-status --id=<id> --status=done")} to mark a task as complete`, - { - padding: 1, - borderColor: "gray", - borderStyle: "round", - margin: { top: 1 }, - } - ) - ); - } catch (error) { - log("error", `Error listing tasks: ${error.message}`); + // Show next steps + console.log( + boxen( + chalk.white.bold('Suggested Next Steps:') + + '\n\n' + + `${chalk.cyan('1.')} Run ${chalk.yellow('task-master next')} to see what to work on next\n` + + `${chalk.cyan('2.')} Run ${chalk.yellow('task-master expand --id=<id>')} to break down a task into subtasks\n` + + `${chalk.cyan('3.')} Run ${chalk.yellow('task-master set-status --id=<id> --status=done')} to mark a task as complete`, + { + padding: 1, + borderColor: 'gray', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); + } catch (error) { + log('error', `Error listing tasks: ${error.message}`); - if (outputFormat === "json") { - // Return structured error for JSON output - throw { - code: "TASK_LIST_ERROR", - message: error.message, - details: error.stack, - }; - } + if (outputFormat === 'json') { + // Return structured error for JSON output + throw { + code: 'TASK_LIST_ERROR', + message: error.message, + details: error.stack + }; + } - console.error(chalk.red(`Error: ${error.message}`)); - process.exit(1); - } + console.error(chalk.red(`Error: ${error.message}`)); + process.exit(1); + } } // *** Helper function to get description for task or subtask *** function getWorkItemDescription(item, allTasks) { - if (!item) return "N/A"; - if (item.parentId) { - // It's a subtask - const parent = allTasks.find((t) => t.id === item.parentId); - const subtask = parent?.subtasks?.find( - (st) => `${parent.id}.${st.id}` === item.id - ); - return subtask?.description || "No description available."; - } else { - // It's a top-level task - const task = allTasks.find((t) => String(t.id) === String(item.id)); - return task?.description || "No description available."; - } + if (!item) return 'N/A'; + if (item.parentId) { + // It's a subtask + const parent = allTasks.find((t) => t.id === item.parentId); + const subtask = parent?.subtasks?.find( + (st) => `${parent.id}.${st.id}` === item.id + ); + return subtask?.description || 'No description available.'; + } else { + // It's a top-level task + const task = allTasks.find((t) => String(t.id) === String(item.id)); + return task?.description || 'No description available.'; + } } export default listTasks; diff --git a/scripts/modules/task-manager/models.js b/scripts/modules/task-manager/models.js index 5fe02d8a..b5b18538 100644 --- a/scripts/modules/task-manager/models.js +++ b/scripts/modules/task-manager/models.js @@ -3,71 +3,71 @@ * Core functionality for managing AI model configurations */ -import https from "https"; -import http from "http"; +import https from 'https'; +import http from 'http'; import { - getMainModelId, - getResearchModelId, - getFallbackModelId, - getAvailableModels, - getMainProvider, - getResearchProvider, - getFallbackProvider, - isApiKeySet, - getMcpApiKeyStatus, - getConfig, - writeConfig, - isConfigFilePresent, - getAllProviders, - getBaseUrlForRole, -} from "../config-manager.js"; -import { findConfigPath } from "../../../src/utils/path-utils.js"; -import { log } from "../utils.js"; + getMainModelId, + getResearchModelId, + getFallbackModelId, + getAvailableModels, + getMainProvider, + getResearchProvider, + getFallbackProvider, + isApiKeySet, + getMcpApiKeyStatus, + getConfig, + writeConfig, + isConfigFilePresent, + getAllProviders, + getBaseUrlForRole +} from '../config-manager.js'; +import { findConfigPath } from '../../../src/utils/path-utils.js'; +import { log } from '../utils.js'; /** * Fetches the list of models from OpenRouter API. * @returns {Promise<Array|null>} A promise that resolves with the list of model IDs or null if fetch fails. */ function fetchOpenRouterModels() { - return new Promise((resolve) => { - const options = { - hostname: "openrouter.ai", - path: "/api/v1/models", - method: "GET", - headers: { - Accept: "application/json", - }, - }; + return new Promise((resolve) => { + const options = { + hostname: 'openrouter.ai', + path: '/api/v1/models', + method: 'GET', + headers: { + Accept: 'application/json' + } + }; - const req = https.request(options, (res) => { - let data = ""; - res.on("data", (chunk) => { - data += chunk; - }); - res.on("end", () => { - if (res.statusCode === 200) { - try { - const parsedData = JSON.parse(data); - resolve(parsedData.data || []); // Return the array of models - } catch (e) { - console.error("Error parsing OpenRouter response:", e); - resolve(null); // Indicate failure - } - } else { - console.error( - `OpenRouter API request failed with status code: ${res.statusCode}` - ); - resolve(null); // Indicate failure - } - }); - }); + const req = https.request(options, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + if (res.statusCode === 200) { + try { + const parsedData = JSON.parse(data); + resolve(parsedData.data || []); // Return the array of models + } catch (e) { + console.error('Error parsing OpenRouter response:', e); + resolve(null); // Indicate failure + } + } else { + console.error( + `OpenRouter API request failed with status code: ${res.statusCode}` + ); + resolve(null); // Indicate failure + } + }); + }); - req.on("error", (e) => { - console.error("Error fetching OpenRouter models:", e); - resolve(null); // Indicate failure - }); - req.end(); - }); + req.on('error', (e) => { + console.error('Error fetching OpenRouter models:', e); + resolve(null); // Indicate failure + }); + req.end(); + }); } /** @@ -75,61 +75,61 @@ function fetchOpenRouterModels() { * @param {string} baseURL - The base URL for the Ollama API (e.g., "http://localhost:11434/api") * @returns {Promise<Array|null>} A promise that resolves with the list of model objects or null if fetch fails. */ -function fetchOllamaModels(baseURL = "http://localhost:11434/api") { - return new Promise((resolve) => { - try { - // Parse the base URL to extract hostname, port, and base path - const url = new URL(baseURL); - const isHttps = url.protocol === "https:"; - const port = url.port || (isHttps ? 443 : 80); - const basePath = url.pathname.endsWith("/") - ? url.pathname.slice(0, -1) - : url.pathname; +function fetchOllamaModels(baseURL = 'http://localhost:11434/api') { + return new Promise((resolve) => { + try { + // Parse the base URL to extract hostname, port, and base path + const url = new URL(baseURL); + const isHttps = url.protocol === 'https:'; + const port = url.port || (isHttps ? 443 : 80); + const basePath = url.pathname.endsWith('/') + ? url.pathname.slice(0, -1) + : url.pathname; - const options = { - hostname: url.hostname, - port: parseInt(port, 10), - path: `${basePath}/tags`, - method: "GET", - headers: { - Accept: "application/json", - }, - }; + const options = { + hostname: url.hostname, + port: parseInt(port, 10), + path: `${basePath}/tags`, + method: 'GET', + headers: { + Accept: 'application/json' + } + }; - const requestLib = isHttps ? https : http; - const req = requestLib.request(options, (res) => { - let data = ""; - res.on("data", (chunk) => { - data += chunk; - }); - res.on("end", () => { - if (res.statusCode === 200) { - try { - const parsedData = JSON.parse(data); - resolve(parsedData.models || []); // Return the array of models - } catch (e) { - console.error("Error parsing Ollama response:", e); - resolve(null); // Indicate failure - } - } else { - console.error( - `Ollama API request failed with status code: ${res.statusCode}` - ); - resolve(null); // Indicate failure - } - }); - }); + const requestLib = isHttps ? https : http; + const req = requestLib.request(options, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + if (res.statusCode === 200) { + try { + const parsedData = JSON.parse(data); + resolve(parsedData.models || []); // Return the array of models + } catch (e) { + console.error('Error parsing Ollama response:', e); + resolve(null); // Indicate failure + } + } else { + console.error( + `Ollama API request failed with status code: ${res.statusCode}` + ); + resolve(null); // Indicate failure + } + }); + }); - req.on("error", (e) => { - console.error("Error fetching Ollama models:", e); - resolve(null); // Indicate failure - }); - req.end(); - } catch (e) { - console.error("Error parsing Ollama base URL:", e); - resolve(null); // Indicate failure - } - }); + req.on('error', (e) => { + console.error('Error fetching Ollama models:', e); + resolve(null); // Indicate failure + }); + req.end(); + } catch (e) { + console.error('Error parsing Ollama base URL:', e); + resolve(null); // Indicate failure + } + }); } /** @@ -141,125 +141,125 @@ function fetchOllamaModels(baseURL = "http://localhost:11434/api") { * @returns {Object} RESTful response with current model configuration */ async function getModelConfiguration(options = {}) { - const { mcpLog, projectRoot, session } = options; + const { mcpLog, projectRoot, session } = options; - const report = (level, ...args) => { - if (mcpLog && typeof mcpLog[level] === "function") { - mcpLog[level](...args); - } - }; + const report = (level, ...args) => { + if (mcpLog && typeof mcpLog[level] === 'function') { + mcpLog[level](...args); + } + }; - if (!projectRoot) { - throw new Error("Project root is required but not found."); - } + if (!projectRoot) { + throw new Error('Project root is required but not found.'); + } - // Use centralized config path finding instead of hardcoded path - const configPath = findConfigPath(null, { projectRoot }); - const configExists = isConfigFilePresent(projectRoot); + // Use centralized config path finding instead of hardcoded path + const configPath = findConfigPath(null, { projectRoot }); + const configExists = isConfigFilePresent(projectRoot); - log( - "debug", - `Checking for config file using findConfigPath, found: ${configPath}` - ); - log( - "debug", - `Checking config file using isConfigFilePresent(), exists: ${configExists}` - ); + log( + 'debug', + `Checking for config file using findConfigPath, found: ${configPath}` + ); + log( + 'debug', + `Checking config file using isConfigFilePresent(), exists: ${configExists}` + ); - if (!configExists) { - throw new Error( - 'The configuration file is missing. Run "task-master models --setup" to create it.' - ); - } + if (!configExists) { + throw new Error( + 'The configuration file is missing. Run "task-master models --setup" to create it.' + ); + } - try { - // Get current settings - these should use the config from the found path automatically - const mainProvider = getMainProvider(projectRoot); - const mainModelId = getMainModelId(projectRoot); - const researchProvider = getResearchProvider(projectRoot); - const researchModelId = getResearchModelId(projectRoot); - const fallbackProvider = getFallbackProvider(projectRoot); - const fallbackModelId = getFallbackModelId(projectRoot); + try { + // Get current settings - these should use the config from the found path automatically + const mainProvider = getMainProvider(projectRoot); + const mainModelId = getMainModelId(projectRoot); + const researchProvider = getResearchProvider(projectRoot); + const researchModelId = getResearchModelId(projectRoot); + const fallbackProvider = getFallbackProvider(projectRoot); + const fallbackModelId = getFallbackModelId(projectRoot); - // Check API keys - const mainCliKeyOk = isApiKeySet(mainProvider, session, projectRoot); - const mainMcpKeyOk = getMcpApiKeyStatus(mainProvider, projectRoot); - const researchCliKeyOk = isApiKeySet( - researchProvider, - session, - projectRoot - ); - const researchMcpKeyOk = getMcpApiKeyStatus(researchProvider, projectRoot); - const fallbackCliKeyOk = fallbackProvider - ? isApiKeySet(fallbackProvider, session, projectRoot) - : true; - const fallbackMcpKeyOk = fallbackProvider - ? getMcpApiKeyStatus(fallbackProvider, projectRoot) - : true; + // Check API keys + const mainCliKeyOk = isApiKeySet(mainProvider, session, projectRoot); + const mainMcpKeyOk = getMcpApiKeyStatus(mainProvider, projectRoot); + const researchCliKeyOk = isApiKeySet( + researchProvider, + session, + projectRoot + ); + const researchMcpKeyOk = getMcpApiKeyStatus(researchProvider, projectRoot); + const fallbackCliKeyOk = fallbackProvider + ? isApiKeySet(fallbackProvider, session, projectRoot) + : true; + const fallbackMcpKeyOk = fallbackProvider + ? getMcpApiKeyStatus(fallbackProvider, projectRoot) + : true; - // Get available models to find detailed info - const availableModels = getAvailableModels(projectRoot); + // Get available models to find detailed info + const availableModels = getAvailableModels(projectRoot); - // Find model details - const mainModelData = availableModels.find((m) => m.id === mainModelId); - const researchModelData = availableModels.find( - (m) => m.id === researchModelId - ); - const fallbackModelData = fallbackModelId - ? availableModels.find((m) => m.id === fallbackModelId) - : null; + // Find model details + const mainModelData = availableModels.find((m) => m.id === mainModelId); + const researchModelData = availableModels.find( + (m) => m.id === researchModelId + ); + const fallbackModelData = fallbackModelId + ? availableModels.find((m) => m.id === fallbackModelId) + : null; - // Return structured configuration data - return { - success: true, - data: { - activeModels: { - main: { - provider: mainProvider, - modelId: mainModelId, - sweScore: mainModelData?.swe_score || null, - cost: mainModelData?.cost_per_1m_tokens || null, - keyStatus: { - cli: mainCliKeyOk, - mcp: mainMcpKeyOk, - }, - }, - research: { - provider: researchProvider, - modelId: researchModelId, - sweScore: researchModelData?.swe_score || null, - cost: researchModelData?.cost_per_1m_tokens || null, - keyStatus: { - cli: researchCliKeyOk, - mcp: researchMcpKeyOk, - }, - }, - fallback: fallbackProvider - ? { - provider: fallbackProvider, - modelId: fallbackModelId, - sweScore: fallbackModelData?.swe_score || null, - cost: fallbackModelData?.cost_per_1m_tokens || null, - keyStatus: { - cli: fallbackCliKeyOk, - mcp: fallbackMcpKeyOk, - }, - } - : null, - }, - message: "Successfully retrieved current model configuration", - }, - }; - } catch (error) { - report("error", `Error getting model configuration: ${error.message}`); - return { - success: false, - error: { - code: "CONFIG_ERROR", - message: error.message, - }, - }; - } + // Return structured configuration data + return { + success: true, + data: { + activeModels: { + main: { + provider: mainProvider, + modelId: mainModelId, + sweScore: mainModelData?.swe_score || null, + cost: mainModelData?.cost_per_1m_tokens || null, + keyStatus: { + cli: mainCliKeyOk, + mcp: mainMcpKeyOk + } + }, + research: { + provider: researchProvider, + modelId: researchModelId, + sweScore: researchModelData?.swe_score || null, + cost: researchModelData?.cost_per_1m_tokens || null, + keyStatus: { + cli: researchCliKeyOk, + mcp: researchMcpKeyOk + } + }, + fallback: fallbackProvider + ? { + provider: fallbackProvider, + modelId: fallbackModelId, + sweScore: fallbackModelData?.swe_score || null, + cost: fallbackModelData?.cost_per_1m_tokens || null, + keyStatus: { + cli: fallbackCliKeyOk, + mcp: fallbackMcpKeyOk + } + } + : null + }, + message: 'Successfully retrieved current model configuration' + } + }; + } catch (error) { + report('error', `Error getting model configuration: ${error.message}`); + return { + success: false, + error: { + code: 'CONFIG_ERROR', + message: error.message + } + }; + } } /** @@ -271,85 +271,85 @@ async function getModelConfiguration(options = {}) { * @returns {Object} RESTful response with available models */ async function getAvailableModelsList(options = {}) { - const { mcpLog, projectRoot } = options; + const { mcpLog, projectRoot } = options; - const report = (level, ...args) => { - if (mcpLog && typeof mcpLog[level] === "function") { - mcpLog[level](...args); - } - }; + const report = (level, ...args) => { + if (mcpLog && typeof mcpLog[level] === 'function') { + mcpLog[level](...args); + } + }; - if (!projectRoot) { - throw new Error("Project root is required but not found."); - } + if (!projectRoot) { + throw new Error('Project root is required but not found.'); + } - // Use centralized config path finding instead of hardcoded path - const configPath = findConfigPath(null, { projectRoot }); - const configExists = isConfigFilePresent(projectRoot); + // Use centralized config path finding instead of hardcoded path + const configPath = findConfigPath(null, { projectRoot }); + const configExists = isConfigFilePresent(projectRoot); - log( - "debug", - `Checking for config file using findConfigPath, found: ${configPath}` - ); - log( - "debug", - `Checking config file using isConfigFilePresent(), exists: ${configExists}` - ); + log( + 'debug', + `Checking for config file using findConfigPath, found: ${configPath}` + ); + log( + 'debug', + `Checking config file using isConfigFilePresent(), exists: ${configExists}` + ); - if (!configExists) { - throw new Error( - 'The configuration file is missing. Run "task-master models --setup" to create it.' - ); - } + if (!configExists) { + throw new Error( + 'The configuration file is missing. Run "task-master models --setup" to create it.' + ); + } - try { - // Get all available models - const allAvailableModels = getAvailableModels(projectRoot); + try { + // Get all available models + const allAvailableModels = getAvailableModels(projectRoot); - if (!allAvailableModels || allAvailableModels.length === 0) { - return { - success: true, - data: { - models: [], - message: "No available models found", - }, - }; - } + if (!allAvailableModels || allAvailableModels.length === 0) { + return { + success: true, + data: { + models: [], + message: 'No available models found' + } + }; + } - // Get currently used model IDs - const mainModelId = getMainModelId(projectRoot); - const researchModelId = getResearchModelId(projectRoot); - const fallbackModelId = getFallbackModelId(projectRoot); + // Get currently used model IDs + const mainModelId = getMainModelId(projectRoot); + const researchModelId = getResearchModelId(projectRoot); + const fallbackModelId = getFallbackModelId(projectRoot); - // Filter out placeholder models and active models - const activeIds = [mainModelId, researchModelId, fallbackModelId].filter( - Boolean - ); - const otherAvailableModels = allAvailableModels.map((model) => ({ - provider: model.provider || "N/A", - modelId: model.id, - sweScore: model.swe_score || null, - cost: model.cost_per_1m_tokens || null, - allowedRoles: model.allowed_roles || [], - })); + // Filter out placeholder models and active models + const activeIds = [mainModelId, researchModelId, fallbackModelId].filter( + Boolean + ); + const otherAvailableModels = allAvailableModels.map((model) => ({ + provider: model.provider || 'N/A', + modelId: model.id, + sweScore: model.swe_score || null, + cost: model.cost_per_1m_tokens || null, + allowedRoles: model.allowed_roles || [] + })); - return { - success: true, - data: { - models: otherAvailableModels, - message: `Successfully retrieved ${otherAvailableModels.length} available models`, - }, - }; - } catch (error) { - report("error", `Error getting available models: ${error.message}`); - return { - success: false, - error: { - code: "MODELS_LIST_ERROR", - message: error.message, - }, - }; - } + return { + success: true, + data: { + models: otherAvailableModels, + message: `Successfully retrieved ${otherAvailableModels.length} available models` + } + }; + } catch (error) { + report('error', `Error getting available models: ${error.message}`); + return { + success: false, + error: { + code: 'MODELS_LIST_ERROR', + message: error.message + } + }; + } } /** @@ -364,218 +364,218 @@ async function getAvailableModelsList(options = {}) { * @returns {Object} RESTful response with result of update operation */ async function setModel(role, modelId, options = {}) { - const { mcpLog, projectRoot, providerHint } = options; + const { mcpLog, projectRoot, providerHint } = options; - const report = (level, ...args) => { - if (mcpLog && typeof mcpLog[level] === "function") { - mcpLog[level](...args); - } - }; + const report = (level, ...args) => { + if (mcpLog && typeof mcpLog[level] === 'function') { + mcpLog[level](...args); + } + }; - if (!projectRoot) { - throw new Error("Project root is required but not found."); - } + if (!projectRoot) { + throw new Error('Project root is required but not found.'); + } - // Use centralized config path finding instead of hardcoded path - const configPath = findConfigPath(null, { projectRoot }); - const configExists = isConfigFilePresent(projectRoot); + // Use centralized config path finding instead of hardcoded path + const configPath = findConfigPath(null, { projectRoot }); + const configExists = isConfigFilePresent(projectRoot); - log( - "debug", - `Checking for config file using findConfigPath, found: ${configPath}` - ); - log( - "debug", - `Checking config file using isConfigFilePresent(), exists: ${configExists}` - ); + log( + 'debug', + `Checking for config file using findConfigPath, found: ${configPath}` + ); + log( + 'debug', + `Checking config file using isConfigFilePresent(), exists: ${configExists}` + ); - if (!configExists) { - throw new Error( - 'The configuration file is missing. Run "task-master models --setup" to create it.' - ); - } + if (!configExists) { + throw new Error( + 'The configuration file is missing. Run "task-master models --setup" to create it.' + ); + } - // Validate role - if (!["main", "research", "fallback"].includes(role)) { - return { - success: false, - error: { - code: "INVALID_ROLE", - message: `Invalid role: ${role}. Must be one of: main, research, fallback.`, - }, - }; - } + // Validate role + if (!['main', 'research', 'fallback'].includes(role)) { + return { + success: false, + error: { + code: 'INVALID_ROLE', + message: `Invalid role: ${role}. Must be one of: main, research, fallback.` + } + }; + } - // Validate model ID - if (typeof modelId !== "string" || modelId.trim() === "") { - return { - success: false, - error: { - code: "INVALID_MODEL_ID", - message: `Invalid model ID: ${modelId}. Must be a non-empty string.`, - }, - }; - } + // Validate model ID + if (typeof modelId !== 'string' || modelId.trim() === '') { + return { + success: false, + error: { + code: 'INVALID_MODEL_ID', + message: `Invalid model ID: ${modelId}. Must be a non-empty string.` + } + }; + } - try { - const availableModels = getAvailableModels(projectRoot); - const currentConfig = getConfig(projectRoot); - let determinedProvider = null; // Initialize provider - let warningMessage = null; + try { + const availableModels = getAvailableModels(projectRoot); + const currentConfig = getConfig(projectRoot); + let determinedProvider = null; // Initialize provider + let warningMessage = null; - // Find the model data in internal list initially to see if it exists at all - const modelData = availableModels.find((m) => m.id === modelId); + // Find the model data in internal list initially to see if it exists at all + const modelData = availableModels.find((m) => m.id === modelId); - // --- Revised Logic: Prioritize providerHint --- // + // --- Revised Logic: Prioritize providerHint --- // - if (providerHint) { - // Hint provided (--ollama or --openrouter flag used) - if (modelData && modelData.provider === providerHint) { - // Found internally AND provider matches the hint - determinedProvider = providerHint; - report( - "info", - `Model ${modelId} found internally with matching provider hint ${determinedProvider}.` - ); - } else { - // Either not found internally, OR found but under a DIFFERENT provider than hinted. - // Proceed with custom logic based ONLY on the hint. - if (providerHint === "openrouter") { - // Check OpenRouter ONLY because hint was openrouter - report("info", `Checking OpenRouter for ${modelId} (as hinted)...`); - const openRouterModels = await fetchOpenRouterModels(); + if (providerHint) { + // Hint provided (--ollama or --openrouter flag used) + if (modelData && modelData.provider === providerHint) { + // Found internally AND provider matches the hint + determinedProvider = providerHint; + report( + 'info', + `Model ${modelId} found internally with matching provider hint ${determinedProvider}.` + ); + } else { + // Either not found internally, OR found but under a DIFFERENT provider than hinted. + // Proceed with custom logic based ONLY on the hint. + if (providerHint === 'openrouter') { + // Check OpenRouter ONLY because hint was openrouter + report('info', `Checking OpenRouter for ${modelId} (as hinted)...`); + const openRouterModels = await fetchOpenRouterModels(); - if ( - openRouterModels && - openRouterModels.some((m) => m.id === modelId) - ) { - determinedProvider = "openrouter"; + if ( + openRouterModels && + openRouterModels.some((m) => m.id === modelId) + ) { + determinedProvider = 'openrouter'; - // Check if this is a free model (ends with :free) - if (modelId.endsWith(":free")) { - warningMessage = `Warning: OpenRouter free model '${modelId}' selected. Free models have significant limitations including lower context windows, reduced rate limits, and may not support advanced features like tool_use. Consider using the paid version '${modelId.replace(":free", "")}' for full functionality.`; - } else { - warningMessage = `Warning: Custom OpenRouter model '${modelId}' set. This model is not officially validated by Taskmaster and may not function as expected.`; - } + // Check if this is a free model (ends with :free) + if (modelId.endsWith(':free')) { + warningMessage = `Warning: OpenRouter free model '${modelId}' selected. Free models have significant limitations including lower context windows, reduced rate limits, and may not support advanced features like tool_use. Consider using the paid version '${modelId.replace(':free', '')}' for full functionality.`; + } else { + warningMessage = `Warning: Custom OpenRouter model '${modelId}' set. This model is not officially validated by Taskmaster and may not function as expected.`; + } - report("warn", warningMessage); - } else { - // Hinted as OpenRouter but not found in live check - throw new Error( - `Model ID "${modelId}" not found in the live OpenRouter model list. Please verify the ID and ensure it's available on OpenRouter.` - ); - } - } else if (providerHint === "ollama") { - // Check Ollama ONLY because hint was ollama - report("info", `Checking Ollama for ${modelId} (as hinted)...`); + report('warn', warningMessage); + } else { + // Hinted as OpenRouter but not found in live check + throw new Error( + `Model ID "${modelId}" not found in the live OpenRouter model list. Please verify the ID and ensure it's available on OpenRouter.` + ); + } + } else if (providerHint === 'ollama') { + // Check Ollama ONLY because hint was ollama + report('info', `Checking Ollama for ${modelId} (as hinted)...`); - // Get the Ollama base URL from config - const ollamaBaseURL = getBaseUrlForRole(role, projectRoot); - const ollamaModels = await fetchOllamaModels(ollamaBaseURL); + // Get the Ollama base URL from config + const ollamaBaseURL = getBaseUrlForRole(role, projectRoot); + const ollamaModels = await fetchOllamaModels(ollamaBaseURL); - if (ollamaModels === null) { - // Connection failed - server probably not running - throw new Error( - `Unable to connect to Ollama server at ${ollamaBaseURL}. Please ensure Ollama is running and try again.` - ); - } else if (ollamaModels.some((m) => m.model === modelId)) { - determinedProvider = "ollama"; - warningMessage = `Warning: Custom Ollama model '${modelId}' set. Ensure your Ollama server is running and has pulled this model. Taskmaster cannot guarantee compatibility.`; - report("warn", warningMessage); - } else { - // Server is running but model not found - const tagsUrl = `${ollamaBaseURL}/tags`; - throw new Error( - `Model ID "${modelId}" not found in the Ollama instance. Please verify the model is pulled and available. You can check available models with: curl ${tagsUrl}` - ); - } - } else if (providerHint === "bedrock") { - // Set provider without model validation since Bedrock models are managed by AWS - determinedProvider = "bedrock"; - warningMessage = `Warning: Custom Bedrock model '${modelId}' set. Please ensure the model ID is valid and accessible in your AWS account.`; - report("warn", warningMessage); - } else { - // Invalid provider hint - should not happen - throw new Error(`Invalid provider hint received: ${providerHint}`); - } - } - } else { - // No hint provided (flags not used) - if (modelData) { - // Found internally, use the provider from the internal list - determinedProvider = modelData.provider; - report( - "info", - `Model ${modelId} found internally with provider ${determinedProvider}.` - ); - } else { - // Model not found and no provider hint was given - return { - success: false, - error: { - code: "MODEL_NOT_FOUND_NO_HINT", - message: `Model ID "${modelId}" not found in Taskmaster's supported models. If this is a custom model, please specify the provider using --openrouter or --ollama.`, - }, - }; - } - } + if (ollamaModels === null) { + // Connection failed - server probably not running + throw new Error( + `Unable to connect to Ollama server at ${ollamaBaseURL}. Please ensure Ollama is running and try again.` + ); + } else if (ollamaModels.some((m) => m.model === modelId)) { + determinedProvider = 'ollama'; + warningMessage = `Warning: Custom Ollama model '${modelId}' set. Ensure your Ollama server is running and has pulled this model. Taskmaster cannot guarantee compatibility.`; + report('warn', warningMessage); + } else { + // Server is running but model not found + const tagsUrl = `${ollamaBaseURL}/tags`; + throw new Error( + `Model ID "${modelId}" not found in the Ollama instance. Please verify the model is pulled and available. You can check available models with: curl ${tagsUrl}` + ); + } + } else if (providerHint === 'bedrock') { + // Set provider without model validation since Bedrock models are managed by AWS + determinedProvider = 'bedrock'; + warningMessage = `Warning: Custom Bedrock model '${modelId}' set. Please ensure the model ID is valid and accessible in your AWS account.`; + report('warn', warningMessage); + } else { + // Invalid provider hint - should not happen + throw new Error(`Invalid provider hint received: ${providerHint}`); + } + } + } else { + // No hint provided (flags not used) + if (modelData) { + // Found internally, use the provider from the internal list + determinedProvider = modelData.provider; + report( + 'info', + `Model ${modelId} found internally with provider ${determinedProvider}.` + ); + } else { + // Model not found and no provider hint was given + return { + success: false, + error: { + code: 'MODEL_NOT_FOUND_NO_HINT', + message: `Model ID "${modelId}" not found in Taskmaster's supported models. If this is a custom model, please specify the provider using --openrouter or --ollama.` + } + }; + } + } - // --- End of Revised Logic --- // + // --- End of Revised Logic --- // - // At this point, we should have a determinedProvider if the model is valid (internally or custom) - if (!determinedProvider) { - // This case acts as a safeguard - return { - success: false, - error: { - code: "PROVIDER_UNDETERMINED", - message: `Could not determine the provider for model ID "${modelId}".`, - }, - }; - } + // At this point, we should have a determinedProvider if the model is valid (internally or custom) + if (!determinedProvider) { + // This case acts as a safeguard + return { + success: false, + error: { + code: 'PROVIDER_UNDETERMINED', + message: `Could not determine the provider for model ID "${modelId}".` + } + }; + } - // Update configuration - currentConfig.models[role] = { - ...currentConfig.models[role], // Keep existing params like maxTokens - provider: determinedProvider, - modelId: modelId, - }; + // Update configuration + currentConfig.models[role] = { + ...currentConfig.models[role], // Keep existing params like maxTokens + provider: determinedProvider, + modelId: modelId + }; - // Write updated configuration - const writeResult = writeConfig(currentConfig, projectRoot); - if (!writeResult) { - return { - success: false, - error: { - code: "CONFIG_WRITE_ERROR", - message: "Error writing updated configuration to configuration file", - }, - }; - } + // Write updated configuration + const writeResult = writeConfig(currentConfig, projectRoot); + if (!writeResult) { + return { + success: false, + error: { + code: 'CONFIG_WRITE_ERROR', + message: 'Error writing updated configuration to configuration file' + } + }; + } - const successMessage = `Successfully set ${role} model to ${modelId} (Provider: ${determinedProvider})`; - report("info", successMessage); + const successMessage = `Successfully set ${role} model to ${modelId} (Provider: ${determinedProvider})`; + report('info', successMessage); - return { - success: true, - data: { - role, - provider: determinedProvider, - modelId, - message: successMessage, - warning: warningMessage, // Include warning in the response data - }, - }; - } catch (error) { - report("error", `Error setting ${role} model: ${error.message}`); - return { - success: false, - error: { - code: "SET_MODEL_ERROR", - message: error.message, - }, - }; - } + return { + success: true, + data: { + role, + provider: determinedProvider, + modelId, + message: successMessage, + warning: warningMessage // Include warning in the response data + } + }; + } catch (error) { + report('error', `Error setting ${role} model: ${error.message}`); + return { + success: false, + error: { + code: 'SET_MODEL_ERROR', + message: error.message + } + }; + } } /** @@ -587,52 +587,52 @@ async function setModel(role, modelId, options = {}) { * @returns {Object} RESTful response with API key status report */ async function getApiKeyStatusReport(options = {}) { - const { mcpLog, projectRoot, session } = options; - const report = (level, ...args) => { - if (mcpLog && typeof mcpLog[level] === "function") { - mcpLog[level](...args); - } - }; + const { mcpLog, projectRoot, session } = options; + const report = (level, ...args) => { + if (mcpLog && typeof mcpLog[level] === 'function') { + mcpLog[level](...args); + } + }; - try { - const providers = getAllProviders(); - const providersToCheck = providers.filter( - (p) => p.toLowerCase() !== "ollama" - ); // Ollama is not a provider, it's a service, doesn't need an api key usually - const statusReport = providersToCheck.map((provider) => { - // Use provided projectRoot for MCP status check - const cliOk = isApiKeySet(provider, session, projectRoot); // Pass session and projectRoot for CLI check - const mcpOk = getMcpApiKeyStatus(provider, projectRoot); - return { - provider, - cli: cliOk, - mcp: mcpOk, - }; - }); + try { + const providers = getAllProviders(); + const providersToCheck = providers.filter( + (p) => p.toLowerCase() !== 'ollama' + ); // Ollama is not a provider, it's a service, doesn't need an api key usually + const statusReport = providersToCheck.map((provider) => { + // Use provided projectRoot for MCP status check + const cliOk = isApiKeySet(provider, session, projectRoot); // Pass session and projectRoot for CLI check + const mcpOk = getMcpApiKeyStatus(provider, projectRoot); + return { + provider, + cli: cliOk, + mcp: mcpOk + }; + }); - report("info", "Successfully generated API key status report."); - return { - success: true, - data: { - report: statusReport, - message: "API key status report generated.", - }, - }; - } catch (error) { - report("error", `Error generating API key status report: ${error.message}`); - return { - success: false, - error: { - code: "API_KEY_STATUS_ERROR", - message: error.message, - }, - }; - } + report('info', 'Successfully generated API key status report.'); + return { + success: true, + data: { + report: statusReport, + message: 'API key status report generated.' + } + }; + } catch (error) { + report('error', `Error generating API key status report: ${error.message}`); + return { + success: false, + error: { + code: 'API_KEY_STATUS_ERROR', + message: error.message + } + }; + } } export { - getModelConfiguration, - getAvailableModelsList, - setModel, - getApiKeyStatusReport, + getModelConfiguration, + getAvailableModelsList, + setModel, + getApiKeyStatusReport }; diff --git a/scripts/modules/task-manager/set-task-status.js b/scripts/modules/task-manager/set-task-status.js index 7e66babd..1687eb30 100644 --- a/scripts/modules/task-manager/set-task-status.js +++ b/scripts/modules/task-manager/set-task-status.js @@ -1,17 +1,17 @@ -import path from "path"; -import chalk from "chalk"; -import boxen from "boxen"; +import path from 'path'; +import chalk from 'chalk'; +import boxen from 'boxen'; -import { log, readJSON, writeJSON, findTaskById } from "../utils.js"; -import { displayBanner } from "../ui.js"; -import { validateTaskDependencies } from "../dependency-manager.js"; -import { getDebugFlag } from "../config-manager.js"; -import updateSingleTaskStatus from "./update-single-task-status.js"; -import generateTaskFiles from "./generate-task-files.js"; +import { log, readJSON, writeJSON, findTaskById } from '../utils.js'; +import { displayBanner } from '../ui.js'; +import { validateTaskDependencies } from '../dependency-manager.js'; +import { getDebugFlag } from '../config-manager.js'; +import updateSingleTaskStatus from './update-single-task-status.js'; +import generateTaskFiles from './generate-task-files.js'; import { - isValidTaskStatus, - TASK_STATUS_OPTIONS, -} from "../../../src/constants/task-status.js"; + isValidTaskStatus, + TASK_STATUS_OPTIONS +} from '../../../src/constants/task-status.js'; /** * Set the status of a task @@ -22,100 +22,100 @@ import { * @returns {Object|undefined} Result object in MCP mode, undefined in CLI mode */ async function setTaskStatus(tasksPath, taskIdInput, newStatus, options = {}) { - try { - if (!isValidTaskStatus(newStatus)) { - throw new Error( - `Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(", ")}` - ); - } - // Determine if we're in MCP mode by checking for mcpLog - const isMcpMode = !!options?.mcpLog; + try { + if (!isValidTaskStatus(newStatus)) { + throw new Error( + `Error: Invalid status value: ${newStatus}. Use one of: ${TASK_STATUS_OPTIONS.join(', ')}` + ); + } + // Determine if we're in MCP mode by checking for mcpLog + const isMcpMode = !!options?.mcpLog; - // Only display UI elements if not in MCP mode - if (!isMcpMode) { - console.log( - boxen(chalk.white.bold(`Updating Task Status to: ${newStatus}`), { - padding: 1, - borderColor: "blue", - borderStyle: "round", - }) - ); - } + // Only display UI elements if not in MCP mode + if (!isMcpMode) { + console.log( + boxen(chalk.white.bold(`Updating Task Status to: ${newStatus}`), { + padding: 1, + borderColor: 'blue', + borderStyle: 'round' + }) + ); + } - log("info", `Reading tasks from ${tasksPath}...`); - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - throw new Error(`No valid tasks found in ${tasksPath}`); - } + log('info', `Reading tasks from ${tasksPath}...`); + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + throw new Error(`No valid tasks found in ${tasksPath}`); + } - // Handle multiple task IDs (comma-separated) - const taskIds = taskIdInput.split(",").map((id) => id.trim()); - const updatedTasks = []; + // Handle multiple task IDs (comma-separated) + const taskIds = taskIdInput.split(',').map((id) => id.trim()); + const updatedTasks = []; - // Update each task - for (const id of taskIds) { - await updateSingleTaskStatus(tasksPath, id, newStatus, data, !isMcpMode); - updatedTasks.push(id); - } + // Update each task + for (const id of taskIds) { + await updateSingleTaskStatus(tasksPath, id, newStatus, data, !isMcpMode); + updatedTasks.push(id); + } - // Write the updated tasks to the file - writeJSON(tasksPath, data); + // Write the updated tasks to the file + writeJSON(tasksPath, data); - // Validate dependencies after status update - log("info", "Validating dependencies after status update..."); - validateTaskDependencies(data.tasks); + // Validate dependencies after status update + log('info', 'Validating dependencies after status update...'); + validateTaskDependencies(data.tasks); - // Generate individual task files - log("info", "Regenerating task files..."); - await generateTaskFiles(tasksPath, path.dirname(tasksPath), { - mcpLog: options.mcpLog, - }); + // Generate individual task files + log('info', 'Regenerating task files...'); + await generateTaskFiles(tasksPath, path.dirname(tasksPath), { + mcpLog: options.mcpLog + }); - // Display success message - only in CLI mode - if (!isMcpMode) { - for (const id of updatedTasks) { - const task = findTaskById(data.tasks, id); - const taskName = task ? task.title : id; + // Display success message - only in CLI mode + if (!isMcpMode) { + for (const id of updatedTasks) { + const task = findTaskById(data.tasks, id); + const taskName = task ? task.title : id; - console.log( - boxen( - chalk.white.bold(`Successfully updated task ${id} status:`) + - "\n" + - `From: ${chalk.yellow(task ? task.status : "unknown")}\n` + - `To: ${chalk.green(newStatus)}`, - { padding: 1, borderColor: "green", borderStyle: "round" } - ) - ); - } - } + console.log( + boxen( + chalk.white.bold(`Successfully updated task ${id} status:`) + + '\n' + + `From: ${chalk.yellow(task ? task.status : 'unknown')}\n` + + `To: ${chalk.green(newStatus)}`, + { padding: 1, borderColor: 'green', borderStyle: 'round' } + ) + ); + } + } - // Return success value for programmatic use - return { - success: true, - updatedTasks: updatedTasks.map((id) => ({ - id, - status: newStatus, - })), - }; - } catch (error) { - log("error", `Error setting task status: ${error.message}`); + // Return success value for programmatic use + return { + success: true, + updatedTasks: updatedTasks.map((id) => ({ + id, + status: newStatus + })) + }; + } catch (error) { + log('error', `Error setting task status: ${error.message}`); - // Only show error UI in CLI mode - if (!options?.mcpLog) { - console.error(chalk.red(`Error: ${error.message}`)); + // Only show error UI in CLI mode + if (!options?.mcpLog) { + console.error(chalk.red(`Error: ${error.message}`)); - // Pass session to getDebugFlag - if (getDebugFlag(options?.session)) { - // Use getter - console.error(error); - } + // Pass session to getDebugFlag + if (getDebugFlag(options?.session)) { + // Use getter + console.error(error); + } - process.exit(1); - } else { - // In MCP mode, throw the error for the caller to handle - throw error; - } - } + process.exit(1); + } else { + // In MCP mode, throw the error for the caller to handle + throw error; + } + } } export default setTaskStatus; diff --git a/scripts/modules/ui.js b/scripts/modules/ui.js index 9c1307ca..bcafbc5e 100644 --- a/scripts/modules/ui.js +++ b/scripts/modules/ui.js @@ -3,73 +3,73 @@ * User interface functions for the Task Master CLI */ -import chalk from "chalk"; -import figlet from "figlet"; -import boxen from "boxen"; -import ora from "ora"; -import Table from "cli-table3"; -import gradient from "gradient-string"; +import chalk from 'chalk'; +import figlet from 'figlet'; +import boxen from 'boxen'; +import ora from 'ora'; +import Table from 'cli-table3'; +import gradient from 'gradient-string'; import { - log, - findTaskById, - readJSON, - truncate, - isSilentMode, -} from "./utils.js"; -import fs from "fs"; + log, + findTaskById, + readJSON, + truncate, + isSilentMode +} from './utils.js'; +import fs from 'fs'; import { - findNextTask, - analyzeTaskComplexity, - readComplexityReport, -} from "./task-manager.js"; -import { getProjectName, getDefaultSubtasks } from "./config-manager.js"; -import { TASK_STATUS_OPTIONS } from "../../src/constants/task-status.js"; + findNextTask, + analyzeTaskComplexity, + readComplexityReport +} from './task-manager.js'; +import { getProjectName, getDefaultSubtasks } from './config-manager.js'; +import { TASK_STATUS_OPTIONS } from '../../src/constants/task-status.js'; import { - TASKMASTER_CONFIG_FILE, - TASKMASTER_TASKS_FILE, -} from "../../src/constants/paths.js"; -import { getTaskMasterVersion } from "../../src/utils/getVersion.js"; + TASKMASTER_CONFIG_FILE, + TASKMASTER_TASKS_FILE +} from '../../src/constants/paths.js'; +import { getTaskMasterVersion } from '../../src/utils/getVersion.js'; // Create a color gradient for the banner -const coolGradient = gradient(["#00b4d8", "#0077b6", "#03045e"]); -const warmGradient = gradient(["#fb8b24", "#e36414", "#9a031e"]); +const coolGradient = gradient(['#00b4d8', '#0077b6', '#03045e']); +const warmGradient = gradient(['#fb8b24', '#e36414', '#9a031e']); /** * Display a fancy banner for the CLI */ function displayBanner() { - if (isSilentMode()) return; + if (isSilentMode()) return; - // console.clear(); // Removing this to avoid clearing the terminal per command - const bannerText = figlet.textSync("Task Master", { - font: "Standard", - horizontalLayout: "default", - verticalLayout: "default", - }); + // console.clear(); // Removing this to avoid clearing the terminal per command + const bannerText = figlet.textSync('Task Master', { + font: 'Standard', + horizontalLayout: 'default', + verticalLayout: 'default' + }); - console.log(coolGradient(bannerText)); + console.log(coolGradient(bannerText)); - // Add creator credit line below the banner - console.log( - chalk.dim("by ") + chalk.cyan.underline("https://x.com/eyaltoledano") - ); + // Add creator credit line below the banner + console.log( + chalk.dim('by ') + chalk.cyan.underline('https://x.com/eyaltoledano') + ); - // Read version directly from package.json - const version = getTaskMasterVersion(); + // Read version directly from package.json + const version = getTaskMasterVersion(); - console.log( - boxen( - chalk.white( - `${chalk.bold("Version:")} ${version} ${chalk.bold("Project:")} ${getProjectName(null)}` - ), - { - padding: 1, - margin: { top: 0, bottom: 1 }, - borderStyle: "round", - borderColor: "cyan", - } - ) - ); + console.log( + boxen( + chalk.white( + `${chalk.bold('Version:')} ${version} ${chalk.bold('Project:')} ${getProjectName(null)}` + ), + { + padding: 1, + margin: { top: 0, bottom: 1 }, + borderStyle: 'round', + borderColor: 'cyan' + } + ) + ); } /** @@ -78,14 +78,14 @@ function displayBanner() { * @returns {Object} Spinner object */ function startLoadingIndicator(message) { - if (isSilentMode()) return null; + if (isSilentMode()) return null; - const spinner = ora({ - text: message, - color: "cyan", - }).start(); + const spinner = ora({ + text: message, + color: 'cyan' + }).start(); - return spinner; + return spinner; } /** @@ -93,9 +93,9 @@ function startLoadingIndicator(message) { * @param {Object} spinner - Spinner object to stop */ function stopLoadingIndicator(spinner) { - if (spinner && typeof spinner.stop === "function") { - spinner.stop(); - } + if (spinner && typeof spinner.stop === 'function') { + spinner.stop(); + } } /** @@ -104,13 +104,13 @@ function stopLoadingIndicator(spinner) { * @param {string} message - Optional success message (defaults to current text) */ function succeedLoadingIndicator(spinner, message = null) { - if (spinner && typeof spinner.succeed === "function") { - if (message) { - spinner.succeed(message); - } else { - spinner.succeed(); - } - } + if (spinner && typeof spinner.succeed === 'function') { + if (message) { + spinner.succeed(message); + } else { + spinner.succeed(); + } + } } /** @@ -119,13 +119,13 @@ function succeedLoadingIndicator(spinner, message = null) { * @param {string} message - Optional failure message (defaults to current text) */ function failLoadingIndicator(spinner, message = null) { - if (spinner && typeof spinner.fail === "function") { - if (message) { - spinner.fail(message); - } else { - spinner.fail(); - } - } + if (spinner && typeof spinner.fail === 'function') { + if (message) { + spinner.fail(message); + } else { + spinner.fail(); + } + } } /** @@ -134,13 +134,13 @@ function failLoadingIndicator(spinner, message = null) { * @param {string} message - Optional warning message (defaults to current text) */ function warnLoadingIndicator(spinner, message = null) { - if (spinner && typeof spinner.warn === "function") { - if (message) { - spinner.warn(message); - } else { - spinner.warn(); - } - } + if (spinner && typeof spinner.warn === 'function') { + if (message) { + spinner.warn(message); + } else { + spinner.warn(); + } + } } /** @@ -149,13 +149,13 @@ function warnLoadingIndicator(spinner, message = null) { * @param {string} message - Optional info message (defaults to current text) */ function infoLoadingIndicator(spinner, message = null) { - if (spinner && typeof spinner.info === "function") { - if (message) { - spinner.info(message); - } else { - spinner.info(); - } - } + if (spinner && typeof spinner.info === 'function') { + if (message) { + spinner.info(message); + } else { + spinner.info(); + } + } } /** @@ -166,120 +166,120 @@ function infoLoadingIndicator(spinner, message = null) { * @returns {string} The formatted progress bar */ function createProgressBar(percent, length = 30, statusBreakdown = null) { - // Adjust the percent to treat deferred and cancelled as complete - const effectivePercent = statusBreakdown - ? Math.min( - 100, - percent + - (statusBreakdown.deferred || 0) + - (statusBreakdown.cancelled || 0) - ) - : percent; + // Adjust the percent to treat deferred and cancelled as complete + const effectivePercent = statusBreakdown + ? Math.min( + 100, + percent + + (statusBreakdown.deferred || 0) + + (statusBreakdown.cancelled || 0) + ) + : percent; - // Calculate how many characters to fill for "true completion" - const trueCompletedFilled = Math.round((percent * length) / 100); + // Calculate how many characters to fill for "true completion" + const trueCompletedFilled = Math.round((percent * length) / 100); - // Calculate how many characters to fill for "effective completion" (including deferred/cancelled) - const effectiveCompletedFilled = Math.round( - (effectivePercent * length) / 100 - ); + // Calculate how many characters to fill for "effective completion" (including deferred/cancelled) + const effectiveCompletedFilled = Math.round( + (effectivePercent * length) / 100 + ); - // The "deferred/cancelled" section (difference between true and effective) - const deferredCancelledFilled = - effectiveCompletedFilled - trueCompletedFilled; + // The "deferred/cancelled" section (difference between true and effective) + const deferredCancelledFilled = + effectiveCompletedFilled - trueCompletedFilled; - // Set the empty section (remaining after effective completion) - const empty = length - effectiveCompletedFilled; + // Set the empty section (remaining after effective completion) + const empty = length - effectiveCompletedFilled; - // Determine color based on percentage for the completed section - let completedColor; - if (percent < 25) { - completedColor = chalk.red; - } else if (percent < 50) { - completedColor = chalk.hex("#FFA500"); // Orange - } else if (percent < 75) { - completedColor = chalk.yellow; - } else if (percent < 100) { - completedColor = chalk.green; - } else { - completedColor = chalk.hex("#006400"); // Dark green - } + // Determine color based on percentage for the completed section + let completedColor; + if (percent < 25) { + completedColor = chalk.red; + } else if (percent < 50) { + completedColor = chalk.hex('#FFA500'); // Orange + } else if (percent < 75) { + completedColor = chalk.yellow; + } else if (percent < 100) { + completedColor = chalk.green; + } else { + completedColor = chalk.hex('#006400'); // Dark green + } - // Create colored sections - const completedSection = completedColor("โ–ˆ".repeat(trueCompletedFilled)); + // Create colored sections + const completedSection = completedColor('โ–ˆ'.repeat(trueCompletedFilled)); - // Gray section for deferred/cancelled items - const deferredCancelledSection = chalk.gray( - "โ–ˆ".repeat(deferredCancelledFilled) - ); + // Gray section for deferred/cancelled items + const deferredCancelledSection = chalk.gray( + 'โ–ˆ'.repeat(deferredCancelledFilled) + ); - // If we have a status breakdown, create a multi-colored remaining section - let remainingSection = ""; + // If we have a status breakdown, create a multi-colored remaining section + let remainingSection = ''; - if (statusBreakdown && empty > 0) { - // Status colors (matching the statusConfig colors in getStatusWithColor) - const statusColors = { - pending: chalk.yellow, - "in-progress": chalk.hex("#FFA500"), // Orange - blocked: chalk.red, - review: chalk.magenta, - // Deferred and cancelled are treated as part of the completed section - }; + if (statusBreakdown && empty > 0) { + // Status colors (matching the statusConfig colors in getStatusWithColor) + const statusColors = { + pending: chalk.yellow, + 'in-progress': chalk.hex('#FFA500'), // Orange + blocked: chalk.red, + review: chalk.magenta + // Deferred and cancelled are treated as part of the completed section + }; - // Calculate proportions for each status - const totalRemaining = Object.entries(statusBreakdown) - .filter( - ([status]) => - !["deferred", "cancelled", "done", "completed"].includes(status) - ) - .reduce((sum, [_, val]) => sum + val, 0); + // Calculate proportions for each status + const totalRemaining = Object.entries(statusBreakdown) + .filter( + ([status]) => + !['deferred', 'cancelled', 'done', 'completed'].includes(status) + ) + .reduce((sum, [_, val]) => sum + val, 0); - // If no remaining tasks with tracked statuses, just use gray - if (totalRemaining <= 0) { - remainingSection = chalk.gray("โ–‘".repeat(empty)); - } else { - // Track how many characters we've added - let addedChars = 0; + // If no remaining tasks with tracked statuses, just use gray + if (totalRemaining <= 0) { + remainingSection = chalk.gray('โ–‘'.repeat(empty)); + } else { + // Track how many characters we've added + let addedChars = 0; - // Add each status section proportionally - for (const [status, percentage] of Object.entries(statusBreakdown)) { - // Skip statuses that are considered complete - if (["deferred", "cancelled", "done", "completed"].includes(status)) - continue; + // Add each status section proportionally + for (const [status, percentage] of Object.entries(statusBreakdown)) { + // Skip statuses that are considered complete + if (['deferred', 'cancelled', 'done', 'completed'].includes(status)) + continue; - // Calculate how many characters this status should fill - const statusChars = Math.round((percentage / totalRemaining) * empty); + // Calculate how many characters this status should fill + const statusChars = Math.round((percentage / totalRemaining) * empty); - // Make sure we don't exceed the total length due to rounding - const actualChars = Math.min(statusChars, empty - addedChars); + // Make sure we don't exceed the total length due to rounding + const actualChars = Math.min(statusChars, empty - addedChars); - // Add colored section for this status - const colorFn = statusColors[status] || chalk.gray; - remainingSection += colorFn("โ–‘".repeat(actualChars)); + // Add colored section for this status + const colorFn = statusColors[status] || chalk.gray; + remainingSection += colorFn('โ–‘'.repeat(actualChars)); - addedChars += actualChars; - } + addedChars += actualChars; + } - // If we have any remaining space due to rounding, fill with gray - if (addedChars < empty) { - remainingSection += chalk.gray("โ–‘".repeat(empty - addedChars)); - } - } - } else { - // Default to gray for the empty section if no breakdown provided - remainingSection = chalk.gray("โ–‘".repeat(empty)); - } + // If we have any remaining space due to rounding, fill with gray + if (addedChars < empty) { + remainingSection += chalk.gray('โ–‘'.repeat(empty - addedChars)); + } + } + } else { + // Default to gray for the empty section if no breakdown provided + remainingSection = chalk.gray('โ–‘'.repeat(empty)); + } - // Effective percentage text color should reflect the highest category - const percentTextColor = - percent === 100 - ? chalk.hex("#006400") // Dark green for 100% - : effectivePercent === 100 - ? chalk.gray // Gray for 100% with deferred/cancelled - : completedColor; // Otherwise match the completed color + // Effective percentage text color should reflect the highest category + const percentTextColor = + percent === 100 + ? chalk.hex('#006400') // Dark green for 100% + : effectivePercent === 100 + ? chalk.gray // Gray for 100% with deferred/cancelled + : completedColor; // Otherwise match the completed color - // Build the complete progress bar - return `${completedSection}${deferredCancelledSection}${remainingSection} ${percentTextColor(`${effectivePercent.toFixed(0)}%`)}`; + // Build the complete progress bar + return `${completedSection}${deferredCancelledSection}${remainingSection} ${percentTextColor(`${effectivePercent.toFixed(0)}%`)}`; } /** @@ -289,44 +289,44 @@ function createProgressBar(percent, length = 30, statusBreakdown = null) { * @returns {string} Colored status string */ function getStatusWithColor(status, forTable = false) { - if (!status) { - return chalk.gray("โ“ unknown"); - } + if (!status) { + return chalk.gray('โ“ unknown'); + } - const statusConfig = { - done: { color: chalk.green, icon: "โœ“", tableIcon: "โœ“" }, - completed: { color: chalk.green, icon: "โœ“", tableIcon: "โœ“" }, - pending: { color: chalk.yellow, icon: "โ—‹", tableIcon: "โฑ" }, - "in-progress": { color: chalk.hex("#FFA500"), icon: "๐Ÿ”„", tableIcon: "โ–บ" }, - deferred: { color: chalk.gray, icon: "x", tableIcon: "โฑ" }, - blocked: { color: chalk.red, icon: "!", tableIcon: "โœ—" }, - review: { color: chalk.magenta, icon: "?", tableIcon: "?" }, - cancelled: { color: chalk.gray, icon: "โŒ", tableIcon: "x" }, - }; + const statusConfig = { + done: { color: chalk.green, icon: 'โœ“', tableIcon: 'โœ“' }, + completed: { color: chalk.green, icon: 'โœ“', tableIcon: 'โœ“' }, + pending: { color: chalk.yellow, icon: 'โ—‹', tableIcon: 'โฑ' }, + 'in-progress': { color: chalk.hex('#FFA500'), icon: '๐Ÿ”„', tableIcon: 'โ–บ' }, + deferred: { color: chalk.gray, icon: 'x', tableIcon: 'โฑ' }, + blocked: { color: chalk.red, icon: '!', tableIcon: 'โœ—' }, + review: { color: chalk.magenta, icon: '?', tableIcon: '?' }, + cancelled: { color: chalk.gray, icon: 'โŒ', tableIcon: 'x' } + }; - const config = statusConfig[status.toLowerCase()] || { - color: chalk.red, - icon: "โŒ", - tableIcon: "โœ—", - }; + const config = statusConfig[status.toLowerCase()] || { + color: chalk.red, + icon: 'โŒ', + tableIcon: 'โœ—' + }; - // Use simpler icons for table display to prevent border issues - if (forTable) { - // Use ASCII characters instead of Unicode for completely stable display - const simpleIcons = { - done: "โœ“", - completed: "โœ“", - pending: "โ—‹", - "in-progress": "โ–บ", - deferred: "x", - blocked: "!", // Using plain x character for better compatibility - review: "?", // Using circled dot symbol - }; - const simpleIcon = simpleIcons[status.toLowerCase()] || "x"; - return config.color(`${simpleIcon} ${status}`); - } + // Use simpler icons for table display to prevent border issues + if (forTable) { + // Use ASCII characters instead of Unicode for completely stable display + const simpleIcons = { + done: 'โœ“', + completed: 'โœ“', + pending: 'โ—‹', + 'in-progress': 'โ–บ', + deferred: 'x', + blocked: '!', // Using plain x character for better compatibility + review: '?' // Using circled dot symbol + }; + const simpleIcon = simpleIcons[status.toLowerCase()] || 'x'; + return config.color(`${simpleIcon} ${status}`); + } - return config.color(`${config.icon} ${status}`); + return config.color(`${config.icon} ${status}`); } /** @@ -338,465 +338,465 @@ function getStatusWithColor(status, forTable = false) { * @returns {string} Formatted dependencies string */ function formatDependenciesWithStatus( - dependencies, - allTasks, - forConsole = false, - complexityReport = null // Add complexityReport parameter + dependencies, + allTasks, + forConsole = false, + complexityReport = null // Add complexityReport parameter ) { - if ( - !dependencies || - !Array.isArray(dependencies) || - dependencies.length === 0 - ) { - return forConsole ? chalk.gray("None") : "None"; - } + if ( + !dependencies || + !Array.isArray(dependencies) || + dependencies.length === 0 + ) { + return forConsole ? chalk.gray('None') : 'None'; + } - const formattedDeps = dependencies.map((depId) => { - const depIdStr = depId.toString(); // Ensure string format for display + const formattedDeps = dependencies.map((depId) => { + const depIdStr = depId.toString(); // Ensure string format for display - // Check if it's already a fully qualified subtask ID (like "22.1") - if (depIdStr.includes(".")) { - const [parentId, subtaskId] = depIdStr - .split(".") - .map((id) => parseInt(id, 10)); + // Check if it's already a fully qualified subtask ID (like "22.1") + if (depIdStr.includes('.')) { + const [parentId, subtaskId] = depIdStr + .split('.') + .map((id) => parseInt(id, 10)); - // Find the parent task - const parentTask = allTasks.find((t) => t.id === parentId); - if (!parentTask || !parentTask.subtasks) { - return forConsole - ? chalk.red(`${depIdStr} (Not found)`) - : `${depIdStr} (Not found)`; - } + // Find the parent task + const parentTask = allTasks.find((t) => t.id === parentId); + if (!parentTask || !parentTask.subtasks) { + return forConsole + ? chalk.red(`${depIdStr} (Not found)`) + : `${depIdStr} (Not found)`; + } - // Find the subtask - const subtask = parentTask.subtasks.find((st) => st.id === subtaskId); - if (!subtask) { - return forConsole - ? chalk.red(`${depIdStr} (Not found)`) - : `${depIdStr} (Not found)`; - } + // Find the subtask + const subtask = parentTask.subtasks.find((st) => st.id === subtaskId); + if (!subtask) { + return forConsole + ? chalk.red(`${depIdStr} (Not found)`) + : `${depIdStr} (Not found)`; + } - // Format with status - const status = subtask.status || "pending"; - const isDone = - status.toLowerCase() === "done" || status.toLowerCase() === "completed"; - const isInProgress = status.toLowerCase() === "in-progress"; + // Format with status + const status = subtask.status || 'pending'; + const isDone = + status.toLowerCase() === 'done' || status.toLowerCase() === 'completed'; + const isInProgress = status.toLowerCase() === 'in-progress'; - if (forConsole) { - if (isDone) { - return chalk.green.bold(depIdStr); - } else if (isInProgress) { - return chalk.hex("#FFA500").bold(depIdStr); - } else { - return chalk.red.bold(depIdStr); - } - } + if (forConsole) { + if (isDone) { + return chalk.green.bold(depIdStr); + } else if (isInProgress) { + return chalk.hex('#FFA500').bold(depIdStr); + } else { + return chalk.red.bold(depIdStr); + } + } - // For plain text output (task files), return just the ID without any formatting or emoji - return depIdStr; - } + // For plain text output (task files), return just the ID without any formatting or emoji + return depIdStr; + } - // If depId is a number less than 100, it's likely a reference to a subtask ID in the current task - // This case is typically handled elsewhere (in task-specific code) before calling this function + // If depId is a number less than 100, it's likely a reference to a subtask ID in the current task + // This case is typically handled elsewhere (in task-specific code) before calling this function - // For regular task dependencies (not subtasks) - // Convert string depId to number if needed - const numericDepId = - typeof depId === "string" ? parseInt(depId, 10) : depId; + // For regular task dependencies (not subtasks) + // Convert string depId to number if needed + const numericDepId = + typeof depId === 'string' ? parseInt(depId, 10) : depId; - // Look up the task using the numeric ID - const depTaskResult = findTaskById( - allTasks, - numericDepId, - complexityReport - ); - const depTask = depTaskResult.task; // Access the task object from the result + // Look up the task using the numeric ID + const depTaskResult = findTaskById( + allTasks, + numericDepId, + complexityReport + ); + const depTask = depTaskResult.task; // Access the task object from the result - if (!depTask) { - return forConsole - ? chalk.red(`${depIdStr} (Not found)`) - : `${depIdStr} (Not found)`; - } + if (!depTask) { + return forConsole + ? chalk.red(`${depIdStr} (Not found)`) + : `${depIdStr} (Not found)`; + } - // Format with status - const status = depTask.status || "pending"; - const isDone = - status.toLowerCase() === "done" || status.toLowerCase() === "completed"; - const isInProgress = status.toLowerCase() === "in-progress"; + // Format with status + const status = depTask.status || 'pending'; + const isDone = + status.toLowerCase() === 'done' || status.toLowerCase() === 'completed'; + const isInProgress = status.toLowerCase() === 'in-progress'; - if (forConsole) { - if (isDone) { - return chalk.green.bold(depIdStr); - } else if (isInProgress) { - return chalk.yellow.bold(depIdStr); - } else { - return chalk.red.bold(depIdStr); - } - } + if (forConsole) { + if (isDone) { + return chalk.green.bold(depIdStr); + } else if (isInProgress) { + return chalk.yellow.bold(depIdStr); + } else { + return chalk.red.bold(depIdStr); + } + } - // For plain text output (task files), return just the ID without any formatting or emoji - return depIdStr; - }); + // For plain text output (task files), return just the ID without any formatting or emoji + return depIdStr; + }); - return formattedDeps.join(", "); + return formattedDeps.join(', '); } /** * Display a comprehensive help guide */ function displayHelp() { - // Get terminal width - moved to top of function to make it available throughout - const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect + // Get terminal width - moved to top of function to make it available throughout + const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect - console.log( - boxen(chalk.white.bold("Task Master CLI"), { - padding: 1, - borderColor: "blue", - borderStyle: "round", - margin: { top: 1, bottom: 1 }, - }) - ); + console.log( + boxen(chalk.white.bold('Task Master CLI'), { + padding: 1, + borderColor: 'blue', + borderStyle: 'round', + margin: { top: 1, bottom: 1 } + }) + ); - // Command categories - const commandCategories = [ - { - title: "Project Setup & Configuration", - color: "blue", - commands: [ - { - name: "init", - args: "[--name=<name>] [--description=<desc>] [-y]", - desc: "Initialize a new project with Task Master structure", - }, - { - name: "models", - args: "", - desc: "View current AI model configuration and available models", - }, - { - name: "models --setup", - args: "", - desc: "Run interactive setup to configure AI models", - }, - { - name: "models --set-main", - args: "<model_id>", - desc: "Set the primary model for task generation", - }, - { - name: "models --set-research", - args: "<model_id>", - desc: "Set the model for research operations", - }, - { - name: "models --set-fallback", - args: "<model_id>", - desc: "Set the fallback model (optional)", - }, - ], - }, - { - title: "Task Generation", - color: "cyan", - commands: [ - { - name: "parse-prd", - args: "--input=<file.txt> [--num-tasks=10]", - desc: "Generate tasks from a PRD document", - }, - { - name: "generate", - args: "", - desc: "Create individual task files from tasks.json", - }, - ], - }, - { - title: "Task Management", - color: "green", - commands: [ - { - name: "list", - args: "[--status=<status>] [--with-subtasks]", - desc: "List all tasks with their status", - }, - { - name: "set-status", - args: "--id=<id> --status=<status>", - desc: `Update task status (${TASK_STATUS_OPTIONS.join(", ")})`, - }, - { - name: "update", - args: '--from=<id> --prompt="<context>"', - desc: "Update multiple tasks based on new requirements", - }, - { - name: "update-task", - args: '--id=<id> --prompt="<context>"', - desc: "Update a single specific task with new information", - }, - { - name: "update-subtask", - args: '--id=<parentId.subtaskId> --prompt="<context>"', - desc: "Append additional information to a subtask", - }, - { - name: "add-task", - args: '--prompt="<text>" [--dependencies=<ids>] [--priority=<priority>]', - desc: "Add a new task using AI", - }, - { - name: "remove-task", - args: "--id=<id> [-y]", - desc: "Permanently remove a task or subtask", - }, - ], - }, - { - title: "Subtask Management", - color: "yellow", - commands: [ - { - name: "add-subtask", - args: '--parent=<id> --title="<title>" [--description="<desc>"]', - desc: "Add a new subtask to a parent task", - }, - { - name: "add-subtask", - args: "--parent=<id> --task-id=<id>", - desc: "Convert an existing task into a subtask", - }, - { - name: "remove-subtask", - args: "--id=<parentId.subtaskId> [--convert]", - desc: "Remove a subtask (optionally convert to standalone task)", - }, - { - name: "clear-subtasks", - args: "--id=<id>", - desc: "Remove all subtasks from specified tasks", - }, - { - name: "clear-subtasks --all", - args: "", - desc: "Remove subtasks from all tasks", - }, - ], - }, - { - title: "Task Analysis & Breakdown", - color: "magenta", - commands: [ - { - name: "analyze-complexity", - args: "[--research] [--threshold=5]", - desc: "Analyze tasks and generate expansion recommendations", - }, - { - name: "complexity-report", - args: "[--file=<path>]", - desc: "Display the complexity analysis report", - }, - { - name: "expand", - args: '--id=<id> [--num=5] [--research] [--prompt="<context>"]', - desc: "Break down tasks into detailed subtasks", - }, - { - name: "expand --all", - args: "[--force] [--research]", - desc: "Expand all pending tasks with subtasks", - }, - ], - }, - { - title: "Task Navigation & Viewing", - color: "cyan", - commands: [ - { - name: "next", - args: "", - desc: "Show the next task to work on based on dependencies", - }, - { - name: "show", - args: "<id>", - desc: "Display detailed information about a specific task", - }, - ], - }, - { - title: "Dependency Management", - color: "blue", - commands: [ - { - name: "add-dependency", - args: "--id=<id> --depends-on=<id>", - desc: "Add a dependency to a task", - }, - { - name: "remove-dependency", - args: "--id=<id> --depends-on=<id>", - desc: "Remove a dependency from a task", - }, - { - name: "validate-dependencies", - args: "", - desc: "Identify invalid dependencies without fixing them", - }, - { - name: "fix-dependencies", - args: "", - desc: "Fix invalid dependencies automatically", - }, - ], - }, - ]; + // Command categories + const commandCategories = [ + { + title: 'Project Setup & Configuration', + color: 'blue', + commands: [ + { + name: 'init', + args: '[--name=<name>] [--description=<desc>] [-y]', + desc: 'Initialize a new project with Task Master structure' + }, + { + name: 'models', + args: '', + desc: 'View current AI model configuration and available models' + }, + { + name: 'models --setup', + args: '', + desc: 'Run interactive setup to configure AI models' + }, + { + name: 'models --set-main', + args: '<model_id>', + desc: 'Set the primary model for task generation' + }, + { + name: 'models --set-research', + args: '<model_id>', + desc: 'Set the model for research operations' + }, + { + name: 'models --set-fallback', + args: '<model_id>', + desc: 'Set the fallback model (optional)' + } + ] + }, + { + title: 'Task Generation', + color: 'cyan', + commands: [ + { + name: 'parse-prd', + args: '--input=<file.txt> [--num-tasks=10]', + desc: 'Generate tasks from a PRD document' + }, + { + name: 'generate', + args: '', + desc: 'Create individual task files from tasks.json' + } + ] + }, + { + title: 'Task Management', + color: 'green', + commands: [ + { + name: 'list', + args: '[--status=<status>] [--with-subtasks]', + desc: 'List all tasks with their status' + }, + { + name: 'set-status', + args: '--id=<id> --status=<status>', + desc: `Update task status (${TASK_STATUS_OPTIONS.join(', ')})` + }, + { + name: 'update', + args: '--from=<id> --prompt="<context>"', + desc: 'Update multiple tasks based on new requirements' + }, + { + name: 'update-task', + args: '--id=<id> --prompt="<context>"', + desc: 'Update a single specific task with new information' + }, + { + name: 'update-subtask', + args: '--id=<parentId.subtaskId> --prompt="<context>"', + desc: 'Append additional information to a subtask' + }, + { + name: 'add-task', + args: '--prompt="<text>" [--dependencies=<ids>] [--priority=<priority>]', + desc: 'Add a new task using AI' + }, + { + name: 'remove-task', + args: '--id=<id> [-y]', + desc: 'Permanently remove a task or subtask' + } + ] + }, + { + title: 'Subtask Management', + color: 'yellow', + commands: [ + { + name: 'add-subtask', + args: '--parent=<id> --title="<title>" [--description="<desc>"]', + desc: 'Add a new subtask to a parent task' + }, + { + name: 'add-subtask', + args: '--parent=<id> --task-id=<id>', + desc: 'Convert an existing task into a subtask' + }, + { + name: 'remove-subtask', + args: '--id=<parentId.subtaskId> [--convert]', + desc: 'Remove a subtask (optionally convert to standalone task)' + }, + { + name: 'clear-subtasks', + args: '--id=<id>', + desc: 'Remove all subtasks from specified tasks' + }, + { + name: 'clear-subtasks --all', + args: '', + desc: 'Remove subtasks from all tasks' + } + ] + }, + { + title: 'Task Analysis & Breakdown', + color: 'magenta', + commands: [ + { + name: 'analyze-complexity', + args: '[--research] [--threshold=5]', + desc: 'Analyze tasks and generate expansion recommendations' + }, + { + name: 'complexity-report', + args: '[--file=<path>]', + desc: 'Display the complexity analysis report' + }, + { + name: 'expand', + args: '--id=<id> [--num=5] [--research] [--prompt="<context>"]', + desc: 'Break down tasks into detailed subtasks' + }, + { + name: 'expand --all', + args: '[--force] [--research]', + desc: 'Expand all pending tasks with subtasks' + } + ] + }, + { + title: 'Task Navigation & Viewing', + color: 'cyan', + commands: [ + { + name: 'next', + args: '', + desc: 'Show the next task to work on based on dependencies' + }, + { + name: 'show', + args: '<id>', + desc: 'Display detailed information about a specific task' + } + ] + }, + { + title: 'Dependency Management', + color: 'blue', + commands: [ + { + name: 'add-dependency', + args: '--id=<id> --depends-on=<id>', + desc: 'Add a dependency to a task' + }, + { + name: 'remove-dependency', + args: '--id=<id> --depends-on=<id>', + desc: 'Remove a dependency from a task' + }, + { + name: 'validate-dependencies', + args: '', + desc: 'Identify invalid dependencies without fixing them' + }, + { + name: 'fix-dependencies', + args: '', + desc: 'Fix invalid dependencies automatically' + } + ] + } + ]; - // Display each category - commandCategories.forEach((category) => { - console.log( - boxen(chalk[category.color].bold(category.title), { - padding: { left: 2, right: 2, top: 0, bottom: 0 }, - margin: { top: 1, bottom: 0 }, - borderColor: category.color, - borderStyle: "round", - }) - ); + // Display each category + commandCategories.forEach((category) => { + console.log( + boxen(chalk[category.color].bold(category.title), { + padding: { left: 2, right: 2, top: 0, bottom: 0 }, + margin: { top: 1, bottom: 0 }, + borderColor: category.color, + borderStyle: 'round' + }) + ); - // Calculate dynamic column widths - adjust ratios as needed - const nameWidth = Math.max(25, Math.floor(terminalWidth * 0.2)); // 20% of width but min 25 - const argsWidth = Math.max(40, Math.floor(terminalWidth * 0.35)); // 35% of width but min 40 - const descWidth = Math.max(45, Math.floor(terminalWidth * 0.45) - 10); // 45% of width but min 45, minus some buffer + // Calculate dynamic column widths - adjust ratios as needed + const nameWidth = Math.max(25, Math.floor(terminalWidth * 0.2)); // 20% of width but min 25 + const argsWidth = Math.max(40, Math.floor(terminalWidth * 0.35)); // 35% of width but min 40 + const descWidth = Math.max(45, Math.floor(terminalWidth * 0.45) - 10); // 45% of width but min 45, minus some buffer - const commandTable = new Table({ - colWidths: [nameWidth, argsWidth, descWidth], - chars: { - top: "", - "top-mid": "", - "top-left": "", - "top-right": "", - bottom: "", - "bottom-mid": "", - "bottom-left": "", - "bottom-right": "", - left: "", - "left-mid": "", - mid: "", - "mid-mid": "", - right: "", - "right-mid": "", - middle: " ", - }, - style: { border: [], "padding-left": 4 }, - wordWrap: true, - }); + const commandTable = new Table({ + colWidths: [nameWidth, argsWidth, descWidth], + chars: { + top: '', + 'top-mid': '', + 'top-left': '', + 'top-right': '', + bottom: '', + 'bottom-mid': '', + 'bottom-left': '', + 'bottom-right': '', + left: '', + 'left-mid': '', + mid: '', + 'mid-mid': '', + right: '', + 'right-mid': '', + middle: ' ' + }, + style: { border: [], 'padding-left': 4 }, + wordWrap: true + }); - category.commands.forEach((cmd, index) => { - commandTable.push([ - `${chalk.yellow.bold(cmd.name)}${chalk.reset("")}`, - `${chalk.white(cmd.args)}${chalk.reset("")}`, - `${chalk.dim(cmd.desc)}${chalk.reset("")}`, - ]); - }); + category.commands.forEach((cmd, index) => { + commandTable.push([ + `${chalk.yellow.bold(cmd.name)}${chalk.reset('')}`, + `${chalk.white(cmd.args)}${chalk.reset('')}`, + `${chalk.dim(cmd.desc)}${chalk.reset('')}` + ]); + }); - console.log(commandTable.toString()); - console.log(""); - }); + console.log(commandTable.toString()); + console.log(''); + }); - // Display configuration section - console.log( - boxen(chalk.cyan.bold("Configuration"), { - padding: { left: 2, right: 2, top: 0, bottom: 0 }, - margin: { top: 1, bottom: 0 }, - borderColor: "cyan", - borderStyle: "round", - }) - ); + // Display configuration section + console.log( + boxen(chalk.cyan.bold('Configuration'), { + padding: { left: 2, right: 2, top: 0, bottom: 0 }, + margin: { top: 1, bottom: 0 }, + borderColor: 'cyan', + borderStyle: 'round' + }) + ); - // Get terminal width if not already defined - const configTerminalWidth = terminalWidth || process.stdout.columns || 100; + // Get terminal width if not already defined + const configTerminalWidth = terminalWidth || process.stdout.columns || 100; - // Calculate dynamic column widths for config table - const configKeyWidth = Math.max(30, Math.floor(configTerminalWidth * 0.25)); - const configDescWidth = Math.max(50, Math.floor(configTerminalWidth * 0.45)); - const configValueWidth = Math.max( - 30, - Math.floor(configTerminalWidth * 0.3) - 10 - ); + // Calculate dynamic column widths for config table + const configKeyWidth = Math.max(30, Math.floor(configTerminalWidth * 0.25)); + const configDescWidth = Math.max(50, Math.floor(configTerminalWidth * 0.45)); + const configValueWidth = Math.max( + 30, + Math.floor(configTerminalWidth * 0.3) - 10 + ); - const configTable = new Table({ - colWidths: [configKeyWidth, configDescWidth, configValueWidth], - chars: { - top: "", - "top-mid": "", - "top-left": "", - "top-right": "", - bottom: "", - "bottom-mid": "", - "bottom-left": "", - "bottom-right": "", - left: "", - "left-mid": "", - mid: "", - "mid-mid": "", - right: "", - "right-mid": "", - middle: " ", - }, - style: { border: [], "padding-left": 4 }, - wordWrap: true, - }); + const configTable = new Table({ + colWidths: [configKeyWidth, configDescWidth, configValueWidth], + chars: { + top: '', + 'top-mid': '', + 'top-left': '', + 'top-right': '', + bottom: '', + 'bottom-mid': '', + 'bottom-left': '', + 'bottom-right': '', + left: '', + 'left-mid': '', + mid: '', + 'mid-mid': '', + right: '', + 'right-mid': '', + middle: ' ' + }, + style: { border: [], 'padding-left': 4 }, + wordWrap: true + }); - configTable.push( - [ - `${chalk.yellow(TASKMASTER_CONFIG_FILE)}${chalk.reset("")}`, - `${chalk.white("AI model configuration file (project root)")}${chalk.reset("")}`, - `${chalk.dim("Managed by models cmd")}${chalk.reset("")}`, - ], - [ - `${chalk.yellow("API Keys (.env)")}${chalk.reset("")}`, - `${chalk.white("API keys for AI providers (ANTHROPIC_API_KEY, etc.)")}${chalk.reset("")}`, - `${chalk.dim("Required in .env file")}${chalk.reset("")}`, - ], - [ - `${chalk.yellow("MCP Keys (mcp.json)")}${chalk.reset("")}`, - `${chalk.white("API keys for Cursor integration")}${chalk.reset("")}`, - `${chalk.dim("Required in .cursor/")}${chalk.reset("")}`, - ] - ); + configTable.push( + [ + `${chalk.yellow(TASKMASTER_CONFIG_FILE)}${chalk.reset('')}`, + `${chalk.white('AI model configuration file (project root)')}${chalk.reset('')}`, + `${chalk.dim('Managed by models cmd')}${chalk.reset('')}` + ], + [ + `${chalk.yellow('API Keys (.env)')}${chalk.reset('')}`, + `${chalk.white('API keys for AI providers (ANTHROPIC_API_KEY, etc.)')}${chalk.reset('')}`, + `${chalk.dim('Required in .env file')}${chalk.reset('')}` + ], + [ + `${chalk.yellow('MCP Keys (mcp.json)')}${chalk.reset('')}`, + `${chalk.white('API keys for Cursor integration')}${chalk.reset('')}`, + `${chalk.dim('Required in .cursor/')}${chalk.reset('')}` + ] + ); - console.log(configTable.toString()); - console.log(""); + console.log(configTable.toString()); + console.log(''); - // Show helpful hints - console.log( - boxen( - chalk.white.bold("Quick Start:") + - "\n\n" + - chalk.cyan("1. Create Project: ") + - chalk.white("task-master init") + - "\n" + - chalk.cyan("2. Setup Models: ") + - chalk.white("task-master models --setup") + - "\n" + - chalk.cyan("3. Parse PRD: ") + - chalk.white("task-master parse-prd --input=<prd-file>") + - "\n" + - chalk.cyan("4. List Tasks: ") + - chalk.white("task-master list") + - "\n" + - chalk.cyan("5. Find Next Task: ") + - chalk.white("task-master next"), - { - padding: 1, - borderColor: "yellow", - borderStyle: "round", - margin: { top: 1 }, - width: Math.min(configTerminalWidth - 10, 100), // Limit width to terminal width minus padding, max 100 - } - ) - ); + // Show helpful hints + console.log( + boxen( + chalk.white.bold('Quick Start:') + + '\n\n' + + chalk.cyan('1. Create Project: ') + + chalk.white('task-master init') + + '\n' + + chalk.cyan('2. Setup Models: ') + + chalk.white('task-master models --setup') + + '\n' + + chalk.cyan('3. Parse PRD: ') + + chalk.white('task-master parse-prd --input=<prd-file>') + + '\n' + + chalk.cyan('4. List Tasks: ') + + chalk.white('task-master list') + + '\n' + + chalk.cyan('5. Find Next Task: ') + + chalk.white('task-master next'), + { + padding: 1, + borderColor: 'yellow', + borderStyle: 'round', + margin: { top: 1 }, + width: Math.min(configTerminalWidth - 10, 100) // Limit width to terminal width minus padding, max 100 + } + ) + ); } /** @@ -805,9 +805,9 @@ function displayHelp() { * @returns {string} Colored complexity score */ function getComplexityWithColor(score) { - if (score <= 3) return chalk.green(`โ— ${score}`); - if (score <= 6) return chalk.yellow(`โ— ${score}`); - return chalk.red(`โ— ${score}`); + if (score <= 3) return chalk.green(`โ— ${score}`); + if (score <= 6) return chalk.yellow(`โ— ${score}`); + return chalk.red(`โ— ${score}`); } /** @@ -817,9 +817,9 @@ function getComplexityWithColor(score) { * @returns {string} Truncated string */ function truncateString(str, maxLength) { - if (!str) return ""; - if (str.length <= maxLength) return str; - return str.substring(0, maxLength - 3) + "..."; + if (!str) return ''; + if (str.length <= maxLength) return str; + return str.substring(0, maxLength - 3) + '...'; } /** @@ -827,262 +827,262 @@ function truncateString(str, maxLength) { * @param {string} tasksPath - Path to the tasks.json file */ async function displayNextTask(tasksPath, complexityReportPath = null) { - // Read the tasks file - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log("error", "No valid tasks found."); - process.exit(1); - } + // Read the tasks file + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log('error', 'No valid tasks found.'); + process.exit(1); + } - // Read complexity report once - const complexityReport = readComplexityReport(complexityReportPath); + // Read complexity report once + const complexityReport = readComplexityReport(complexityReportPath); - // Find the next task - const nextTask = findNextTask(data.tasks, complexityReport); + // Find the next task + const nextTask = findNextTask(data.tasks, complexityReport); - if (!nextTask) { - console.log( - boxen( - chalk.yellow("No eligible tasks found!\n\n") + - "All pending tasks have unsatisfied dependencies, or all tasks are completed.", - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "yellow", - borderStyle: "round", - margin: { top: 1 }, - } - ) - ); - return; - } + if (!nextTask) { + console.log( + boxen( + chalk.yellow('No eligible tasks found!\n\n') + + 'All pending tasks have unsatisfied dependencies, or all tasks are completed.', + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'yellow', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); + return; + } - // Display the task in a nice format - console.log( - boxen(chalk.white.bold(`Next Task: #${nextTask.id} - ${nextTask.title}`), { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "blue", - borderStyle: "round", - margin: { top: 1, bottom: 0 }, - }) - ); + // Display the task in a nice format + console.log( + boxen(chalk.white.bold(`Next Task: #${nextTask.id} - ${nextTask.title}`), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'blue', + borderStyle: 'round', + margin: { top: 1, bottom: 0 } + }) + ); - // Create a table with task details - const taskTable = new Table({ - style: { - head: [], - border: [], - "padding-top": 0, - "padding-bottom": 0, - compact: true, - }, - chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, - colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], - wordWrap: true, - }); + // Create a table with task details + const taskTable = new Table({ + style: { + head: [], + border: [], + 'padding-top': 0, + 'padding-bottom': 0, + compact: true + }, + chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, + colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], + wordWrap: true + }); - // Priority with color - const priorityColors = { - high: chalk.red.bold, - medium: chalk.yellow, - low: chalk.gray, - }; - const priorityColor = - priorityColors[nextTask.priority || "medium"] || chalk.white; + // Priority with color + const priorityColors = { + high: chalk.red.bold, + medium: chalk.yellow, + low: chalk.gray + }; + const priorityColor = + priorityColors[nextTask.priority || 'medium'] || chalk.white; - // Add task details to table - taskTable.push( - [chalk.cyan.bold("ID:"), nextTask.id.toString()], - [chalk.cyan.bold("Title:"), nextTask.title], - [ - chalk.cyan.bold("Priority:"), - priorityColor(nextTask.priority || "medium"), - ], - [ - chalk.cyan.bold("Dependencies:"), - formatDependenciesWithStatus( - nextTask.dependencies, - data.tasks, - true, - complexityReport - ), - ], - [ - chalk.cyan.bold("Complexity:"), - nextTask.complexityScore - ? getComplexityWithColor(nextTask.complexityScore) - : chalk.gray("N/A"), - ], - [chalk.cyan.bold("Description:"), nextTask.description] - ); + // Add task details to table + taskTable.push( + [chalk.cyan.bold('ID:'), nextTask.id.toString()], + [chalk.cyan.bold('Title:'), nextTask.title], + [ + chalk.cyan.bold('Priority:'), + priorityColor(nextTask.priority || 'medium') + ], + [ + chalk.cyan.bold('Dependencies:'), + formatDependenciesWithStatus( + nextTask.dependencies, + data.tasks, + true, + complexityReport + ) + ], + [ + chalk.cyan.bold('Complexity:'), + nextTask.complexityScore + ? getComplexityWithColor(nextTask.complexityScore) + : chalk.gray('N/A') + ], + [chalk.cyan.bold('Description:'), nextTask.description] + ); - console.log(taskTable.toString()); + console.log(taskTable.toString()); - // If task has details, show them in a separate box - if (nextTask.details && nextTask.details.trim().length > 0) { - console.log( - boxen( - chalk.white.bold("Implementation Details:") + "\n\n" + nextTask.details, - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "cyan", - borderStyle: "round", - margin: { top: 1, bottom: 0 }, - } - ) - ); - } + // If task has details, show them in a separate box + if (nextTask.details && nextTask.details.trim().length > 0) { + console.log( + boxen( + chalk.white.bold('Implementation Details:') + '\n\n' + nextTask.details, + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'cyan', + borderStyle: 'round', + margin: { top: 1, bottom: 0 } + } + ) + ); + } - // Determine if the nextTask is a subtask - const isSubtask = !!nextTask.parentId; + // Determine if the nextTask is a subtask + const isSubtask = !!nextTask.parentId; - // Show subtasks if they exist (only for parent tasks) - if (!isSubtask && nextTask.subtasks && nextTask.subtasks.length > 0) { - console.log( - boxen(chalk.white.bold("Subtasks"), { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - margin: { top: 1, bottom: 0 }, - borderColor: "magenta", - borderStyle: "round", - }) - ); + // Show subtasks if they exist (only for parent tasks) + if (!isSubtask && nextTask.subtasks && nextTask.subtasks.length > 0) { + console.log( + boxen(chalk.white.bold('Subtasks'), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 1, bottom: 0 }, + borderColor: 'magenta', + borderStyle: 'round' + }) + ); - // Calculate available width for the subtask table - const availableWidth = process.stdout.columns - 10 || 100; // Default to 100 if can't detect + // Calculate available width for the subtask table + const availableWidth = process.stdout.columns - 10 || 100; // Default to 100 if can't detect - // Define percentage-based column widths - const idWidthPct = 8; - const statusWidthPct = 15; - const depsWidthPct = 25; - const titleWidthPct = 100 - idWidthPct - statusWidthPct - depsWidthPct; + // Define percentage-based column widths + const idWidthPct = 8; + const statusWidthPct = 15; + const depsWidthPct = 25; + const titleWidthPct = 100 - idWidthPct - statusWidthPct - depsWidthPct; - // Calculate actual column widths - const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); - const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); - const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); - const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); + // Calculate actual column widths + const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); + const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); + const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); + const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); - // Create a table for subtasks with improved handling - const subtaskTable = new Table({ - head: [ - chalk.magenta.bold("ID"), - chalk.magenta.bold("Status"), - chalk.magenta.bold("Title"), - chalk.magenta.bold("Deps"), - ], - colWidths: [idWidth, statusWidth, titleWidth, depsWidth], - style: { - head: [], - border: [], - "padding-top": 0, - "padding-bottom": 0, - compact: true, - }, - chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, - wordWrap: true, - }); + // Create a table for subtasks with improved handling + const subtaskTable = new Table({ + head: [ + chalk.magenta.bold('ID'), + chalk.magenta.bold('Status'), + chalk.magenta.bold('Title'), + chalk.magenta.bold('Deps') + ], + colWidths: [idWidth, statusWidth, titleWidth, depsWidth], + style: { + head: [], + border: [], + 'padding-top': 0, + 'padding-bottom': 0, + compact: true + }, + chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, + wordWrap: true + }); - // Add subtasks to table - nextTask.subtasks.forEach((st) => { - const statusColor = - { - done: chalk.green, - completed: chalk.green, - pending: chalk.yellow, - "in-progress": chalk.blue, - }[st.status || "pending"] || chalk.white; + // Add subtasks to table + nextTask.subtasks.forEach((st) => { + const statusColor = + { + done: chalk.green, + completed: chalk.green, + pending: chalk.yellow, + 'in-progress': chalk.blue + }[st.status || 'pending'] || chalk.white; - // Format subtask dependencies - let subtaskDeps = "None"; - if (st.dependencies && st.dependencies.length > 0) { - // Format dependencies with correct notation - const formattedDeps = st.dependencies.map((depId) => { - if (typeof depId === "number" && depId < 100) { - const foundSubtask = nextTask.subtasks.find( - (st) => st.id === depId - ); - if (foundSubtask) { - const isDone = - foundSubtask.status === "done" || - foundSubtask.status === "completed"; - const isInProgress = foundSubtask.status === "in-progress"; + // Format subtask dependencies + let subtaskDeps = 'None'; + if (st.dependencies && st.dependencies.length > 0) { + // Format dependencies with correct notation + const formattedDeps = st.dependencies.map((depId) => { + if (typeof depId === 'number' && depId < 100) { + const foundSubtask = nextTask.subtasks.find( + (st) => st.id === depId + ); + if (foundSubtask) { + const isDone = + foundSubtask.status === 'done' || + foundSubtask.status === 'completed'; + const isInProgress = foundSubtask.status === 'in-progress'; - // Use consistent color formatting instead of emojis - if (isDone) { - return chalk.green.bold(`${nextTask.id}.${depId}`); - } else if (isInProgress) { - return chalk.hex("#FFA500").bold(`${nextTask.id}.${depId}`); - } else { - return chalk.red.bold(`${nextTask.id}.${depId}`); - } - } - return chalk.red(`${nextTask.id}.${depId} (Not found)`); - } - return depId; - }); + // Use consistent color formatting instead of emojis + if (isDone) { + return chalk.green.bold(`${nextTask.id}.${depId}`); + } else if (isInProgress) { + return chalk.hex('#FFA500').bold(`${nextTask.id}.${depId}`); + } else { + return chalk.red.bold(`${nextTask.id}.${depId}`); + } + } + return chalk.red(`${nextTask.id}.${depId} (Not found)`); + } + return depId; + }); - // Join the formatted dependencies directly instead of passing to formatDependenciesWithStatus again - subtaskDeps = - formattedDeps.length === 1 - ? formattedDeps[0] - : formattedDeps.join(chalk.white(", ")); - } + // Join the formatted dependencies directly instead of passing to formatDependenciesWithStatus again + subtaskDeps = + formattedDeps.length === 1 + ? formattedDeps[0] + : formattedDeps.join(chalk.white(', ')); + } - subtaskTable.push([ - `${nextTask.id}.${st.id}`, - statusColor(st.status || "pending"), - st.title, - subtaskDeps, - ]); - }); + subtaskTable.push([ + `${nextTask.id}.${st.id}`, + statusColor(st.status || 'pending'), + st.title, + subtaskDeps + ]); + }); - console.log(subtaskTable.toString()); - } + console.log(subtaskTable.toString()); + } - // Suggest expanding if no subtasks (only for parent tasks without subtasks) - if (!isSubtask && (!nextTask.subtasks || nextTask.subtasks.length === 0)) { - console.log( - boxen( - chalk.yellow("No subtasks found. Consider breaking down this task:") + - "\n" + - chalk.white( - `Run: ${chalk.cyan(`task-master expand --id=${nextTask.id}`)}` - ), - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "yellow", - borderStyle: "round", - margin: { top: 1, bottom: 0 }, - } - ) - ); - } + // Suggest expanding if no subtasks (only for parent tasks without subtasks) + if (!isSubtask && (!nextTask.subtasks || nextTask.subtasks.length === 0)) { + console.log( + boxen( + chalk.yellow('No subtasks found. Consider breaking down this task:') + + '\n' + + chalk.white( + `Run: ${chalk.cyan(`task-master expand --id=${nextTask.id}`)}` + ), + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'yellow', + borderStyle: 'round', + margin: { top: 1, bottom: 0 } + } + ) + ); + } - // Show action suggestions - let suggestedActionsContent = chalk.white.bold("Suggested Actions:") + "\n"; - if (isSubtask) { - // Suggested actions for a subtask - suggestedActionsContent += - `${chalk.cyan("1.")} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` + - `${chalk.cyan("2.")} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` + - `${chalk.cyan("3.")} View parent task: ${chalk.yellow(`task-master show --id=${nextTask.parentId}`)}`; - } else { - // Suggested actions for a parent task - suggestedActionsContent += - `${chalk.cyan("1.")} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` + - `${chalk.cyan("2.")} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` + - (nextTask.subtasks && nextTask.subtasks.length > 0 - ? `${chalk.cyan("3.")} Update subtask status: ${chalk.yellow(`task-master set-status --id=${nextTask.id}.1 --status=done`)}` // Example: first subtask - : `${chalk.cyan("3.")} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${nextTask.id}`)}`); - } + // Show action suggestions + let suggestedActionsContent = chalk.white.bold('Suggested Actions:') + '\n'; + if (isSubtask) { + // Suggested actions for a subtask + suggestedActionsContent += + `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` + + `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` + + `${chalk.cyan('3.')} View parent task: ${chalk.yellow(`task-master show --id=${nextTask.parentId}`)}`; + } else { + // Suggested actions for a parent task + suggestedActionsContent += + `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=in-progress`)}\n` + + `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${nextTask.id} --status=done`)}\n` + + (nextTask.subtasks && nextTask.subtasks.length > 0 + ? `${chalk.cyan('3.')} Update subtask status: ${chalk.yellow(`task-master set-status --id=${nextTask.id}.1 --status=done`)}` // Example: first subtask + : `${chalk.cyan('3.')} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${nextTask.id}`)}`); + } - console.log( - boxen(suggestedActionsContent, { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "green", - borderStyle: "round", - margin: { top: 1 }, - }) - ); + console.log( + boxen(suggestedActionsContent, { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'green', + borderStyle: 'round', + margin: { top: 1 } + }) + ); } /** @@ -1092,458 +1092,458 @@ async function displayNextTask(tasksPath, complexityReportPath = null) { * @param {string} [statusFilter] - Optional status to filter subtasks by */ async function displayTaskById( - tasksPath, - taskId, - complexityReportPath = null, - statusFilter = null + tasksPath, + taskId, + complexityReportPath = null, + statusFilter = null ) { - // Read the tasks file - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log("error", "No valid tasks found."); - process.exit(1); - } + // Read the tasks file + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + log('error', 'No valid tasks found.'); + process.exit(1); + } - // Read complexity report once - const complexityReport = readComplexityReport(complexityReportPath); + // Read complexity report once + const complexityReport = readComplexityReport(complexityReportPath); - // Find the task by ID, applying the status filter if provided - // Returns { task, originalSubtaskCount, originalSubtasks } - const { task, originalSubtaskCount, originalSubtasks } = findTaskById( - data.tasks, - taskId, - complexityReport, - statusFilter - ); + // Find the task by ID, applying the status filter if provided + // Returns { task, originalSubtaskCount, originalSubtasks } + const { task, originalSubtaskCount, originalSubtasks } = findTaskById( + data.tasks, + taskId, + complexityReport, + statusFilter + ); - if (!task) { - console.log( - boxen(chalk.yellow(`Task with ID ${taskId} not found!`), { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "yellow", - borderStyle: "round", - margin: { top: 1 }, - }) - ); - return; - } + if (!task) { + console.log( + boxen(chalk.yellow(`Task with ID ${taskId} not found!`), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'yellow', + borderStyle: 'round', + margin: { top: 1 } + }) + ); + return; + } - // Handle subtask display specially (This logic remains the same) - if (task.isSubtask || task.parentTask) { - console.log( - boxen( - chalk.white.bold( - `Subtask: #${task.parentTask.id}.${task.id} - ${task.title}` - ), - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "magenta", - borderStyle: "round", - margin: { top: 1, bottom: 0 }, - } - ) - ); + // Handle subtask display specially (This logic remains the same) + if (task.isSubtask || task.parentTask) { + console.log( + boxen( + chalk.white.bold( + `Subtask: #${task.parentTask.id}.${task.id} - ${task.title}` + ), + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'magenta', + borderStyle: 'round', + margin: { top: 1, bottom: 0 } + } + ) + ); - const subtaskTable = new Table({ - style: { - head: [], - border: [], - "padding-top": 0, - "padding-bottom": 0, - compact: true, - }, - chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, - colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], - wordWrap: true, - }); - subtaskTable.push( - [chalk.cyan.bold("ID:"), `${task.parentTask.id}.${task.id}`], - [ - chalk.cyan.bold("Parent Task:"), - `#${task.parentTask.id} - ${task.parentTask.title}`, - ], - [chalk.cyan.bold("Title:"), task.title], - [ - chalk.cyan.bold("Status:"), - getStatusWithColor(task.status || "pending", true), - ], - [ - chalk.cyan.bold("Complexity:"), - task.complexityScore - ? getComplexityWithColor(task.complexityScore) - : chalk.gray("N/A"), - ], - [ - chalk.cyan.bold("Description:"), - task.description || "No description provided.", - ] - ); - console.log(subtaskTable.toString()); + const subtaskTable = new Table({ + style: { + head: [], + border: [], + 'padding-top': 0, + 'padding-bottom': 0, + compact: true + }, + chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, + colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], + wordWrap: true + }); + subtaskTable.push( + [chalk.cyan.bold('ID:'), `${task.parentTask.id}.${task.id}`], + [ + chalk.cyan.bold('Parent Task:'), + `#${task.parentTask.id} - ${task.parentTask.title}` + ], + [chalk.cyan.bold('Title:'), task.title], + [ + chalk.cyan.bold('Status:'), + getStatusWithColor(task.status || 'pending', true) + ], + [ + chalk.cyan.bold('Complexity:'), + task.complexityScore + ? getComplexityWithColor(task.complexityScore) + : chalk.gray('N/A') + ], + [ + chalk.cyan.bold('Description:'), + task.description || 'No description provided.' + ] + ); + console.log(subtaskTable.toString()); - if (task.details && task.details.trim().length > 0) { - console.log( - boxen( - chalk.white.bold("Implementation Details:") + "\n\n" + task.details, - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "cyan", - borderStyle: "round", - margin: { top: 1, bottom: 0 }, - } - ) - ); - } + if (task.details && task.details.trim().length > 0) { + console.log( + boxen( + chalk.white.bold('Implementation Details:') + '\n\n' + task.details, + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'cyan', + borderStyle: 'round', + margin: { top: 1, bottom: 0 } + } + ) + ); + } - console.log( - boxen( - chalk.white.bold("Suggested Actions:") + - "\n" + - `${chalk.cyan("1.")} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=in-progress`)}\n` + - `${chalk.cyan("2.")} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=done`)}\n` + - `${chalk.cyan("3.")} View parent task: ${chalk.yellow(`task-master show --id=${task.parentTask.id}`)}`, - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "green", - borderStyle: "round", - margin: { top: 1 }, - } - ) - ); - return; // Exit after displaying subtask details - } + console.log( + boxen( + chalk.white.bold('Suggested Actions:') + + '\n' + + `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=in-progress`)}\n` + + `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.parentTask.id}.${task.id} --status=done`)}\n` + + `${chalk.cyan('3.')} View parent task: ${chalk.yellow(`task-master show --id=${task.parentTask.id}`)}`, + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'green', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); + return; // Exit after displaying subtask details + } - // --- Display Regular Task Details --- - console.log( - boxen(chalk.white.bold(`Task: #${task.id} - ${task.title}`), { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "blue", - borderStyle: "round", - margin: { top: 1, bottom: 0 }, - }) - ); + // --- Display Regular Task Details --- + console.log( + boxen(chalk.white.bold(`Task: #${task.id} - ${task.title}`), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'blue', + borderStyle: 'round', + margin: { top: 1, bottom: 0 } + }) + ); - const taskTable = new Table({ - style: { - head: [], - border: [], - "padding-top": 0, - "padding-bottom": 0, - compact: true, - }, - chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, - colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], - wordWrap: true, - }); - const priorityColors = { - high: chalk.red.bold, - medium: chalk.yellow, - low: chalk.gray, - }; - const priorityColor = - priorityColors[task.priority || "medium"] || chalk.white; - taskTable.push( - [chalk.cyan.bold("ID:"), task.id.toString()], - [chalk.cyan.bold("Title:"), task.title], - [ - chalk.cyan.bold("Status:"), - getStatusWithColor(task.status || "pending", true), - ], - [chalk.cyan.bold("Priority:"), priorityColor(task.priority || "medium")], - [ - chalk.cyan.bold("Dependencies:"), - formatDependenciesWithStatus( - task.dependencies, - data.tasks, - true, - complexityReport - ), - ], - [ - chalk.cyan.bold("Complexity:"), - task.complexityScore - ? getComplexityWithColor(task.complexityScore) - : chalk.gray("N/A"), - ], - [chalk.cyan.bold("Description:"), task.description] - ); - console.log(taskTable.toString()); + const taskTable = new Table({ + style: { + head: [], + border: [], + 'padding-top': 0, + 'padding-bottom': 0, + compact: true + }, + chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, + colWidths: [15, Math.min(75, process.stdout.columns - 20 || 60)], + wordWrap: true + }); + const priorityColors = { + high: chalk.red.bold, + medium: chalk.yellow, + low: chalk.gray + }; + const priorityColor = + priorityColors[task.priority || 'medium'] || chalk.white; + taskTable.push( + [chalk.cyan.bold('ID:'), task.id.toString()], + [chalk.cyan.bold('Title:'), task.title], + [ + chalk.cyan.bold('Status:'), + getStatusWithColor(task.status || 'pending', true) + ], + [chalk.cyan.bold('Priority:'), priorityColor(task.priority || 'medium')], + [ + chalk.cyan.bold('Dependencies:'), + formatDependenciesWithStatus( + task.dependencies, + data.tasks, + true, + complexityReport + ) + ], + [ + chalk.cyan.bold('Complexity:'), + task.complexityScore + ? getComplexityWithColor(task.complexityScore) + : chalk.gray('N/A') + ], + [chalk.cyan.bold('Description:'), task.description] + ); + console.log(taskTable.toString()); - if (task.details && task.details.trim().length > 0) { - console.log( - boxen( - chalk.white.bold("Implementation Details:") + "\n\n" + task.details, - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "cyan", - borderStyle: "round", - margin: { top: 1, bottom: 0 }, - } - ) - ); - } - if (task.testStrategy && task.testStrategy.trim().length > 0) { - console.log( - boxen(chalk.white.bold("Test Strategy:") + "\n\n" + task.testStrategy, { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "cyan", - borderStyle: "round", - margin: { top: 1, bottom: 0 }, - }) - ); - } + if (task.details && task.details.trim().length > 0) { + console.log( + boxen( + chalk.white.bold('Implementation Details:') + '\n\n' + task.details, + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'cyan', + borderStyle: 'round', + margin: { top: 1, bottom: 0 } + } + ) + ); + } + if (task.testStrategy && task.testStrategy.trim().length > 0) { + console.log( + boxen(chalk.white.bold('Test Strategy:') + '\n\n' + task.testStrategy, { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'cyan', + borderStyle: 'round', + margin: { top: 1, bottom: 0 } + }) + ); + } - // --- Subtask Table Display (uses filtered list: task.subtasks) --- - if (task.subtasks && task.subtasks.length > 0) { - console.log( - boxen(chalk.white.bold("Subtasks"), { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - margin: { top: 1, bottom: 0 }, - borderColor: "magenta", - borderStyle: "round", - }) - ); + // --- Subtask Table Display (uses filtered list: task.subtasks) --- + if (task.subtasks && task.subtasks.length > 0) { + console.log( + boxen(chalk.white.bold('Subtasks'), { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 1, bottom: 0 }, + borderColor: 'magenta', + borderStyle: 'round' + }) + ); - const availableWidth = process.stdout.columns - 10 || 100; - const idWidthPct = 10; - const statusWidthPct = 15; - const depsWidthPct = 25; - const titleWidthPct = 100 - idWidthPct - statusWidthPct - depsWidthPct; - const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); - const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); - const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); - const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); + const availableWidth = process.stdout.columns - 10 || 100; + const idWidthPct = 10; + const statusWidthPct = 15; + const depsWidthPct = 25; + const titleWidthPct = 100 - idWidthPct - statusWidthPct - depsWidthPct; + const idWidth = Math.floor(availableWidth * (idWidthPct / 100)); + const statusWidth = Math.floor(availableWidth * (statusWidthPct / 100)); + const depsWidth = Math.floor(availableWidth * (depsWidthPct / 100)); + const titleWidth = Math.floor(availableWidth * (titleWidthPct / 100)); - const subtaskTable = new Table({ - head: [ - chalk.magenta.bold("ID"), - chalk.magenta.bold("Status"), - chalk.magenta.bold("Title"), - chalk.magenta.bold("Deps"), - ], - colWidths: [idWidth, statusWidth, titleWidth, depsWidth], - style: { - head: [], - border: [], - "padding-top": 0, - "padding-bottom": 0, - compact: true, - }, - chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, - wordWrap: true, - }); + const subtaskTable = new Table({ + head: [ + chalk.magenta.bold('ID'), + chalk.magenta.bold('Status'), + chalk.magenta.bold('Title'), + chalk.magenta.bold('Deps') + ], + colWidths: [idWidth, statusWidth, titleWidth, depsWidth], + style: { + head: [], + border: [], + 'padding-top': 0, + 'padding-bottom': 0, + compact: true + }, + chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' }, + wordWrap: true + }); - // Populate table with the potentially filtered subtasks - task.subtasks.forEach((st) => { - const statusColorMap = { - done: chalk.green, - completed: chalk.green, - pending: chalk.yellow, - "in-progress": chalk.blue, - }; - const statusColor = statusColorMap[st.status || "pending"] || chalk.white; - let subtaskDeps = "None"; - if (st.dependencies && st.dependencies.length > 0) { - const formattedDeps = st.dependencies.map((depId) => { - // Use the original, unfiltered list for dependency status lookup - const sourceListForDeps = originalSubtasks || task.subtasks; - const foundDepSubtask = - typeof depId === "number" && depId < 100 - ? sourceListForDeps.find((sub) => sub.id === depId) - : null; + // Populate table with the potentially filtered subtasks + task.subtasks.forEach((st) => { + const statusColorMap = { + done: chalk.green, + completed: chalk.green, + pending: chalk.yellow, + 'in-progress': chalk.blue + }; + const statusColor = statusColorMap[st.status || 'pending'] || chalk.white; + let subtaskDeps = 'None'; + if (st.dependencies && st.dependencies.length > 0) { + const formattedDeps = st.dependencies.map((depId) => { + // Use the original, unfiltered list for dependency status lookup + const sourceListForDeps = originalSubtasks || task.subtasks; + const foundDepSubtask = + typeof depId === 'number' && depId < 100 + ? sourceListForDeps.find((sub) => sub.id === depId) + : null; - if (foundDepSubtask) { - const isDone = - foundDepSubtask.status === "done" || - foundDepSubtask.status === "completed"; - const isInProgress = foundDepSubtask.status === "in-progress"; - const color = isDone - ? chalk.green.bold - : isInProgress - ? chalk.hex("#FFA500").bold - : chalk.red.bold; - return color(`${task.id}.${depId}`); - } else if (typeof depId === "number" && depId < 100) { - return chalk.red(`${task.id}.${depId} (Not found)`); - } - return depId; // Assume it's a top-level task ID if not a number < 100 - }); - subtaskDeps = - formattedDeps.length === 1 - ? formattedDeps[0] - : formattedDeps.join(chalk.white(", ")); - } - subtaskTable.push([ - `${task.id}.${st.id}`, - statusColor(st.status || "pending"), - st.title, - subtaskDeps, - ]); - }); - console.log(subtaskTable.toString()); + if (foundDepSubtask) { + const isDone = + foundDepSubtask.status === 'done' || + foundDepSubtask.status === 'completed'; + const isInProgress = foundDepSubtask.status === 'in-progress'; + const color = isDone + ? chalk.green.bold + : isInProgress + ? chalk.hex('#FFA500').bold + : chalk.red.bold; + return color(`${task.id}.${depId}`); + } else if (typeof depId === 'number' && depId < 100) { + return chalk.red(`${task.id}.${depId} (Not found)`); + } + return depId; // Assume it's a top-level task ID if not a number < 100 + }); + subtaskDeps = + formattedDeps.length === 1 + ? formattedDeps[0] + : formattedDeps.join(chalk.white(', ')); + } + subtaskTable.push([ + `${task.id}.${st.id}`, + statusColor(st.status || 'pending'), + st.title, + subtaskDeps + ]); + }); + console.log(subtaskTable.toString()); - // Display filter summary line *immediately after the table* if a filter was applied - if (statusFilter && originalSubtaskCount !== null) { - console.log( - chalk.cyan( - ` Filtered by status: ${chalk.bold(statusFilter)}. Showing ${chalk.bold(task.subtasks.length)} of ${chalk.bold(originalSubtaskCount)} subtasks.` - ) - ); - // Add a newline for spacing before the progress bar if the filter line was shown - console.log(); - } - // --- Conditional Messages for No Subtasks Shown --- - } else if (statusFilter && originalSubtaskCount === 0) { - // Case where filter applied, but the parent task had 0 subtasks originally - console.log( - boxen( - chalk.yellow( - `No subtasks found matching status: ${statusFilter} (Task has no subtasks)` - ), - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - margin: { top: 1, bottom: 0 }, - borderColor: "yellow", - borderStyle: "round", - } - ) - ); - } else if ( - statusFilter && - originalSubtaskCount > 0 && - task.subtasks.length === 0 - ) { - // Case where filter applied, original subtasks existed, but none matched - console.log( - boxen( - chalk.yellow( - `No subtasks found matching status: ${statusFilter} (out of ${originalSubtaskCount} total)` - ), - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - margin: { top: 1, bottom: 0 }, - borderColor: "yellow", - borderStyle: "round", - } - ) - ); - } else if ( - !statusFilter && - (!originalSubtasks || originalSubtasks.length === 0) - ) { - // Case where NO filter applied AND the task genuinely has no subtasks - // Use the authoritative originalSubtasks if it exists (from filtering), else check task.subtasks - const actualSubtasks = originalSubtasks || task.subtasks; - if (!actualSubtasks || actualSubtasks.length === 0) { - console.log( - boxen( - chalk.yellow("No subtasks found. Consider breaking down this task:") + - "\n" + - chalk.white( - `Run: ${chalk.cyan(`task-master expand --id=${task.id}`)}` - ), - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "yellow", - borderStyle: "round", - margin: { top: 1, bottom: 0 }, - } - ) - ); - } - } + // Display filter summary line *immediately after the table* if a filter was applied + if (statusFilter && originalSubtaskCount !== null) { + console.log( + chalk.cyan( + ` Filtered by status: ${chalk.bold(statusFilter)}. Showing ${chalk.bold(task.subtasks.length)} of ${chalk.bold(originalSubtaskCount)} subtasks.` + ) + ); + // Add a newline for spacing before the progress bar if the filter line was shown + console.log(); + } + // --- Conditional Messages for No Subtasks Shown --- + } else if (statusFilter && originalSubtaskCount === 0) { + // Case where filter applied, but the parent task had 0 subtasks originally + console.log( + boxen( + chalk.yellow( + `No subtasks found matching status: ${statusFilter} (Task has no subtasks)` + ), + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 1, bottom: 0 }, + borderColor: 'yellow', + borderStyle: 'round' + } + ) + ); + } else if ( + statusFilter && + originalSubtaskCount > 0 && + task.subtasks.length === 0 + ) { + // Case where filter applied, original subtasks existed, but none matched + console.log( + boxen( + chalk.yellow( + `No subtasks found matching status: ${statusFilter} (out of ${originalSubtaskCount} total)` + ), + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 1, bottom: 0 }, + borderColor: 'yellow', + borderStyle: 'round' + } + ) + ); + } else if ( + !statusFilter && + (!originalSubtasks || originalSubtasks.length === 0) + ) { + // Case where NO filter applied AND the task genuinely has no subtasks + // Use the authoritative originalSubtasks if it exists (from filtering), else check task.subtasks + const actualSubtasks = originalSubtasks || task.subtasks; + if (!actualSubtasks || actualSubtasks.length === 0) { + console.log( + boxen( + chalk.yellow('No subtasks found. Consider breaking down this task:') + + '\n' + + chalk.white( + `Run: ${chalk.cyan(`task-master expand --id=${task.id}`)}` + ), + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'yellow', + borderStyle: 'round', + margin: { top: 1, bottom: 0 } + } + ) + ); + } + } - // --- Subtask Progress Bar Display (uses originalSubtasks or task.subtasks) --- - // Determine the list to use for progress calculation (always the original if available and filtering happened) - const subtasksForProgress = originalSubtasks || task.subtasks; // Use original if filtering occurred, else the potentially empty task.subtasks + // --- Subtask Progress Bar Display (uses originalSubtasks or task.subtasks) --- + // Determine the list to use for progress calculation (always the original if available and filtering happened) + const subtasksForProgress = originalSubtasks || task.subtasks; // Use original if filtering occurred, else the potentially empty task.subtasks - // Only show progress if there are actually subtasks - if (subtasksForProgress && subtasksForProgress.length > 0) { - const totalSubtasks = subtasksForProgress.length; - const completedSubtasks = subtasksForProgress.filter( - (st) => st.status === "done" || st.status === "completed" - ).length; + // Only show progress if there are actually subtasks + if (subtasksForProgress && subtasksForProgress.length > 0) { + const totalSubtasks = subtasksForProgress.length; + const completedSubtasks = subtasksForProgress.filter( + (st) => st.status === 'done' || st.status === 'completed' + ).length; - // Count other statuses from the original/complete list - const inProgressSubtasks = subtasksForProgress.filter( - (st) => st.status === "in-progress" - ).length; - const pendingSubtasks = subtasksForProgress.filter( - (st) => st.status === "pending" - ).length; - const blockedSubtasks = subtasksForProgress.filter( - (st) => st.status === "blocked" - ).length; - const deferredSubtasks = subtasksForProgress.filter( - (st) => st.status === "deferred" - ).length; - const cancelledSubtasks = subtasksForProgress.filter( - (st) => st.status === "cancelled" - ).length; + // Count other statuses from the original/complete list + const inProgressSubtasks = subtasksForProgress.filter( + (st) => st.status === 'in-progress' + ).length; + const pendingSubtasks = subtasksForProgress.filter( + (st) => st.status === 'pending' + ).length; + const blockedSubtasks = subtasksForProgress.filter( + (st) => st.status === 'blocked' + ).length; + const deferredSubtasks = subtasksForProgress.filter( + (st) => st.status === 'deferred' + ).length; + const cancelledSubtasks = subtasksForProgress.filter( + (st) => st.status === 'cancelled' + ).length; - const statusBreakdown = { - // Calculate breakdown based on the complete list - "in-progress": (inProgressSubtasks / totalSubtasks) * 100, - pending: (pendingSubtasks / totalSubtasks) * 100, - blocked: (blockedSubtasks / totalSubtasks) * 100, - deferred: (deferredSubtasks / totalSubtasks) * 100, - cancelled: (cancelledSubtasks / totalSubtasks) * 100, - }; - const completionPercentage = (completedSubtasks / totalSubtasks) * 100; + const statusBreakdown = { + // Calculate breakdown based on the complete list + 'in-progress': (inProgressSubtasks / totalSubtasks) * 100, + pending: (pendingSubtasks / totalSubtasks) * 100, + blocked: (blockedSubtasks / totalSubtasks) * 100, + deferred: (deferredSubtasks / totalSubtasks) * 100, + cancelled: (cancelledSubtasks / totalSubtasks) * 100 + }; + const completionPercentage = (completedSubtasks / totalSubtasks) * 100; - const availableWidth = process.stdout.columns || 80; - const boxPadding = 2; - const boxBorders = 2; - const percentTextLength = 5; - const progressBarLength = Math.max( - 20, - Math.min( - 60, - availableWidth - boxPadding - boxBorders - percentTextLength - 35 - ) - ); + const availableWidth = process.stdout.columns || 80; + const boxPadding = 2; + const boxBorders = 2; + const percentTextLength = 5; + const progressBarLength = Math.max( + 20, + Math.min( + 60, + availableWidth - boxPadding - boxBorders - percentTextLength - 35 + ) + ); - const statusCounts = - `${chalk.green("โœ“ Done:")} ${completedSubtasks} ${chalk.hex("#FFA500")("โ–บ In Progress:")} ${inProgressSubtasks} ${chalk.yellow("โ—‹ Pending:")} ${pendingSubtasks}\n` + - `${chalk.red("! Blocked:")} ${blockedSubtasks} ${chalk.gray("โฑ Deferred:")} ${deferredSubtasks} ${chalk.gray("โœ— Cancelled:")} ${cancelledSubtasks}`; + const statusCounts = + `${chalk.green('โœ“ Done:')} ${completedSubtasks} ${chalk.hex('#FFA500')('โ–บ In Progress:')} ${inProgressSubtasks} ${chalk.yellow('โ—‹ Pending:')} ${pendingSubtasks}\n` + + `${chalk.red('! Blocked:')} ${blockedSubtasks} ${chalk.gray('โฑ Deferred:')} ${deferredSubtasks} ${chalk.gray('โœ— Cancelled:')} ${cancelledSubtasks}`; - console.log( - boxen( - chalk.white.bold("Subtask Progress:") + - "\n\n" + - `${chalk.cyan("Completed:")} ${completedSubtasks}/${totalSubtasks} (${completionPercentage.toFixed(1)}%)\n` + - `${statusCounts}\n` + - `${chalk.cyan("Progress:")} ${createProgressBar(completionPercentage, progressBarLength, statusBreakdown)}`, - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "blue", - borderStyle: "round", - margin: { top: 1, bottom: 0 }, - width: Math.min(availableWidth - 10, 100), - textAlignment: "left", - } - ) - ); - } + console.log( + boxen( + chalk.white.bold('Subtask Progress:') + + '\n\n' + + `${chalk.cyan('Completed:')} ${completedSubtasks}/${totalSubtasks} (${completionPercentage.toFixed(1)}%)\n` + + `${statusCounts}\n` + + `${chalk.cyan('Progress:')} ${createProgressBar(completionPercentage, progressBarLength, statusBreakdown)}`, + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'blue', + borderStyle: 'round', + margin: { top: 1, bottom: 0 }, + width: Math.min(availableWidth - 10, 100), + textAlignment: 'left' + } + ) + ); + } - // --- Suggested Actions --- - console.log( - boxen( - chalk.white.bold("Suggested Actions:") + - "\n" + - `${chalk.cyan("1.")} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}\n` + - `${chalk.cyan("2.")} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.id} --status=done`)}\n` + - // Determine action 3 based on whether subtasks *exist* (use the source list for progress) - (subtasksForProgress && subtasksForProgress.length > 0 - ? `${chalk.cyan("3.")} Update subtask status: ${chalk.yellow(`task-master set-status --id=${task.id}.1 --status=done`)}` // Example uses .1 - : `${chalk.cyan("3.")} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${task.id}`)}`), - { - padding: { top: 0, bottom: 0, left: 1, right: 1 }, - borderColor: "green", - borderStyle: "round", - margin: { top: 1 }, - } - ) - ); + // --- Suggested Actions --- + console.log( + boxen( + chalk.white.bold('Suggested Actions:') + + '\n' + + `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`task-master set-status --id=${task.id} --status=in-progress`)}\n` + + `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`task-master set-status --id=${task.id} --status=done`)}\n` + + // Determine action 3 based on whether subtasks *exist* (use the source list for progress) + (subtasksForProgress && subtasksForProgress.length > 0 + ? `${chalk.cyan('3.')} Update subtask status: ${chalk.yellow(`task-master set-status --id=${task.id}.1 --status=done`)}` // Example uses .1 + : `${chalk.cyan('3.')} Break down into subtasks: ${chalk.yellow(`task-master expand --id=${task.id}`)}`), + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + borderColor: 'green', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); } /** @@ -1551,249 +1551,249 @@ async function displayTaskById( * @param {string} reportPath - Path to the complexity report file */ async function displayComplexityReport(reportPath) { - // Check if the report exists - if (!fs.existsSync(reportPath)) { - console.log( - boxen( - chalk.yellow(`No complexity report found at ${reportPath}\n\n`) + - "Would you like to generate one now?", - { - padding: 1, - borderColor: "yellow", - borderStyle: "round", - margin: { top: 1 }, - } - ) - ); + // Check if the report exists + if (!fs.existsSync(reportPath)) { + console.log( + boxen( + chalk.yellow(`No complexity report found at ${reportPath}\n\n`) + + 'Would you like to generate one now?', + { + padding: 1, + borderColor: 'yellow', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); - const readline = require("readline").createInterface({ - input: process.stdin, - output: process.stdout, - }); + const readline = require('readline').createInterface({ + input: process.stdin, + output: process.stdout + }); - const answer = await new Promise((resolve) => { - readline.question( - chalk.cyan("Generate complexity report? (y/n): "), - resolve - ); - }); - readline.close(); + const answer = await new Promise((resolve) => { + readline.question( + chalk.cyan('Generate complexity report? (y/n): '), + resolve + ); + }); + readline.close(); - if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") { - // Call the analyze-complexity command - console.log(chalk.blue("Generating complexity report...")); - const tasksPath = TASKMASTER_TASKS_FILE; - if (!fs.existsSync(tasksPath)) { - console.error( - 'โŒ No tasks.json file found. Please run "task-master init" or create a tasks.json file.' - ); - return null; - } + if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') { + // Call the analyze-complexity command + console.log(chalk.blue('Generating complexity report...')); + const tasksPath = TASKMASTER_TASKS_FILE; + if (!fs.existsSync(tasksPath)) { + console.error( + 'โŒ No tasks.json file found. Please run "task-master init" or create a tasks.json file.' + ); + return null; + } - await analyzeTaskComplexity({ - output: reportPath, - research: false, // Default to no research for speed - file: tasksPath, - }); - // Read the newly generated report - return displayComplexityReport(reportPath); - } else { - console.log(chalk.yellow("Report generation cancelled.")); - return; - } - } + await analyzeTaskComplexity({ + output: reportPath, + research: false, // Default to no research for speed + file: tasksPath + }); + // Read the newly generated report + return displayComplexityReport(reportPath); + } else { + console.log(chalk.yellow('Report generation cancelled.')); + return; + } + } - // Read the report - let report; - try { - report = JSON.parse(fs.readFileSync(reportPath, "utf8")); - } catch (error) { - log("error", `Error reading complexity report: ${error.message}`); - return; - } + // Read the report + let report; + try { + report = JSON.parse(fs.readFileSync(reportPath, 'utf8')); + } catch (error) { + log('error', `Error reading complexity report: ${error.message}`); + return; + } - // Display report header - console.log( - boxen(chalk.white.bold("Task Complexity Analysis Report"), { - padding: 1, - borderColor: "blue", - borderStyle: "round", - margin: { top: 1, bottom: 1 }, - }) - ); + // Display report header + console.log( + boxen(chalk.white.bold('Task Complexity Analysis Report'), { + padding: 1, + borderColor: 'blue', + borderStyle: 'round', + margin: { top: 1, bottom: 1 } + }) + ); - // Display metadata - const metaTable = new Table({ - style: { - head: [], - border: [], - "padding-top": 0, - "padding-bottom": 0, - compact: true, - }, - chars: { - mid: "", - "left-mid": "", - "mid-mid": "", - "right-mid": "", - }, - colWidths: [20, 50], - }); + // Display metadata + const metaTable = new Table({ + style: { + head: [], + border: [], + 'padding-top': 0, + 'padding-bottom': 0, + compact: true + }, + chars: { + mid: '', + 'left-mid': '', + 'mid-mid': '', + 'right-mid': '' + }, + colWidths: [20, 50] + }); - metaTable.push( - [ - chalk.cyan.bold("Generated:"), - new Date(report.meta.generatedAt).toLocaleString(), - ], - [chalk.cyan.bold("Tasks Analyzed:"), report.meta.tasksAnalyzed], - [chalk.cyan.bold("Threshold Score:"), report.meta.thresholdScore], - [chalk.cyan.bold("Project:"), report.meta.projectName], - [ - chalk.cyan.bold("Research-backed:"), - report.meta.usedResearch ? "Yes" : "No", - ] - ); + metaTable.push( + [ + chalk.cyan.bold('Generated:'), + new Date(report.meta.generatedAt).toLocaleString() + ], + [chalk.cyan.bold('Tasks Analyzed:'), report.meta.tasksAnalyzed], + [chalk.cyan.bold('Threshold Score:'), report.meta.thresholdScore], + [chalk.cyan.bold('Project:'), report.meta.projectName], + [ + chalk.cyan.bold('Research-backed:'), + report.meta.usedResearch ? 'Yes' : 'No' + ] + ); - console.log(metaTable.toString()); + console.log(metaTable.toString()); - // Sort tasks by complexity score (highest first) - const sortedTasks = [...report.complexityAnalysis].sort( - (a, b) => b.complexityScore - a.complexityScore - ); + // Sort tasks by complexity score (highest first) + const sortedTasks = [...report.complexityAnalysis].sort( + (a, b) => b.complexityScore - a.complexityScore + ); - // Determine which tasks need expansion based on threshold - const tasksNeedingExpansion = sortedTasks.filter( - (task) => task.complexityScore >= report.meta.thresholdScore - ); - const simpleTasks = sortedTasks.filter( - (task) => task.complexityScore < report.meta.thresholdScore - ); + // Determine which tasks need expansion based on threshold + const tasksNeedingExpansion = sortedTasks.filter( + (task) => task.complexityScore >= report.meta.thresholdScore + ); + const simpleTasks = sortedTasks.filter( + (task) => task.complexityScore < report.meta.thresholdScore + ); - // Create progress bar to show complexity distribution - const complexityDistribution = [0, 0, 0]; // Low (0-4), Medium (5-7), High (8-10) - sortedTasks.forEach((task) => { - if (task.complexityScore < 5) complexityDistribution[0]++; - else if (task.complexityScore < 8) complexityDistribution[1]++; - else complexityDistribution[2]++; - }); + // Create progress bar to show complexity distribution + const complexityDistribution = [0, 0, 0]; // Low (0-4), Medium (5-7), High (8-10) + sortedTasks.forEach((task) => { + if (task.complexityScore < 5) complexityDistribution[0]++; + else if (task.complexityScore < 8) complexityDistribution[1]++; + else complexityDistribution[2]++; + }); - const percentLow = Math.round( - (complexityDistribution[0] / sortedTasks.length) * 100 - ); - const percentMedium = Math.round( - (complexityDistribution[1] / sortedTasks.length) * 100 - ); - const percentHigh = Math.round( - (complexityDistribution[2] / sortedTasks.length) * 100 - ); + const percentLow = Math.round( + (complexityDistribution[0] / sortedTasks.length) * 100 + ); + const percentMedium = Math.round( + (complexityDistribution[1] / sortedTasks.length) * 100 + ); + const percentHigh = Math.round( + (complexityDistribution[2] / sortedTasks.length) * 100 + ); - console.log( - boxen( - chalk.white.bold("Complexity Distribution\n\n") + - `${chalk.green.bold("Low (1-4):")} ${complexityDistribution[0]} tasks (${percentLow}%)\n` + - `${chalk.yellow.bold("Medium (5-7):")} ${complexityDistribution[1]} tasks (${percentMedium}%)\n` + - `${chalk.red.bold("High (8-10):")} ${complexityDistribution[2]} tasks (${percentHigh}%)`, - { - padding: 1, - borderColor: "cyan", - borderStyle: "round", - margin: { top: 1, bottom: 1 }, - } - ) - ); + console.log( + boxen( + chalk.white.bold('Complexity Distribution\n\n') + + `${chalk.green.bold('Low (1-4):')} ${complexityDistribution[0]} tasks (${percentLow}%)\n` + + `${chalk.yellow.bold('Medium (5-7):')} ${complexityDistribution[1]} tasks (${percentMedium}%)\n` + + `${chalk.red.bold('High (8-10):')} ${complexityDistribution[2]} tasks (${percentHigh}%)`, + { + padding: 1, + borderColor: 'cyan', + borderStyle: 'round', + margin: { top: 1, bottom: 1 } + } + ) + ); - // Get terminal width - const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect + // Get terminal width + const terminalWidth = process.stdout.columns || 100; // Default to 100 if can't detect - // Calculate dynamic column widths - const idWidth = 12; - const titleWidth = Math.floor(terminalWidth * 0.25); // 25% of width - const scoreWidth = 8; - const subtasksWidth = 8; - // Command column gets the remaining space (minus some buffer for borders) - const commandWidth = - terminalWidth - idWidth - titleWidth - scoreWidth - subtasksWidth - 10; + // Calculate dynamic column widths + const idWidth = 12; + const titleWidth = Math.floor(terminalWidth * 0.25); // 25% of width + const scoreWidth = 8; + const subtasksWidth = 8; + // Command column gets the remaining space (minus some buffer for borders) + const commandWidth = + terminalWidth - idWidth - titleWidth - scoreWidth - subtasksWidth - 10; - // Create table with new column widths and word wrapping - const complexTable = new Table({ - head: [ - chalk.yellow.bold("ID"), - chalk.yellow.bold("Title"), - chalk.yellow.bold("Score"), - chalk.yellow.bold("Subtasks"), - chalk.yellow.bold("Expansion Command"), - ], - colWidths: [idWidth, titleWidth, scoreWidth, subtasksWidth, commandWidth], - style: { head: [], border: [] }, - wordWrap: true, - wrapOnWordBoundary: true, - }); + // Create table with new column widths and word wrapping + const complexTable = new Table({ + head: [ + chalk.yellow.bold('ID'), + chalk.yellow.bold('Title'), + chalk.yellow.bold('Score'), + chalk.yellow.bold('Subtasks'), + chalk.yellow.bold('Expansion Command') + ], + colWidths: [idWidth, titleWidth, scoreWidth, subtasksWidth, commandWidth], + style: { head: [], border: [] }, + wordWrap: true, + wrapOnWordBoundary: true + }); - // When adding rows, don't truncate the expansion command - tasksNeedingExpansion.forEach((task) => { - const expansionCommand = `task-master expand --id=${task.taskId} --num=${task.recommendedSubtasks}${task.expansionPrompt ? ` --prompt="${task.expansionPrompt}"` : ""}`; + // When adding rows, don't truncate the expansion command + tasksNeedingExpansion.forEach((task) => { + const expansionCommand = `task-master expand --id=${task.taskId} --num=${task.recommendedSubtasks}${task.expansionPrompt ? ` --prompt="${task.expansionPrompt}"` : ''}`; - complexTable.push([ - task.taskId, - truncate(task.taskTitle, titleWidth - 3), // Still truncate title for readability - getComplexityWithColor(task.complexityScore), - task.recommendedSubtasks, - chalk.cyan(expansionCommand), // Don't truncate - allow wrapping - ]); - }); + complexTable.push([ + task.taskId, + truncate(task.taskTitle, titleWidth - 3), // Still truncate title for readability + getComplexityWithColor(task.complexityScore), + task.recommendedSubtasks, + chalk.cyan(expansionCommand) // Don't truncate - allow wrapping + ]); + }); - console.log(complexTable.toString()); + console.log(complexTable.toString()); - // Create table for simple tasks - if (simpleTasks.length > 0) { - console.log( - boxen(chalk.green.bold(`Simple Tasks (${simpleTasks.length})`), { - padding: { left: 2, right: 2, top: 0, bottom: 0 }, - margin: { top: 1, bottom: 0 }, - borderColor: "green", - borderStyle: "round", - }) - ); + // Create table for simple tasks + if (simpleTasks.length > 0) { + console.log( + boxen(chalk.green.bold(`Simple Tasks (${simpleTasks.length})`), { + padding: { left: 2, right: 2, top: 0, bottom: 0 }, + margin: { top: 1, bottom: 0 }, + borderColor: 'green', + borderStyle: 'round' + }) + ); - const simpleTable = new Table({ - head: [ - chalk.green.bold("ID"), - chalk.green.bold("Title"), - chalk.green.bold("Score"), - chalk.green.bold("Reasoning"), - ], - colWidths: [5, 40, 8, 50], - style: { head: [], border: [] }, - }); + const simpleTable = new Table({ + head: [ + chalk.green.bold('ID'), + chalk.green.bold('Title'), + chalk.green.bold('Score'), + chalk.green.bold('Reasoning') + ], + colWidths: [5, 40, 8, 50], + style: { head: [], border: [] } + }); - simpleTasks.forEach((task) => { - simpleTable.push([ - task.taskId, - truncate(task.taskTitle, 37), - getComplexityWithColor(task.complexityScore), - truncate(task.reasoning, 47), - ]); - }); + simpleTasks.forEach((task) => { + simpleTable.push([ + task.taskId, + truncate(task.taskTitle, 37), + getComplexityWithColor(task.complexityScore), + truncate(task.reasoning, 47) + ]); + }); - console.log(simpleTable.toString()); - } + console.log(simpleTable.toString()); + } - // Show action suggestions - console.log( - boxen( - chalk.white.bold("Suggested Actions:") + - "\n\n" + - `${chalk.cyan("1.")} Expand all complex tasks: ${chalk.yellow(`task-master expand --all`)}\n` + - `${chalk.cyan("2.")} Expand a specific task: ${chalk.yellow(`task-master expand --id=<id>`)}\n` + - `${chalk.cyan("3.")} Regenerate with research: ${chalk.yellow(`task-master analyze-complexity --research`)}`, - { - padding: 1, - borderColor: "cyan", - borderStyle: "round", - margin: { top: 1 }, - } - ) - ); + // Show action suggestions + console.log( + boxen( + chalk.white.bold('Suggested Actions:') + + '\n\n' + + `${chalk.cyan('1.')} Expand all complex tasks: ${chalk.yellow(`task-master expand --all`)}\n` + + `${chalk.cyan('2.')} Expand a specific task: ${chalk.yellow(`task-master expand --id=<id>`)}\n` + + `${chalk.cyan('3.')} Regenerate with research: ${chalk.yellow(`task-master analyze-complexity --research`)}`, + { + padding: 1, + borderColor: 'cyan', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); } /** @@ -1802,21 +1802,21 @@ async function displayComplexityReport(reportPath) { * @returns {string} Generated prompt */ function generateComplexityAnalysisPrompt(tasksData) { - const defaultSubtasks = getDefaultSubtasks(null); // Use the getter - return `Analyze the complexity of the following tasks and provide recommendations for subtask breakdown: + const defaultSubtasks = getDefaultSubtasks(null); // Use the getter + return `Analyze the complexity of the following tasks and provide recommendations for subtask breakdown: ${tasksData.tasks - .map( - (task) => ` + .map( + (task) => ` Task ID: ${task.id} Title: ${task.title} Description: ${task.description} Details: ${task.details} Dependencies: ${JSON.stringify(task.dependencies || [])} -Priority: ${task.priority || "medium"} +Priority: ${task.priority || 'medium'} ` - ) - .join("\n---\n")} + ) + .join('\n---\n')} Analyze each task and return a JSON array with the following structure for each task: [ @@ -1841,39 +1841,39 @@ IMPORTANT: Make sure to include an analysis for EVERY task listed above, with th * @returns {Promise<boolean>} - Promise resolving to true if user confirms, false otherwise */ async function confirmTaskOverwrite(tasksPath) { - console.log( - boxen( - chalk.yellow( - "It looks like you've already generated tasks for this project.\n" - ) + - chalk.yellow( - "Executing this command will overwrite any existing tasks." - ), - { - padding: 1, - borderColor: "yellow", - borderStyle: "round", - margin: { top: 1 }, - } - ) - ); + console.log( + boxen( + chalk.yellow( + "It looks like you've already generated tasks for this project.\n" + ) + + chalk.yellow( + 'Executing this command will overwrite any existing tasks.' + ), + { + padding: 1, + borderColor: 'yellow', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); - // Use dynamic import to get the readline module - const readline = await import("readline"); - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - }); + // Use dynamic import to get the readline module + const readline = await import('readline'); + const rl = readline.createInterface({ + input: process.stdin, + output: process.stdout + }); - const answer = await new Promise((resolve) => { - rl.question( - chalk.cyan("Are you sure you wish to continue? (y/N): "), - resolve - ); - }); - rl.close(); + const answer = await new Promise((resolve) => { + rl.question( + chalk.cyan('Are you sure you wish to continue? (y/N): '), + resolve + ); + }); + rl.close(); - return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes"; + return answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes'; } /** @@ -1881,75 +1881,75 @@ async function confirmTaskOverwrite(tasksPath) { * @param {Array<{provider: string, cli: boolean, mcp: boolean}>} statusReport - The report generated by getApiKeyStatusReport. */ function displayApiKeyStatus(statusReport) { - if (!statusReport || statusReport.length === 0) { - console.log(chalk.yellow("No API key status information available.")); - return; - } + if (!statusReport || statusReport.length === 0) { + console.log(chalk.yellow('No API key status information available.')); + return; + } - const table = new Table({ - head: [ - chalk.cyan("Provider"), - chalk.cyan("CLI Key (.env)"), - chalk.cyan("MCP Key (mcp.json)"), - ], - colWidths: [15, 20, 25], - chars: { mid: "", "left-mid": "", "mid-mid": "", "right-mid": "" }, - }); + const table = new Table({ + head: [ + chalk.cyan('Provider'), + chalk.cyan('CLI Key (.env)'), + chalk.cyan('MCP Key (mcp.json)') + ], + colWidths: [15, 20, 25], + chars: { mid: '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' } + }); - statusReport.forEach(({ provider, cli, mcp }) => { - const cliStatus = cli ? chalk.green("โœ… Found") : chalk.red("โŒ Missing"); - const mcpStatus = mcp ? chalk.green("โœ… Found") : chalk.red("โŒ Missing"); - // Capitalize provider name for display - const providerName = provider.charAt(0).toUpperCase() + provider.slice(1); - table.push([providerName, cliStatus, mcpStatus]); - }); + statusReport.forEach(({ provider, cli, mcp }) => { + const cliStatus = cli ? chalk.green('โœ… Found') : chalk.red('โŒ Missing'); + const mcpStatus = mcp ? chalk.green('โœ… Found') : chalk.red('โŒ Missing'); + // Capitalize provider name for display + const providerName = provider.charAt(0).toUpperCase() + provider.slice(1); + table.push([providerName, cliStatus, mcpStatus]); + }); - console.log(chalk.bold("\n๐Ÿ”‘ API Key Status:")); - console.log(table.toString()); - console.log( - chalk.gray( - ` Note: Some providers (e.g., Azure, Ollama) may require additional endpoint configuration in ${TASKMASTER_CONFIG_FILE}.` - ) - ); + console.log(chalk.bold('\n๐Ÿ”‘ API Key Status:')); + console.log(table.toString()); + console.log( + chalk.gray( + ` Note: Some providers (e.g., Azure, Ollama) may require additional endpoint configuration in ${TASKMASTER_CONFIG_FILE}.` + ) + ); } // --- Formatting Helpers (Potentially move some to utils.js if reusable) --- const formatSweScoreWithTertileStars = (score, allModels) => { - // ... (Implementation from previous version or refine) ... - if (score === null || score === undefined || score <= 0) return "N/A"; - const formattedPercentage = `${(score * 100).toFixed(1)}%`; + // ... (Implementation from previous version or refine) ... + if (score === null || score === undefined || score <= 0) return 'N/A'; + const formattedPercentage = `${(score * 100).toFixed(1)}%`; - const validScores = allModels - .map((m) => m.sweScore) - .filter((s) => s !== null && s !== undefined && s > 0); - const sortedScores = [...validScores].sort((a, b) => b - a); - const n = sortedScores.length; - let stars = chalk.gray("โ˜†โ˜†โ˜†"); + const validScores = allModels + .map((m) => m.sweScore) + .filter((s) => s !== null && s !== undefined && s > 0); + const sortedScores = [...validScores].sort((a, b) => b - a); + const n = sortedScores.length; + let stars = chalk.gray('โ˜†โ˜†โ˜†'); - if (n > 0) { - const topThirdIndex = Math.max(0, Math.floor(n / 3) - 1); - const midThirdIndex = Math.max(0, Math.floor((2 * n) / 3) - 1); - if (score >= sortedScores[topThirdIndex]) stars = chalk.yellow("โ˜…โ˜…โ˜…"); - else if (score >= sortedScores[midThirdIndex]) - stars = chalk.yellow("โ˜…โ˜…") + chalk.gray("โ˜†"); - else stars = chalk.yellow("โ˜…") + chalk.gray("โ˜†โ˜†"); - } - return `${formattedPercentage} ${stars}`; + if (n > 0) { + const topThirdIndex = Math.max(0, Math.floor(n / 3) - 1); + const midThirdIndex = Math.max(0, Math.floor((2 * n) / 3) - 1); + if (score >= sortedScores[topThirdIndex]) stars = chalk.yellow('โ˜…โ˜…โ˜…'); + else if (score >= sortedScores[midThirdIndex]) + stars = chalk.yellow('โ˜…โ˜…') + chalk.gray('โ˜†'); + else stars = chalk.yellow('โ˜…') + chalk.gray('โ˜†โ˜†'); + } + return `${formattedPercentage} ${stars}`; }; const formatCost = (costObj) => { - // ... (Implementation from previous version or refine) ... - if (!costObj) return "N/A"; - if (costObj.input === 0 && costObj.output === 0) { - return chalk.green("Free"); - } - const formatSingleCost = (costValue) => { - if (costValue === null || costValue === undefined) return "N/A"; - const isInteger = Number.isInteger(costValue); - return `$${costValue.toFixed(isInteger ? 0 : 2)}`; - }; - return `${formatSingleCost(costObj.input)} in, ${formatSingleCost(costObj.output)} out`; + // ... (Implementation from previous version or refine) ... + if (!costObj) return 'N/A'; + if (costObj.input === 0 && costObj.output === 0) { + return chalk.green('Free'); + } + const formatSingleCost = (costValue) => { + if (costValue === null || costValue === undefined) return 'N/A'; + const isInteger = Number.isInteger(costValue); + return `$${costValue.toFixed(isInteger ? 0 : 2)}`; + }; + return `${formatSingleCost(costObj.input)} in, ${formatSingleCost(costObj.output)} out`; }; // --- Display Functions --- @@ -1960,63 +1960,63 @@ const formatCost = (costObj) => { * @param {AvailableModel[]} allAvailableModels - Needed for SWE score tertiles. */ function displayModelConfiguration(configData, allAvailableModels = []) { - console.log(chalk.cyan.bold("\nActive Model Configuration:")); - const active = configData.activeModels; - const activeTable = new Table({ - head: [ - "Role", - "Provider", - "Model ID", - "SWE Score", - "Cost ($/1M tkns)", - // 'API Key Status' // Removed, handled by separate displayApiKeyStatus - ].map((h) => chalk.cyan.bold(h)), - colWidths: [10, 14, 30, 18, 20 /*, 28 */], // Adjusted widths - style: { head: ["cyan", "bold"] }, - }); + console.log(chalk.cyan.bold('\nActive Model Configuration:')); + const active = configData.activeModels; + const activeTable = new Table({ + head: [ + 'Role', + 'Provider', + 'Model ID', + 'SWE Score', + 'Cost ($/1M tkns)' + // 'API Key Status' // Removed, handled by separate displayApiKeyStatus + ].map((h) => chalk.cyan.bold(h)), + colWidths: [10, 14, 30, 18, 20 /*, 28 */], // Adjusted widths + style: { head: ['cyan', 'bold'] } + }); - activeTable.push([ - chalk.white("Main"), - active.main.provider, - active.main.modelId, - formatSweScoreWithTertileStars(active.main.sweScore, allAvailableModels), - formatCost(active.main.cost), - // getCombinedStatus(active.main.keyStatus) // Removed - ]); - activeTable.push([ - chalk.white("Research"), - active.research.provider, - active.research.modelId, - formatSweScoreWithTertileStars( - active.research.sweScore, - allAvailableModels - ), - formatCost(active.research.cost), - // getCombinedStatus(active.research.keyStatus) // Removed - ]); - if (active.fallback && active.fallback.provider && active.fallback.modelId) { - activeTable.push([ - chalk.white("Fallback"), - active.fallback.provider, - active.fallback.modelId, - formatSweScoreWithTertileStars( - active.fallback.sweScore, - allAvailableModels - ), - formatCost(active.fallback.cost), - // getCombinedStatus(active.fallback.keyStatus) // Removed - ]); - } else { - activeTable.push([ - chalk.white("Fallback"), - chalk.gray("-"), - chalk.gray("(Not Set)"), - chalk.gray("-"), - chalk.gray("-"), - // chalk.gray('-') // Removed - ]); - } - console.log(activeTable.toString()); + activeTable.push([ + chalk.white('Main'), + active.main.provider, + active.main.modelId, + formatSweScoreWithTertileStars(active.main.sweScore, allAvailableModels), + formatCost(active.main.cost) + // getCombinedStatus(active.main.keyStatus) // Removed + ]); + activeTable.push([ + chalk.white('Research'), + active.research.provider, + active.research.modelId, + formatSweScoreWithTertileStars( + active.research.sweScore, + allAvailableModels + ), + formatCost(active.research.cost) + // getCombinedStatus(active.research.keyStatus) // Removed + ]); + if (active.fallback && active.fallback.provider && active.fallback.modelId) { + activeTable.push([ + chalk.white('Fallback'), + active.fallback.provider, + active.fallback.modelId, + formatSweScoreWithTertileStars( + active.fallback.sweScore, + allAvailableModels + ), + formatCost(active.fallback.cost) + // getCombinedStatus(active.fallback.keyStatus) // Removed + ]); + } else { + activeTable.push([ + chalk.white('Fallback'), + chalk.gray('-'), + chalk.gray('(Not Set)'), + chalk.gray('-'), + chalk.gray('-') + // chalk.gray('-') // Removed + ]); + } + console.log(activeTable.toString()); } /** @@ -2024,64 +2024,64 @@ function displayModelConfiguration(configData, allAvailableModels = []) { * @param {AvailableModel[]} availableModels - List of available models. */ function displayAvailableModels(availableModels) { - if (!availableModels || availableModels.length === 0) { - console.log( - chalk.gray("\n(No other models available or all are configured)") - ); - return; - } + if (!availableModels || availableModels.length === 0) { + console.log( + chalk.gray('\n(No other models available or all are configured)') + ); + return; + } - console.log(chalk.cyan.bold("\nOther Available Models:")); - const availableTable = new Table({ - head: ["Provider", "Model ID", "SWE Score", "Cost ($/1M tkns)"].map((h) => - chalk.cyan.bold(h) - ), - colWidths: [15, 40, 18, 25], - style: { head: ["cyan", "bold"] }, - }); + console.log(chalk.cyan.bold('\nOther Available Models:')); + const availableTable = new Table({ + head: ['Provider', 'Model ID', 'SWE Score', 'Cost ($/1M tkns)'].map((h) => + chalk.cyan.bold(h) + ), + colWidths: [15, 40, 18, 25], + style: { head: ['cyan', 'bold'] } + }); - availableModels.forEach((model) => { - availableTable.push([ - model.provider, - model.modelId, - formatSweScoreWithTertileStars(model.sweScore, availableModels), // Pass itself for comparison - formatCost(model.cost), - ]); - }); - console.log(availableTable.toString()); + availableModels.forEach((model) => { + availableTable.push([ + model.provider, + model.modelId, + formatSweScoreWithTertileStars(model.sweScore, availableModels), // Pass itself for comparison + formatCost(model.cost) + ]); + }); + console.log(availableTable.toString()); - // --- Suggested Actions Section (moved here from models command) --- - console.log( - boxen( - chalk.white.bold("Next Steps:") + - "\n" + - chalk.cyan( - `1. Set main model: ${chalk.yellow("task-master models --set-main <model_id>")}` - ) + - "\n" + - chalk.cyan( - `2. Set research model: ${chalk.yellow("task-master models --set-research <model_id>")}` - ) + - "\n" + - chalk.cyan( - `3. Set fallback model: ${chalk.yellow("task-master models --set-fallback <model_id>")}` - ) + - "\n" + - chalk.cyan( - `4. Run interactive setup: ${chalk.yellow("task-master models --setup")}` - ) + - "\n" + - chalk.cyan( - `5. Use custom ollama/openrouter models: ${chalk.yellow("task-master models --openrouter|ollama --set-main|research|fallback <model_id>")}` - ), - { - padding: 1, - borderColor: "yellow", - borderStyle: "round", - margin: { top: 1 }, - } - ) - ); + // --- Suggested Actions Section (moved here from models command) --- + console.log( + boxen( + chalk.white.bold('Next Steps:') + + '\n' + + chalk.cyan( + `1. Set main model: ${chalk.yellow('task-master models --set-main <model_id>')}` + ) + + '\n' + + chalk.cyan( + `2. Set research model: ${chalk.yellow('task-master models --set-research <model_id>')}` + ) + + '\n' + + chalk.cyan( + `3. Set fallback model: ${chalk.yellow('task-master models --set-fallback <model_id>')}` + ) + + '\n' + + chalk.cyan( + `4. Run interactive setup: ${chalk.yellow('task-master models --setup')}` + ) + + '\n' + + chalk.cyan( + `5. Use custom ollama/openrouter models: ${chalk.yellow('task-master models --openrouter|ollama --set-main|research|fallback <model_id>')}` + ), + { + padding: 1, + borderColor: 'yellow', + borderStyle: 'round', + margin: { top: 1 } + } + ) + ); } /** @@ -2089,67 +2089,67 @@ function displayAvailableModels(availableModels) { * @param {object} telemetryData - The telemetry data object. * @param {string} outputType - 'cli' or 'mcp' (though typically only called for 'cli'). */ -function displayAiUsageSummary(telemetryData, outputType = "cli") { - if ( - (outputType !== "cli" && outputType !== "text") || - !telemetryData || - isSilentMode() - ) { - return; // Only display for CLI and if data exists and not in silent mode - } +function displayAiUsageSummary(telemetryData, outputType = 'cli') { + if ( + (outputType !== 'cli' && outputType !== 'text') || + !telemetryData || + isSilentMode() + ) { + return; // Only display for CLI and if data exists and not in silent mode + } - const { - modelUsed, - providerName, - inputTokens, - outputTokens, - totalTokens, - totalCost, - commandName, - } = telemetryData; + const { + modelUsed, + providerName, + inputTokens, + outputTokens, + totalTokens, + totalCost, + commandName + } = telemetryData; - let summary = chalk.bold.blue("AI Usage Summary:") + "\n"; - summary += chalk.gray(` Command: ${commandName}\n`); - summary += chalk.gray(` Provider: ${providerName}\n`); - summary += chalk.gray(` Model: ${modelUsed}\n`); - summary += chalk.gray( - ` Tokens: ${totalTokens} (Input: ${inputTokens}, Output: ${outputTokens})\n` - ); - summary += chalk.gray(` Est. Cost: $${totalCost.toFixed(6)}`); + let summary = chalk.bold.blue('AI Usage Summary:') + '\n'; + summary += chalk.gray(` Command: ${commandName}\n`); + summary += chalk.gray(` Provider: ${providerName}\n`); + summary += chalk.gray(` Model: ${modelUsed}\n`); + summary += chalk.gray( + ` Tokens: ${totalTokens} (Input: ${inputTokens}, Output: ${outputTokens})\n` + ); + summary += chalk.gray(` Est. Cost: $${totalCost.toFixed(6)}`); - console.log( - boxen(summary, { - padding: 1, - margin: { top: 1 }, - borderColor: "blue", - borderStyle: "round", - title: "๐Ÿ’ก Telemetry", - titleAlignment: "center", - }) - ); + console.log( + boxen(summary, { + padding: 1, + margin: { top: 1 }, + borderColor: 'blue', + borderStyle: 'round', + title: '๐Ÿ’ก Telemetry', + titleAlignment: 'center' + }) + ); } // Export UI functions export { - displayBanner, - startLoadingIndicator, - stopLoadingIndicator, - createProgressBar, - getStatusWithColor, - formatDependenciesWithStatus, - displayHelp, - getComplexityWithColor, - displayNextTask, - displayTaskById, - displayComplexityReport, - generateComplexityAnalysisPrompt, - confirmTaskOverwrite, - displayApiKeyStatus, - displayModelConfiguration, - displayAvailableModels, - displayAiUsageSummary, - succeedLoadingIndicator, - failLoadingIndicator, - warnLoadingIndicator, - infoLoadingIndicator, + displayBanner, + startLoadingIndicator, + stopLoadingIndicator, + createProgressBar, + getStatusWithColor, + formatDependenciesWithStatus, + displayHelp, + getComplexityWithColor, + displayNextTask, + displayTaskById, + displayComplexityReport, + generateComplexityAnalysisPrompt, + confirmTaskOverwrite, + displayApiKeyStatus, + displayModelConfiguration, + displayAvailableModels, + displayAiUsageSummary, + succeedLoadingIndicator, + failLoadingIndicator, + warnLoadingIndicator, + infoLoadingIndicator }; diff --git a/src/ai-providers/base-provider.js b/src/ai-providers/base-provider.js index b96561d7..c0ad3c05 100644 --- a/src/ai-providers/base-provider.js +++ b/src/ai-providers/base-provider.js @@ -1,214 +1,214 @@ -import { generateText, streamText, generateObject } from "ai"; -import { log } from "../../scripts/modules/index.js"; +import { generateText, streamText, generateObject } from 'ai'; +import { log } from '../../scripts/modules/index.js'; /** * Base class for all AI providers */ export class BaseAIProvider { - constructor() { - if (this.constructor === BaseAIProvider) { - throw new Error("BaseAIProvider cannot be instantiated directly"); - } + constructor() { + if (this.constructor === BaseAIProvider) { + throw new Error('BaseAIProvider cannot be instantiated directly'); + } - // Each provider must set their name - this.name = this.constructor.name; - } + // Each provider must set their name + this.name = this.constructor.name; + } - /** - * Validates authentication parameters - can be overridden by providers - * @param {object} params - Parameters to validate - */ - validateAuth(params) { - // Default: require API key (most providers need this) - if (!params.apiKey) { - throw new Error(`${this.name} API key is required`); - } - } + /** + * Validates authentication parameters - can be overridden by providers + * @param {object} params - Parameters to validate + */ + validateAuth(params) { + // Default: require API key (most providers need this) + if (!params.apiKey) { + throw new Error(`${this.name} API key is required`); + } + } - /** - * Validates common parameters across all methods - * @param {object} params - Parameters to validate - */ - validateParams(params) { - // Validate authentication (can be overridden by providers) - this.validateAuth(params); + /** + * Validates common parameters across all methods + * @param {object} params - Parameters to validate + */ + validateParams(params) { + // Validate authentication (can be overridden by providers) + this.validateAuth(params); - // Validate required model ID - if (!params.modelId) { - throw new Error(`${this.name} Model ID is required`); - } + // Validate required model ID + if (!params.modelId) { + throw new Error(`${this.name} Model ID is required`); + } - // Validate optional parameters - this.validateOptionalParams(params); - } + // Validate optional parameters + this.validateOptionalParams(params); + } - /** - * Validates optional parameters like temperature and maxTokens - * @param {object} params - Parameters to validate - */ - validateOptionalParams(params) { - if ( - params.temperature !== undefined && - (params.temperature < 0 || params.temperature > 1) - ) { - throw new Error("Temperature must be between 0 and 1"); - } - if (params.maxTokens !== undefined && params.maxTokens <= 0) { - throw new Error("maxTokens must be greater than 0"); - } - } + /** + * Validates optional parameters like temperature and maxTokens + * @param {object} params - Parameters to validate + */ + validateOptionalParams(params) { + if ( + params.temperature !== undefined && + (params.temperature < 0 || params.temperature > 1) + ) { + throw new Error('Temperature must be between 0 and 1'); + } + if (params.maxTokens !== undefined && params.maxTokens <= 0) { + throw new Error('maxTokens must be greater than 0'); + } + } - /** - * Validates message array structure - */ - validateMessages(messages) { - if (!messages || !Array.isArray(messages) || messages.length === 0) { - throw new Error("Invalid or empty messages array provided"); - } + /** + * Validates message array structure + */ + validateMessages(messages) { + if (!messages || !Array.isArray(messages) || messages.length === 0) { + throw new Error('Invalid or empty messages array provided'); + } - for (const msg of messages) { - if (!msg.role || !msg.content) { - throw new Error( - "Invalid message format. Each message must have role and content" - ); - } - } - } + for (const msg of messages) { + if (!msg.role || !msg.content) { + throw new Error( + 'Invalid message format. Each message must have role and content' + ); + } + } + } - /** - * Common error handler - */ - handleError(operation, error) { - const errorMessage = error.message || "Unknown error occurred"; - log("error", `${this.name} ${operation} failed: ${errorMessage}`, { - error, - }); - throw new Error( - `${this.name} API error during ${operation}: ${errorMessage}` - ); - } + /** + * Common error handler + */ + handleError(operation, error) { + const errorMessage = error.message || 'Unknown error occurred'; + log('error', `${this.name} ${operation} failed: ${errorMessage}`, { + error + }); + throw new Error( + `${this.name} API error during ${operation}: ${errorMessage}` + ); + } - /** - * Creates and returns a client instance for the provider - * @abstract - */ - getClient(params) { - throw new Error("getClient must be implemented by provider"); - } + /** + * Creates and returns a client instance for the provider + * @abstract + */ + getClient(params) { + throw new Error('getClient must be implemented by provider'); + } - /** - * Generates text using the provider's model - */ - async generateText(params) { - try { - this.validateParams(params); - this.validateMessages(params.messages); + /** + * Generates text using the provider's model + */ + async generateText(params) { + try { + this.validateParams(params); + this.validateMessages(params.messages); - log( - "debug", - `Generating ${this.name} text with model: ${params.modelId}` - ); + log( + 'debug', + `Generating ${this.name} text with model: ${params.modelId}` + ); - const client = this.getClient(params); - const result = await generateText({ - model: client(params.modelId), - messages: params.messages, - maxTokens: params.maxTokens, - temperature: params.temperature, - }); + const client = this.getClient(params); + const result = await generateText({ + model: client(params.modelId), + messages: params.messages, + maxTokens: params.maxTokens, + temperature: params.temperature + }); - log( - "debug", - `${this.name} generateText completed successfully for model: ${params.modelId}` - ); + log( + 'debug', + `${this.name} generateText completed successfully for model: ${params.modelId}` + ); - return { - text: result.text, - usage: { - inputTokens: result.usage?.promptTokens, - outputTokens: result.usage?.completionTokens, - totalTokens: result.usage?.totalTokens, - }, - }; - } catch (error) { - this.handleError("text generation", error); - } - } + return { + text: result.text, + usage: { + inputTokens: result.usage?.promptTokens, + outputTokens: result.usage?.completionTokens, + totalTokens: result.usage?.totalTokens + } + }; + } catch (error) { + this.handleError('text generation', error); + } + } - /** - * Streams text using the provider's model - */ - async streamText(params) { - try { - this.validateParams(params); - this.validateMessages(params.messages); + /** + * Streams text using the provider's model + */ + async streamText(params) { + try { + this.validateParams(params); + this.validateMessages(params.messages); - log("debug", `Streaming ${this.name} text with model: ${params.modelId}`); + log('debug', `Streaming ${this.name} text with model: ${params.modelId}`); - const client = this.getClient(params); - const stream = await streamText({ - model: client(params.modelId), - messages: params.messages, - maxTokens: params.maxTokens, - temperature: params.temperature, - }); + const client = this.getClient(params); + const stream = await streamText({ + model: client(params.modelId), + messages: params.messages, + maxTokens: params.maxTokens, + temperature: params.temperature + }); - log( - "debug", - `${this.name} streamText initiated successfully for model: ${params.modelId}` - ); + log( + 'debug', + `${this.name} streamText initiated successfully for model: ${params.modelId}` + ); - return stream; - } catch (error) { - this.handleError("text streaming", error); - } - } + return stream; + } catch (error) { + this.handleError('text streaming', error); + } + } - /** - * Generates a structured object using the provider's model - */ - async generateObject(params) { - try { - this.validateParams(params); - this.validateMessages(params.messages); + /** + * Generates a structured object using the provider's model + */ + async generateObject(params) { + try { + this.validateParams(params); + this.validateMessages(params.messages); - if (!params.schema) { - throw new Error("Schema is required for object generation"); - } - if (!params.objectName) { - throw new Error("Object name is required for object generation"); - } + if (!params.schema) { + throw new Error('Schema is required for object generation'); + } + if (!params.objectName) { + throw new Error('Object name is required for object generation'); + } - log( - "debug", - `Generating ${this.name} object ('${params.objectName}') with model: ${params.modelId}` - ); + log( + 'debug', + `Generating ${this.name} object ('${params.objectName}') with model: ${params.modelId}` + ); - const client = this.getClient(params); - const result = await generateObject({ - model: client(params.modelId), - messages: params.messages, - schema: params.schema, - mode: "auto", - maxTokens: params.maxTokens, - temperature: params.temperature, - }); + const client = this.getClient(params); + const result = await generateObject({ + model: client(params.modelId), + messages: params.messages, + schema: params.schema, + mode: 'auto', + maxTokens: params.maxTokens, + temperature: params.temperature + }); - log( - "debug", - `${this.name} generateObject completed successfully for model: ${params.modelId}` - ); + log( + 'debug', + `${this.name} generateObject completed successfully for model: ${params.modelId}` + ); - return { - object: result.object, - usage: { - inputTokens: result.usage?.promptTokens, - outputTokens: result.usage?.completionTokens, - totalTokens: result.usage?.totalTokens, - }, - }; - } catch (error) { - this.handleError("object generation", error); - } - } + return { + object: result.object, + usage: { + inputTokens: result.usage?.promptTokens, + outputTokens: result.usage?.completionTokens, + totalTokens: result.usage?.totalTokens + } + }; + } catch (error) { + this.handleError('object generation', error); + } + } } diff --git a/tests/unit/scripts/modules/task-manager/add-task.test.js b/tests/unit/scripts/modules/task-manager/add-task.test.js index 3a3e6288..9a31cf10 100644 --- a/tests/unit/scripts/modules/task-manager/add-task.test.js +++ b/tests/unit/scripts/modules/task-manager/add-task.test.js @@ -1,400 +1,404 @@ /** * Tests for the add-task.js module */ -import { jest } from '@jest/globals'; +import { jest } from "@jest/globals"; // Mock the dependencies before importing the module under test -jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({ - readJSON: jest.fn(), - writeJSON: jest.fn(), - log: jest.fn(), - CONFIG: { - model: 'mock-claude-model', - maxTokens: 4000, - temperature: 0.7, - debug: false - }, - truncate: jest.fn((text) => text) +jest.unstable_mockModule("../../../../../scripts/modules/utils.js", () => ({ + readJSON: jest.fn(), + writeJSON: jest.fn(), + log: jest.fn(), + CONFIG: { + model: "mock-claude-model", + maxTokens: 4000, + temperature: 0.7, + debug: false, + }, + truncate: jest.fn((text) => text), })); -jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({ - displayBanner: jest.fn(), - getStatusWithColor: jest.fn((status) => status), - startLoadingIndicator: jest.fn(), - stopLoadingIndicator: jest.fn(), - displayAiUsageSummary: jest.fn() +jest.unstable_mockModule("../../../../../scripts/modules/ui.js", () => ({ + displayBanner: jest.fn(), + getStatusWithColor: jest.fn((status) => status), + startLoadingIndicator: jest.fn(), + stopLoadingIndicator: jest.fn(), + succeedLoadingIndicator: jest.fn(), + failLoadingIndicator: jest.fn(), + warnLoadingIndicator: jest.fn(), + infoLoadingIndicator: jest.fn(), + displayAiUsageSummary: jest.fn(), })); jest.unstable_mockModule( - '../../../../../scripts/modules/ai-services-unified.js', - () => ({ - generateObjectService: jest.fn().mockResolvedValue({ - mainResult: { - object: { - title: 'Task from prompt: Create a new authentication system', - description: - 'Task generated from: Create a new authentication system', - details: - 'Implementation details for task generated from prompt: Create a new authentication system', - testStrategy: 'Write unit tests to verify functionality', - dependencies: [] - } - }, - telemetryData: { - timestamp: new Date().toISOString(), - userId: '1234567890', - commandName: 'add-task', - modelUsed: 'claude-3-5-sonnet', - providerName: 'anthropic', - inputTokens: 1000, - outputTokens: 500, - totalTokens: 1500, - totalCost: 0.012414, - currency: 'USD' - } - }) - }) + "../../../../../scripts/modules/ai-services-unified.js", + () => ({ + generateObjectService: jest.fn().mockResolvedValue({ + mainResult: { + object: { + title: "Task from prompt: Create a new authentication system", + description: + "Task generated from: Create a new authentication system", + details: + "Implementation details for task generated from prompt: Create a new authentication system", + testStrategy: "Write unit tests to verify functionality", + dependencies: [], + }, + }, + telemetryData: { + timestamp: new Date().toISOString(), + userId: "1234567890", + commandName: "add-task", + modelUsed: "claude-3-5-sonnet", + providerName: "anthropic", + inputTokens: 1000, + outputTokens: 500, + totalTokens: 1500, + totalCost: 0.012414, + currency: "USD", + }, + }), + }) ); jest.unstable_mockModule( - '../../../../../scripts/modules/config-manager.js', - () => ({ - getDefaultPriority: jest.fn(() => 'medium') - }) + "../../../../../scripts/modules/config-manager.js", + () => ({ + getDefaultPriority: jest.fn(() => "medium"), + }) ); jest.unstable_mockModule( - '../../../../../scripts/modules/task-manager/generate-task-files.js', - () => ({ - default: jest.fn().mockResolvedValue() - }) + "../../../../../scripts/modules/task-manager/generate-task-files.js", + () => ({ + default: jest.fn().mockResolvedValue(), + }) ); // Mock external UI libraries -jest.unstable_mockModule('chalk', () => ({ - default: { - white: { bold: jest.fn((text) => text) }, - cyan: Object.assign( - jest.fn((text) => text), - { - bold: jest.fn((text) => text) - } - ), - green: jest.fn((text) => text), - yellow: jest.fn((text) => text), - bold: jest.fn((text) => text) - } +jest.unstable_mockModule("chalk", () => ({ + default: { + white: { bold: jest.fn((text) => text) }, + cyan: Object.assign( + jest.fn((text) => text), + { + bold: jest.fn((text) => text), + } + ), + green: jest.fn((text) => text), + yellow: jest.fn((text) => text), + bold: jest.fn((text) => text), + }, })); -jest.unstable_mockModule('boxen', () => ({ - default: jest.fn((text) => text) +jest.unstable_mockModule("boxen", () => ({ + default: jest.fn((text) => text), })); -jest.unstable_mockModule('cli-table3', () => ({ - default: jest.fn().mockImplementation(() => ({ - push: jest.fn(), - toString: jest.fn(() => 'mocked table') - })) +jest.unstable_mockModule("cli-table3", () => ({ + default: jest.fn().mockImplementation(() => ({ + push: jest.fn(), + toString: jest.fn(() => "mocked table"), + })), })); // Import the mocked modules const { readJSON, writeJSON, log } = await import( - '../../../../../scripts/modules/utils.js' + "../../../../../scripts/modules/utils.js" ); const { generateObjectService } = await import( - '../../../../../scripts/modules/ai-services-unified.js' + "../../../../../scripts/modules/ai-services-unified.js" ); const generateTaskFiles = await import( - '../../../../../scripts/modules/task-manager/generate-task-files.js' + "../../../../../scripts/modules/task-manager/generate-task-files.js" ); // Import the module under test const { default: addTask } = await import( - '../../../../../scripts/modules/task-manager/add-task.js' + "../../../../../scripts/modules/task-manager/add-task.js" ); -describe('addTask', () => { - const sampleTasks = { - tasks: [ - { - id: 1, - title: 'Task 1', - description: 'First task', - status: 'pending', - dependencies: [] - }, - { - id: 2, - title: 'Task 2', - description: 'Second task', - status: 'pending', - dependencies: [] - }, - { - id: 3, - title: 'Task 3', - description: 'Third task', - status: 'pending', - dependencies: [1] - } - ] - }; +describe("addTask", () => { + const sampleTasks = { + tasks: [ + { + id: 1, + title: "Task 1", + description: "First task", + status: "pending", + dependencies: [], + }, + { + id: 2, + title: "Task 2", + description: "Second task", + status: "pending", + dependencies: [], + }, + { + id: 3, + title: "Task 3", + description: "Third task", + status: "pending", + dependencies: [1], + }, + ], + }; - // Create a helper function for consistent mcpLog mock - const createMcpLogMock = () => ({ - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - debug: jest.fn(), - success: jest.fn() - }); + // Create a helper function for consistent mcpLog mock + const createMcpLogMock = () => ({ + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + success: jest.fn(), + }); - beforeEach(() => { - jest.clearAllMocks(); - readJSON.mockReturnValue(JSON.parse(JSON.stringify(sampleTasks))); + beforeEach(() => { + jest.clearAllMocks(); + readJSON.mockReturnValue(JSON.parse(JSON.stringify(sampleTasks))); - // Mock console.log to avoid output during tests - jest.spyOn(console, 'log').mockImplementation(() => {}); - }); + // Mock console.log to avoid output during tests + jest.spyOn(console, "log").mockImplementation(() => {}); + }); - afterEach(() => { - console.log.mockRestore(); - }); + afterEach(() => { + console.log.mockRestore(); + }); - test('should add a new task using AI', async () => { - // Arrange - const prompt = 'Create a new authentication system'; - const context = { - mcpLog: createMcpLogMock() - }; + test("should add a new task using AI", async () => { + // Arrange + const prompt = "Create a new authentication system"; + const context = { + mcpLog: createMcpLogMock(), + }; - // Act - const result = await addTask( - 'tasks/tasks.json', - prompt, - [], - 'medium', - context, - 'json' - ); + // Act + const result = await addTask( + "tasks/tasks.json", + prompt, + [], + "medium", + context, + "json" + ); - // Assert - expect(readJSON).toHaveBeenCalledWith('tasks/tasks.json'); - expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object)); - expect(writeJSON).toHaveBeenCalledWith( - 'tasks/tasks.json', - expect.objectContaining({ - tasks: expect.arrayContaining([ - expect.objectContaining({ - id: 4, // Next ID after existing tasks - title: expect.stringContaining( - 'Create a new authentication system' - ), - status: 'pending' - }) - ]) - }) - ); - expect(generateTaskFiles.default).toHaveBeenCalled(); - expect(result).toEqual( - expect.objectContaining({ - newTaskId: 4, - telemetryData: expect.any(Object) - }) - ); - }); + // Assert + expect(readJSON).toHaveBeenCalledWith("tasks/tasks.json"); + expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object)); + expect(writeJSON).toHaveBeenCalledWith( + "tasks/tasks.json", + expect.objectContaining({ + tasks: expect.arrayContaining([ + expect.objectContaining({ + id: 4, // Next ID after existing tasks + title: expect.stringContaining( + "Create a new authentication system" + ), + status: "pending", + }), + ]), + }) + ); + expect(generateTaskFiles.default).toHaveBeenCalled(); + expect(result).toEqual( + expect.objectContaining({ + newTaskId: 4, + telemetryData: expect.any(Object), + }) + ); + }); - test('should validate dependencies when adding a task', async () => { - // Arrange - const prompt = 'Create a new authentication system'; - const validDependencies = [1, 2]; // These exist in sampleTasks - const context = { - mcpLog: createMcpLogMock() - }; + test("should validate dependencies when adding a task", async () => { + // Arrange + const prompt = "Create a new authentication system"; + const validDependencies = [1, 2]; // These exist in sampleTasks + const context = { + mcpLog: createMcpLogMock(), + }; - // Act - const result = await addTask( - 'tasks/tasks.json', - prompt, - validDependencies, - 'medium', - context, - 'json' - ); + // Act + const result = await addTask( + "tasks/tasks.json", + prompt, + validDependencies, + "medium", + context, + "json" + ); - // Assert - expect(writeJSON).toHaveBeenCalledWith( - 'tasks/tasks.json', - expect.objectContaining({ - tasks: expect.arrayContaining([ - expect.objectContaining({ - id: 4, - dependencies: validDependencies - }) - ]) - }) - ); - }); + // Assert + expect(writeJSON).toHaveBeenCalledWith( + "tasks/tasks.json", + expect.objectContaining({ + tasks: expect.arrayContaining([ + expect.objectContaining({ + id: 4, + dependencies: validDependencies, + }), + ]), + }) + ); + }); - test('should filter out invalid dependencies', async () => { - // Arrange - const prompt = 'Create a new authentication system'; - const invalidDependencies = [999]; // Non-existent task ID - const context = { mcpLog: createMcpLogMock() }; + test("should filter out invalid dependencies", async () => { + // Arrange + const prompt = "Create a new authentication system"; + const invalidDependencies = [999]; // Non-existent task ID + const context = { mcpLog: createMcpLogMock() }; - // Act - const result = await addTask( - 'tasks/tasks.json', - prompt, - invalidDependencies, - 'medium', - context, - 'json' - ); + // Act + const result = await addTask( + "tasks/tasks.json", + prompt, + invalidDependencies, + "medium", + context, + "json" + ); - // Assert - expect(writeJSON).toHaveBeenCalledWith( - 'tasks/tasks.json', - expect.objectContaining({ - tasks: expect.arrayContaining([ - expect.objectContaining({ - id: 4, - dependencies: [] // Invalid dependencies should be filtered out - }) - ]) - }) - ); - expect(context.mcpLog.warn).toHaveBeenCalledWith( - expect.stringContaining( - 'The following dependencies do not exist or are invalid: 999' - ) - ); - }); + // Assert + expect(writeJSON).toHaveBeenCalledWith( + "tasks/tasks.json", + expect.objectContaining({ + tasks: expect.arrayContaining([ + expect.objectContaining({ + id: 4, + dependencies: [], // Invalid dependencies should be filtered out + }), + ]), + }) + ); + expect(context.mcpLog.warn).toHaveBeenCalledWith( + expect.stringContaining( + "The following dependencies do not exist or are invalid: 999" + ) + ); + }); - test('should use specified priority', async () => { - // Arrange - const prompt = 'Create a new authentication system'; - const priority = 'high'; - const context = { - mcpLog: createMcpLogMock() - }; + test("should use specified priority", async () => { + // Arrange + const prompt = "Create a new authentication system"; + const priority = "high"; + const context = { + mcpLog: createMcpLogMock(), + }; - // Act - await addTask('tasks/tasks.json', prompt, [], priority, context, 'json'); + // Act + await addTask("tasks/tasks.json", prompt, [], priority, context, "json"); - // Assert - expect(writeJSON).toHaveBeenCalledWith( - 'tasks/tasks.json', - expect.objectContaining({ - tasks: expect.arrayContaining([ - expect.objectContaining({ - priority: priority - }) - ]) - }) - ); - }); + // Assert + expect(writeJSON).toHaveBeenCalledWith( + "tasks/tasks.json", + expect.objectContaining({ + tasks: expect.arrayContaining([ + expect.objectContaining({ + priority: priority, + }), + ]), + }) + ); + }); - test('should handle empty tasks file', async () => { - // Arrange - readJSON.mockReturnValue({ tasks: [] }); - const prompt = 'Create a new authentication system'; - const context = { - mcpLog: createMcpLogMock() - }; + test("should handle empty tasks file", async () => { + // Arrange + readJSON.mockReturnValue({ tasks: [] }); + const prompt = "Create a new authentication system"; + const context = { + mcpLog: createMcpLogMock(), + }; - // Act - const result = await addTask( - 'tasks/tasks.json', - prompt, - [], - 'medium', - context, - 'json' - ); + // Act + const result = await addTask( + "tasks/tasks.json", + prompt, + [], + "medium", + context, + "json" + ); - // Assert - expect(result.newTaskId).toBe(1); // First task should have ID 1 - expect(writeJSON).toHaveBeenCalledWith( - 'tasks/tasks.json', - expect.objectContaining({ - tasks: expect.arrayContaining([ - expect.objectContaining({ - id: 1 - }) - ]) - }) - ); - }); + // Assert + expect(result.newTaskId).toBe(1); // First task should have ID 1 + expect(writeJSON).toHaveBeenCalledWith( + "tasks/tasks.json", + expect.objectContaining({ + tasks: expect.arrayContaining([ + expect.objectContaining({ + id: 1, + }), + ]), + }) + ); + }); - test('should handle missing tasks file', async () => { - // Arrange - readJSON.mockReturnValue(null); - const prompt = 'Create a new authentication system'; - const context = { - mcpLog: createMcpLogMock() - }; + test("should handle missing tasks file", async () => { + // Arrange + readJSON.mockReturnValue(null); + const prompt = "Create a new authentication system"; + const context = { + mcpLog: createMcpLogMock(), + }; - // Act - const result = await addTask( - 'tasks/tasks.json', - prompt, - [], - 'medium', - context, - 'json' - ); + // Act + const result = await addTask( + "tasks/tasks.json", + prompt, + [], + "medium", + context, + "json" + ); - // Assert - expect(result.newTaskId).toBe(1); // First task should have ID 1 - expect(writeJSON).toHaveBeenCalledTimes(2); // Once to create file, once to add task - }); + // Assert + expect(result.newTaskId).toBe(1); // First task should have ID 1 + expect(writeJSON).toHaveBeenCalledTimes(2); // Once to create file, once to add task + }); - test('should handle AI service errors', async () => { - // Arrange - generateObjectService.mockRejectedValueOnce(new Error('AI service failed')); - const prompt = 'Create a new authentication system'; - const context = { - mcpLog: createMcpLogMock() - }; + test("should handle AI service errors", async () => { + // Arrange + generateObjectService.mockRejectedValueOnce(new Error("AI service failed")); + const prompt = "Create a new authentication system"; + const context = { + mcpLog: createMcpLogMock(), + }; - // Act & Assert - await expect( - addTask('tasks/tasks.json', prompt, [], 'medium', context, 'json') - ).rejects.toThrow('AI service failed'); - }); + // Act & Assert + await expect( + addTask("tasks/tasks.json", prompt, [], "medium", context, "json") + ).rejects.toThrow("AI service failed"); + }); - test('should handle file read errors', async () => { - // Arrange - readJSON.mockImplementation(() => { - throw new Error('File read failed'); - }); - const prompt = 'Create a new authentication system'; - const context = { - mcpLog: createMcpLogMock() - }; + test("should handle file read errors", async () => { + // Arrange + readJSON.mockImplementation(() => { + throw new Error("File read failed"); + }); + const prompt = "Create a new authentication system"; + const context = { + mcpLog: createMcpLogMock(), + }; - // Act & Assert - await expect( - addTask('tasks/tasks.json', prompt, [], 'medium', context, 'json') - ).rejects.toThrow('File read failed'); - }); + // Act & Assert + await expect( + addTask("tasks/tasks.json", prompt, [], "medium", context, "json") + ).rejects.toThrow("File read failed"); + }); - test('should handle file write errors', async () => { - // Arrange - writeJSON.mockImplementation(() => { - throw new Error('File write failed'); - }); - const prompt = 'Create a new authentication system'; - const context = { - mcpLog: createMcpLogMock() - }; + test("should handle file write errors", async () => { + // Arrange + writeJSON.mockImplementation(() => { + throw new Error("File write failed"); + }); + const prompt = "Create a new authentication system"; + const context = { + mcpLog: createMcpLogMock(), + }; - // Act & Assert - await expect( - addTask('tasks/tasks.json', prompt, [], 'medium', context, 'json') - ).rejects.toThrow('File write failed'); - }); + // Act & Assert + await expect( + addTask("tasks/tasks.json", prompt, [], "medium", context, "json") + ).rejects.toThrow("File write failed"); + }); }); diff --git a/tests/unit/ui.test.js b/tests/unit/ui.test.js index dbab8ea8..02b869f0 100644 --- a/tests/unit/ui.test.js +++ b/tests/unit/ui.test.js @@ -82,19 +82,19 @@ describe("UI Module", () => { test("should return done status with emoji for console output", () => { const result = getStatusWithColor("done"); expect(result).toMatch(/done/); - expect(result).toContain("โœ…"); + expect(result).toContain("โœ“"); }); test("should return pending status with emoji for console output", () => { const result = getStatusWithColor("pending"); expect(result).toMatch(/pending/); - expect(result).toContain("โฑ๏ธ"); + expect(result).toContain("โ—‹"); }); test("should return deferred status with emoji for console output", () => { const result = getStatusWithColor("deferred"); expect(result).toMatch(/deferred/); - expect(result).toContain("โฑ๏ธ"); + expect(result).toContain("x"); }); test("should return in-progress status with emoji for console output", () => { From 54bfc72baa50d91efc2f25079bef0ad35f3f63fd Mon Sep 17 00:00:00 2001 From: Eyal Toledano <eyal@microangel.so> Date: Sat, 7 Jun 2025 20:32:37 -0400 Subject: [PATCH 08/10] chore: more linting --- .../modules/task-manager/add-task.test.js | 672 +++++++++--------- tests/unit/ui.test.js | 384 +++++----- 2 files changed, 528 insertions(+), 528 deletions(-) diff --git a/tests/unit/scripts/modules/task-manager/add-task.test.js b/tests/unit/scripts/modules/task-manager/add-task.test.js index 9a31cf10..26ec02ef 100644 --- a/tests/unit/scripts/modules/task-manager/add-task.test.js +++ b/tests/unit/scripts/modules/task-manager/add-task.test.js @@ -1,404 +1,404 @@ /** * Tests for the add-task.js module */ -import { jest } from "@jest/globals"; +import { jest } from '@jest/globals'; // Mock the dependencies before importing the module under test -jest.unstable_mockModule("../../../../../scripts/modules/utils.js", () => ({ - readJSON: jest.fn(), - writeJSON: jest.fn(), - log: jest.fn(), - CONFIG: { - model: "mock-claude-model", - maxTokens: 4000, - temperature: 0.7, - debug: false, - }, - truncate: jest.fn((text) => text), +jest.unstable_mockModule('../../../../../scripts/modules/utils.js', () => ({ + readJSON: jest.fn(), + writeJSON: jest.fn(), + log: jest.fn(), + CONFIG: { + model: 'mock-claude-model', + maxTokens: 4000, + temperature: 0.7, + debug: false + }, + truncate: jest.fn((text) => text) })); -jest.unstable_mockModule("../../../../../scripts/modules/ui.js", () => ({ - displayBanner: jest.fn(), - getStatusWithColor: jest.fn((status) => status), - startLoadingIndicator: jest.fn(), - stopLoadingIndicator: jest.fn(), - succeedLoadingIndicator: jest.fn(), - failLoadingIndicator: jest.fn(), - warnLoadingIndicator: jest.fn(), - infoLoadingIndicator: jest.fn(), - displayAiUsageSummary: jest.fn(), +jest.unstable_mockModule('../../../../../scripts/modules/ui.js', () => ({ + displayBanner: jest.fn(), + getStatusWithColor: jest.fn((status) => status), + startLoadingIndicator: jest.fn(), + stopLoadingIndicator: jest.fn(), + succeedLoadingIndicator: jest.fn(), + failLoadingIndicator: jest.fn(), + warnLoadingIndicator: jest.fn(), + infoLoadingIndicator: jest.fn(), + displayAiUsageSummary: jest.fn() })); jest.unstable_mockModule( - "../../../../../scripts/modules/ai-services-unified.js", - () => ({ - generateObjectService: jest.fn().mockResolvedValue({ - mainResult: { - object: { - title: "Task from prompt: Create a new authentication system", - description: - "Task generated from: Create a new authentication system", - details: - "Implementation details for task generated from prompt: Create a new authentication system", - testStrategy: "Write unit tests to verify functionality", - dependencies: [], - }, - }, - telemetryData: { - timestamp: new Date().toISOString(), - userId: "1234567890", - commandName: "add-task", - modelUsed: "claude-3-5-sonnet", - providerName: "anthropic", - inputTokens: 1000, - outputTokens: 500, - totalTokens: 1500, - totalCost: 0.012414, - currency: "USD", - }, - }), - }) + '../../../../../scripts/modules/ai-services-unified.js', + () => ({ + generateObjectService: jest.fn().mockResolvedValue({ + mainResult: { + object: { + title: 'Task from prompt: Create a new authentication system', + description: + 'Task generated from: Create a new authentication system', + details: + 'Implementation details for task generated from prompt: Create a new authentication system', + testStrategy: 'Write unit tests to verify functionality', + dependencies: [] + } + }, + telemetryData: { + timestamp: new Date().toISOString(), + userId: '1234567890', + commandName: 'add-task', + modelUsed: 'claude-3-5-sonnet', + providerName: 'anthropic', + inputTokens: 1000, + outputTokens: 500, + totalTokens: 1500, + totalCost: 0.012414, + currency: 'USD' + } + }) + }) ); jest.unstable_mockModule( - "../../../../../scripts/modules/config-manager.js", - () => ({ - getDefaultPriority: jest.fn(() => "medium"), - }) + '../../../../../scripts/modules/config-manager.js', + () => ({ + getDefaultPriority: jest.fn(() => 'medium') + }) ); jest.unstable_mockModule( - "../../../../../scripts/modules/task-manager/generate-task-files.js", - () => ({ - default: jest.fn().mockResolvedValue(), - }) + '../../../../../scripts/modules/task-manager/generate-task-files.js', + () => ({ + default: jest.fn().mockResolvedValue() + }) ); // Mock external UI libraries -jest.unstable_mockModule("chalk", () => ({ - default: { - white: { bold: jest.fn((text) => text) }, - cyan: Object.assign( - jest.fn((text) => text), - { - bold: jest.fn((text) => text), - } - ), - green: jest.fn((text) => text), - yellow: jest.fn((text) => text), - bold: jest.fn((text) => text), - }, +jest.unstable_mockModule('chalk', () => ({ + default: { + white: { bold: jest.fn((text) => text) }, + cyan: Object.assign( + jest.fn((text) => text), + { + bold: jest.fn((text) => text) + } + ), + green: jest.fn((text) => text), + yellow: jest.fn((text) => text), + bold: jest.fn((text) => text) + } })); -jest.unstable_mockModule("boxen", () => ({ - default: jest.fn((text) => text), +jest.unstable_mockModule('boxen', () => ({ + default: jest.fn((text) => text) })); -jest.unstable_mockModule("cli-table3", () => ({ - default: jest.fn().mockImplementation(() => ({ - push: jest.fn(), - toString: jest.fn(() => "mocked table"), - })), +jest.unstable_mockModule('cli-table3', () => ({ + default: jest.fn().mockImplementation(() => ({ + push: jest.fn(), + toString: jest.fn(() => 'mocked table') + })) })); // Import the mocked modules const { readJSON, writeJSON, log } = await import( - "../../../../../scripts/modules/utils.js" + '../../../../../scripts/modules/utils.js' ); const { generateObjectService } = await import( - "../../../../../scripts/modules/ai-services-unified.js" + '../../../../../scripts/modules/ai-services-unified.js' ); const generateTaskFiles = await import( - "../../../../../scripts/modules/task-manager/generate-task-files.js" + '../../../../../scripts/modules/task-manager/generate-task-files.js' ); // Import the module under test const { default: addTask } = await import( - "../../../../../scripts/modules/task-manager/add-task.js" + '../../../../../scripts/modules/task-manager/add-task.js' ); -describe("addTask", () => { - const sampleTasks = { - tasks: [ - { - id: 1, - title: "Task 1", - description: "First task", - status: "pending", - dependencies: [], - }, - { - id: 2, - title: "Task 2", - description: "Second task", - status: "pending", - dependencies: [], - }, - { - id: 3, - title: "Task 3", - description: "Third task", - status: "pending", - dependencies: [1], - }, - ], - }; +describe('addTask', () => { + const sampleTasks = { + tasks: [ + { + id: 1, + title: 'Task 1', + description: 'First task', + status: 'pending', + dependencies: [] + }, + { + id: 2, + title: 'Task 2', + description: 'Second task', + status: 'pending', + dependencies: [] + }, + { + id: 3, + title: 'Task 3', + description: 'Third task', + status: 'pending', + dependencies: [1] + } + ] + }; - // Create a helper function for consistent mcpLog mock - const createMcpLogMock = () => ({ - info: jest.fn(), - warn: jest.fn(), - error: jest.fn(), - debug: jest.fn(), - success: jest.fn(), - }); + // Create a helper function for consistent mcpLog mock + const createMcpLogMock = () => ({ + info: jest.fn(), + warn: jest.fn(), + error: jest.fn(), + debug: jest.fn(), + success: jest.fn() + }); - beforeEach(() => { - jest.clearAllMocks(); - readJSON.mockReturnValue(JSON.parse(JSON.stringify(sampleTasks))); + beforeEach(() => { + jest.clearAllMocks(); + readJSON.mockReturnValue(JSON.parse(JSON.stringify(sampleTasks))); - // Mock console.log to avoid output during tests - jest.spyOn(console, "log").mockImplementation(() => {}); - }); + // Mock console.log to avoid output during tests + jest.spyOn(console, 'log').mockImplementation(() => {}); + }); - afterEach(() => { - console.log.mockRestore(); - }); + afterEach(() => { + console.log.mockRestore(); + }); - test("should add a new task using AI", async () => { - // Arrange - const prompt = "Create a new authentication system"; - const context = { - mcpLog: createMcpLogMock(), - }; + test('should add a new task using AI', async () => { + // Arrange + const prompt = 'Create a new authentication system'; + const context = { + mcpLog: createMcpLogMock() + }; - // Act - const result = await addTask( - "tasks/tasks.json", - prompt, - [], - "medium", - context, - "json" - ); + // Act + const result = await addTask( + 'tasks/tasks.json', + prompt, + [], + 'medium', + context, + 'json' + ); - // Assert - expect(readJSON).toHaveBeenCalledWith("tasks/tasks.json"); - expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object)); - expect(writeJSON).toHaveBeenCalledWith( - "tasks/tasks.json", - expect.objectContaining({ - tasks: expect.arrayContaining([ - expect.objectContaining({ - id: 4, // Next ID after existing tasks - title: expect.stringContaining( - "Create a new authentication system" - ), - status: "pending", - }), - ]), - }) - ); - expect(generateTaskFiles.default).toHaveBeenCalled(); - expect(result).toEqual( - expect.objectContaining({ - newTaskId: 4, - telemetryData: expect.any(Object), - }) - ); - }); + // Assert + expect(readJSON).toHaveBeenCalledWith('tasks/tasks.json'); + expect(generateObjectService).toHaveBeenCalledWith(expect.any(Object)); + expect(writeJSON).toHaveBeenCalledWith( + 'tasks/tasks.json', + expect.objectContaining({ + tasks: expect.arrayContaining([ + expect.objectContaining({ + id: 4, // Next ID after existing tasks + title: expect.stringContaining( + 'Create a new authentication system' + ), + status: 'pending' + }) + ]) + }) + ); + expect(generateTaskFiles.default).toHaveBeenCalled(); + expect(result).toEqual( + expect.objectContaining({ + newTaskId: 4, + telemetryData: expect.any(Object) + }) + ); + }); - test("should validate dependencies when adding a task", async () => { - // Arrange - const prompt = "Create a new authentication system"; - const validDependencies = [1, 2]; // These exist in sampleTasks - const context = { - mcpLog: createMcpLogMock(), - }; + test('should validate dependencies when adding a task', async () => { + // Arrange + const prompt = 'Create a new authentication system'; + const validDependencies = [1, 2]; // These exist in sampleTasks + const context = { + mcpLog: createMcpLogMock() + }; - // Act - const result = await addTask( - "tasks/tasks.json", - prompt, - validDependencies, - "medium", - context, - "json" - ); + // Act + const result = await addTask( + 'tasks/tasks.json', + prompt, + validDependencies, + 'medium', + context, + 'json' + ); - // Assert - expect(writeJSON).toHaveBeenCalledWith( - "tasks/tasks.json", - expect.objectContaining({ - tasks: expect.arrayContaining([ - expect.objectContaining({ - id: 4, - dependencies: validDependencies, - }), - ]), - }) - ); - }); + // Assert + expect(writeJSON).toHaveBeenCalledWith( + 'tasks/tasks.json', + expect.objectContaining({ + tasks: expect.arrayContaining([ + expect.objectContaining({ + id: 4, + dependencies: validDependencies + }) + ]) + }) + ); + }); - test("should filter out invalid dependencies", async () => { - // Arrange - const prompt = "Create a new authentication system"; - const invalidDependencies = [999]; // Non-existent task ID - const context = { mcpLog: createMcpLogMock() }; + test('should filter out invalid dependencies', async () => { + // Arrange + const prompt = 'Create a new authentication system'; + const invalidDependencies = [999]; // Non-existent task ID + const context = { mcpLog: createMcpLogMock() }; - // Act - const result = await addTask( - "tasks/tasks.json", - prompt, - invalidDependencies, - "medium", - context, - "json" - ); + // Act + const result = await addTask( + 'tasks/tasks.json', + prompt, + invalidDependencies, + 'medium', + context, + 'json' + ); - // Assert - expect(writeJSON).toHaveBeenCalledWith( - "tasks/tasks.json", - expect.objectContaining({ - tasks: expect.arrayContaining([ - expect.objectContaining({ - id: 4, - dependencies: [], // Invalid dependencies should be filtered out - }), - ]), - }) - ); - expect(context.mcpLog.warn).toHaveBeenCalledWith( - expect.stringContaining( - "The following dependencies do not exist or are invalid: 999" - ) - ); - }); + // Assert + expect(writeJSON).toHaveBeenCalledWith( + 'tasks/tasks.json', + expect.objectContaining({ + tasks: expect.arrayContaining([ + expect.objectContaining({ + id: 4, + dependencies: [] // Invalid dependencies should be filtered out + }) + ]) + }) + ); + expect(context.mcpLog.warn).toHaveBeenCalledWith( + expect.stringContaining( + 'The following dependencies do not exist or are invalid: 999' + ) + ); + }); - test("should use specified priority", async () => { - // Arrange - const prompt = "Create a new authentication system"; - const priority = "high"; - const context = { - mcpLog: createMcpLogMock(), - }; + test('should use specified priority', async () => { + // Arrange + const prompt = 'Create a new authentication system'; + const priority = 'high'; + const context = { + mcpLog: createMcpLogMock() + }; - // Act - await addTask("tasks/tasks.json", prompt, [], priority, context, "json"); + // Act + await addTask('tasks/tasks.json', prompt, [], priority, context, 'json'); - // Assert - expect(writeJSON).toHaveBeenCalledWith( - "tasks/tasks.json", - expect.objectContaining({ - tasks: expect.arrayContaining([ - expect.objectContaining({ - priority: priority, - }), - ]), - }) - ); - }); + // Assert + expect(writeJSON).toHaveBeenCalledWith( + 'tasks/tasks.json', + expect.objectContaining({ + tasks: expect.arrayContaining([ + expect.objectContaining({ + priority: priority + }) + ]) + }) + ); + }); - test("should handle empty tasks file", async () => { - // Arrange - readJSON.mockReturnValue({ tasks: [] }); - const prompt = "Create a new authentication system"; - const context = { - mcpLog: createMcpLogMock(), - }; + test('should handle empty tasks file', async () => { + // Arrange + readJSON.mockReturnValue({ tasks: [] }); + const prompt = 'Create a new authentication system'; + const context = { + mcpLog: createMcpLogMock() + }; - // Act - const result = await addTask( - "tasks/tasks.json", - prompt, - [], - "medium", - context, - "json" - ); + // Act + const result = await addTask( + 'tasks/tasks.json', + prompt, + [], + 'medium', + context, + 'json' + ); - // Assert - expect(result.newTaskId).toBe(1); // First task should have ID 1 - expect(writeJSON).toHaveBeenCalledWith( - "tasks/tasks.json", - expect.objectContaining({ - tasks: expect.arrayContaining([ - expect.objectContaining({ - id: 1, - }), - ]), - }) - ); - }); + // Assert + expect(result.newTaskId).toBe(1); // First task should have ID 1 + expect(writeJSON).toHaveBeenCalledWith( + 'tasks/tasks.json', + expect.objectContaining({ + tasks: expect.arrayContaining([ + expect.objectContaining({ + id: 1 + }) + ]) + }) + ); + }); - test("should handle missing tasks file", async () => { - // Arrange - readJSON.mockReturnValue(null); - const prompt = "Create a new authentication system"; - const context = { - mcpLog: createMcpLogMock(), - }; + test('should handle missing tasks file', async () => { + // Arrange + readJSON.mockReturnValue(null); + const prompt = 'Create a new authentication system'; + const context = { + mcpLog: createMcpLogMock() + }; - // Act - const result = await addTask( - "tasks/tasks.json", - prompt, - [], - "medium", - context, - "json" - ); + // Act + const result = await addTask( + 'tasks/tasks.json', + prompt, + [], + 'medium', + context, + 'json' + ); - // Assert - expect(result.newTaskId).toBe(1); // First task should have ID 1 - expect(writeJSON).toHaveBeenCalledTimes(2); // Once to create file, once to add task - }); + // Assert + expect(result.newTaskId).toBe(1); // First task should have ID 1 + expect(writeJSON).toHaveBeenCalledTimes(2); // Once to create file, once to add task + }); - test("should handle AI service errors", async () => { - // Arrange - generateObjectService.mockRejectedValueOnce(new Error("AI service failed")); - const prompt = "Create a new authentication system"; - const context = { - mcpLog: createMcpLogMock(), - }; + test('should handle AI service errors', async () => { + // Arrange + generateObjectService.mockRejectedValueOnce(new Error('AI service failed')); + const prompt = 'Create a new authentication system'; + const context = { + mcpLog: createMcpLogMock() + }; - // Act & Assert - await expect( - addTask("tasks/tasks.json", prompt, [], "medium", context, "json") - ).rejects.toThrow("AI service failed"); - }); + // Act & Assert + await expect( + addTask('tasks/tasks.json', prompt, [], 'medium', context, 'json') + ).rejects.toThrow('AI service failed'); + }); - test("should handle file read errors", async () => { - // Arrange - readJSON.mockImplementation(() => { - throw new Error("File read failed"); - }); - const prompt = "Create a new authentication system"; - const context = { - mcpLog: createMcpLogMock(), - }; + test('should handle file read errors', async () => { + // Arrange + readJSON.mockImplementation(() => { + throw new Error('File read failed'); + }); + const prompt = 'Create a new authentication system'; + const context = { + mcpLog: createMcpLogMock() + }; - // Act & Assert - await expect( - addTask("tasks/tasks.json", prompt, [], "medium", context, "json") - ).rejects.toThrow("File read failed"); - }); + // Act & Assert + await expect( + addTask('tasks/tasks.json', prompt, [], 'medium', context, 'json') + ).rejects.toThrow('File read failed'); + }); - test("should handle file write errors", async () => { - // Arrange - writeJSON.mockImplementation(() => { - throw new Error("File write failed"); - }); - const prompt = "Create a new authentication system"; - const context = { - mcpLog: createMcpLogMock(), - }; + test('should handle file write errors', async () => { + // Arrange + writeJSON.mockImplementation(() => { + throw new Error('File write failed'); + }); + const prompt = 'Create a new authentication system'; + const context = { + mcpLog: createMcpLogMock() + }; - // Act & Assert - await expect( - addTask("tasks/tasks.json", prompt, [], "medium", context, "json") - ).rejects.toThrow("File write failed"); - }); + // Act & Assert + await expect( + addTask('tasks/tasks.json', prompt, [], 'medium', context, 'json') + ).rejects.toThrow('File write failed'); + }); }); diff --git a/tests/unit/ui.test.js b/tests/unit/ui.test.js index 02b869f0..fff21af6 100644 --- a/tests/unit/ui.test.js +++ b/tests/unit/ui.test.js @@ -2,245 +2,245 @@ * UI module tests */ -import { jest } from "@jest/globals"; +import { jest } from '@jest/globals'; import { - getStatusWithColor, - formatDependenciesWithStatus, - createProgressBar, - getComplexityWithColor, -} from "../../scripts/modules/ui.js"; -import { sampleTasks } from "../fixtures/sample-tasks.js"; + getStatusWithColor, + formatDependenciesWithStatus, + createProgressBar, + getComplexityWithColor +} from '../../scripts/modules/ui.js'; +import { sampleTasks } from '../fixtures/sample-tasks.js'; // Mock dependencies -jest.mock("chalk", () => { - const origChalkFn = (text) => text; - const chalk = origChalkFn; - chalk.green = (text) => text; // Return text as-is for status functions - chalk.yellow = (text) => text; - chalk.red = (text) => text; - chalk.cyan = (text) => text; - chalk.blue = (text) => text; - chalk.gray = (text) => text; - chalk.white = (text) => text; - chalk.bold = (text) => text; - chalk.dim = (text) => text; +jest.mock('chalk', () => { + const origChalkFn = (text) => text; + const chalk = origChalkFn; + chalk.green = (text) => text; // Return text as-is for status functions + chalk.yellow = (text) => text; + chalk.red = (text) => text; + chalk.cyan = (text) => text; + chalk.blue = (text) => text; + chalk.gray = (text) => text; + chalk.white = (text) => text; + chalk.bold = (text) => text; + chalk.dim = (text) => text; - // Add hex and other methods - chalk.hex = () => origChalkFn; - chalk.rgb = () => origChalkFn; + // Add hex and other methods + chalk.hex = () => origChalkFn; + chalk.rgb = () => origChalkFn; - return chalk; + return chalk; }); -jest.mock("figlet", () => ({ - textSync: jest.fn(() => "Task Master Banner"), +jest.mock('figlet', () => ({ + textSync: jest.fn(() => 'Task Master Banner') })); -jest.mock("boxen", () => jest.fn((text) => `[boxed: ${text}]`)); +jest.mock('boxen', () => jest.fn((text) => `[boxed: ${text}]`)); -jest.mock("ora", () => - jest.fn(() => ({ - start: jest.fn(), - succeed: jest.fn(), - fail: jest.fn(), - stop: jest.fn(), - })) +jest.mock('ora', () => + jest.fn(() => ({ + start: jest.fn(), + succeed: jest.fn(), + fail: jest.fn(), + stop: jest.fn() + })) ); -jest.mock("cli-table3", () => - jest.fn().mockImplementation(() => ({ - push: jest.fn(), - toString: jest.fn(() => "Table Content"), - })) +jest.mock('cli-table3', () => + jest.fn().mockImplementation(() => ({ + push: jest.fn(), + toString: jest.fn(() => 'Table Content') + })) ); -jest.mock("gradient-string", () => jest.fn(() => jest.fn((text) => text))); +jest.mock('gradient-string', () => jest.fn(() => jest.fn((text) => text))); -jest.mock("../../scripts/modules/utils.js", () => ({ - CONFIG: { - projectName: "Test Project", - projectVersion: "1.0.0", - }, - log: jest.fn(), - findTaskById: jest.fn(), - readJSON: jest.fn(), - readComplexityReport: jest.fn(), - truncate: jest.fn((text) => text), +jest.mock('../../scripts/modules/utils.js', () => ({ + CONFIG: { + projectName: 'Test Project', + projectVersion: '1.0.0' + }, + log: jest.fn(), + findTaskById: jest.fn(), + readJSON: jest.fn(), + readComplexityReport: jest.fn(), + truncate: jest.fn((text) => text) })); -jest.mock("../../scripts/modules/task-manager.js", () => ({ - findNextTask: jest.fn(), - analyzeTaskComplexity: jest.fn(), +jest.mock('../../scripts/modules/task-manager.js', () => ({ + findNextTask: jest.fn(), + analyzeTaskComplexity: jest.fn() })); -describe("UI Module", () => { - beforeEach(() => { - jest.clearAllMocks(); - }); +describe('UI Module', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); - describe("getStatusWithColor function", () => { - test("should return done status with emoji for console output", () => { - const result = getStatusWithColor("done"); - expect(result).toMatch(/done/); - expect(result).toContain("โœ“"); - }); + describe('getStatusWithColor function', () => { + test('should return done status with emoji for console output', () => { + const result = getStatusWithColor('done'); + expect(result).toMatch(/done/); + expect(result).toContain('โœ“'); + }); - test("should return pending status with emoji for console output", () => { - const result = getStatusWithColor("pending"); - expect(result).toMatch(/pending/); - expect(result).toContain("โ—‹"); - }); + test('should return pending status with emoji for console output', () => { + const result = getStatusWithColor('pending'); + expect(result).toMatch(/pending/); + expect(result).toContain('โ—‹'); + }); - test("should return deferred status with emoji for console output", () => { - const result = getStatusWithColor("deferred"); - expect(result).toMatch(/deferred/); - expect(result).toContain("x"); - }); + test('should return deferred status with emoji for console output', () => { + const result = getStatusWithColor('deferred'); + expect(result).toMatch(/deferred/); + expect(result).toContain('x'); + }); - test("should return in-progress status with emoji for console output", () => { - const result = getStatusWithColor("in-progress"); - expect(result).toMatch(/in-progress/); - expect(result).toContain("๐Ÿ”„"); - }); + test('should return in-progress status with emoji for console output', () => { + const result = getStatusWithColor('in-progress'); + expect(result).toMatch(/in-progress/); + expect(result).toContain('๐Ÿ”„'); + }); - test("should return unknown status with emoji for console output", () => { - const result = getStatusWithColor("unknown"); - expect(result).toMatch(/unknown/); - expect(result).toContain("โŒ"); - }); + test('should return unknown status with emoji for console output', () => { + const result = getStatusWithColor('unknown'); + expect(result).toMatch(/unknown/); + expect(result).toContain('โŒ'); + }); - test("should use simple icons when forTable is true", () => { - const doneResult = getStatusWithColor("done", true); - expect(doneResult).toMatch(/done/); - expect(doneResult).toContain("โœ“"); + test('should use simple icons when forTable is true', () => { + const doneResult = getStatusWithColor('done', true); + expect(doneResult).toMatch(/done/); + expect(doneResult).toContain('โœ“'); - const pendingResult = getStatusWithColor("pending", true); - expect(pendingResult).toMatch(/pending/); - expect(pendingResult).toContain("โ—‹"); + const pendingResult = getStatusWithColor('pending', true); + expect(pendingResult).toMatch(/pending/); + expect(pendingResult).toContain('โ—‹'); - const inProgressResult = getStatusWithColor("in-progress", true); - expect(inProgressResult).toMatch(/in-progress/); - expect(inProgressResult).toContain("โ–บ"); + const inProgressResult = getStatusWithColor('in-progress', true); + expect(inProgressResult).toMatch(/in-progress/); + expect(inProgressResult).toContain('โ–บ'); - const deferredResult = getStatusWithColor("deferred", true); - expect(deferredResult).toMatch(/deferred/); - expect(deferredResult).toContain("x"); - }); - }); + const deferredResult = getStatusWithColor('deferred', true); + expect(deferredResult).toMatch(/deferred/); + expect(deferredResult).toContain('x'); + }); + }); - describe("formatDependenciesWithStatus function", () => { - test("should format dependencies as plain IDs when forConsole is false (default)", () => { - const dependencies = [1, 2, 3]; - const allTasks = [ - { id: 1, status: "done" }, - { id: 2, status: "pending" }, - { id: 3, status: "deferred" }, - ]; + describe('formatDependenciesWithStatus function', () => { + test('should format dependencies as plain IDs when forConsole is false (default)', () => { + const dependencies = [1, 2, 3]; + const allTasks = [ + { id: 1, status: 'done' }, + { id: 2, status: 'pending' }, + { id: 3, status: 'deferred' } + ]; - const result = formatDependenciesWithStatus(dependencies, allTasks); + const result = formatDependenciesWithStatus(dependencies, allTasks); - // With recent changes, we expect just plain IDs when forConsole is false - expect(result).toBe("1, 2, 3"); - }); + // With recent changes, we expect just plain IDs when forConsole is false + expect(result).toBe('1, 2, 3'); + }); - test("should format dependencies with status indicators when forConsole is true", () => { - const dependencies = [1, 2, 3]; - const allTasks = [ - { id: 1, status: "done" }, - { id: 2, status: "pending" }, - { id: 3, status: "deferred" }, - ]; + test('should format dependencies with status indicators when forConsole is true', () => { + const dependencies = [1, 2, 3]; + const allTasks = [ + { id: 1, status: 'done' }, + { id: 2, status: 'pending' }, + { id: 3, status: 'deferred' } + ]; - const result = formatDependenciesWithStatus(dependencies, allTasks, true); + const result = formatDependenciesWithStatus(dependencies, allTasks, true); - // We can't test for exact color formatting due to our chalk mocks - // Instead, test that the result contains all the expected IDs - expect(result).toContain("1"); - expect(result).toContain("2"); - expect(result).toContain("3"); + // We can't test for exact color formatting due to our chalk mocks + // Instead, test that the result contains all the expected IDs + expect(result).toContain('1'); + expect(result).toContain('2'); + expect(result).toContain('3'); - // Test that it's a comma-separated list - expect(result.split(", ").length).toBe(3); - }); + // Test that it's a comma-separated list + expect(result.split(', ').length).toBe(3); + }); - test('should return "None" for empty dependencies', () => { - const result = formatDependenciesWithStatus([], []); - expect(result).toBe("None"); - }); + test('should return "None" for empty dependencies', () => { + const result = formatDependenciesWithStatus([], []); + expect(result).toBe('None'); + }); - test("should handle missing tasks in the task list", () => { - const dependencies = [1, 999]; - const allTasks = [{ id: 1, status: "done" }]; + test('should handle missing tasks in the task list', () => { + const dependencies = [1, 999]; + const allTasks = [{ id: 1, status: 'done' }]; - const result = formatDependenciesWithStatus(dependencies, allTasks); - expect(result).toBe("1, 999 (Not found)"); - }); - }); + const result = formatDependenciesWithStatus(dependencies, allTasks); + expect(result).toBe('1, 999 (Not found)'); + }); + }); - describe("createProgressBar function", () => { - test("should create a progress bar with the correct percentage", () => { - const result = createProgressBar(50, 10, { - pending: 20, - "in-progress": 15, - blocked: 5, - }); - expect(result).toContain("50%"); - }); + describe('createProgressBar function', () => { + test('should create a progress bar with the correct percentage', () => { + const result = createProgressBar(50, 10, { + pending: 20, + 'in-progress': 15, + blocked: 5 + }); + expect(result).toContain('50%'); + }); - test("should handle 0% progress", () => { - const result = createProgressBar(0, 10); - expect(result).toContain("0%"); - }); + test('should handle 0% progress', () => { + const result = createProgressBar(0, 10); + expect(result).toContain('0%'); + }); - test("should handle 100% progress", () => { - const result = createProgressBar(100, 10); - expect(result).toContain("100%"); - }); + test('should handle 100% progress', () => { + const result = createProgressBar(100, 10); + expect(result).toContain('100%'); + }); - test("should handle invalid percentages by clamping", () => { - const result1 = createProgressBar(0, 10); - expect(result1).toContain("0%"); + test('should handle invalid percentages by clamping', () => { + const result1 = createProgressBar(0, 10); + expect(result1).toContain('0%'); - const result2 = createProgressBar(100, 10); - expect(result2).toContain("100%"); - }); + const result2 = createProgressBar(100, 10); + expect(result2).toContain('100%'); + }); - test("should support status breakdown in the progress bar", () => { - const result = createProgressBar(30, 10, { - pending: 30, - "in-progress": 20, - blocked: 10, - deferred: 5, - cancelled: 5, - }); + test('should support status breakdown in the progress bar', () => { + const result = createProgressBar(30, 10, { + pending: 30, + 'in-progress': 20, + blocked: 10, + deferred: 5, + cancelled: 5 + }); - expect(result).toContain("40%"); - }); - }); + expect(result).toContain('40%'); + }); + }); - describe("getComplexityWithColor function", () => { - test("should return high complexity in red", () => { - const result = getComplexityWithColor(8); - expect(result).toMatch(/8/); - expect(result).toContain("โ—"); - }); + describe('getComplexityWithColor function', () => { + test('should return high complexity in red', () => { + const result = getComplexityWithColor(8); + expect(result).toMatch(/8/); + expect(result).toContain('โ—'); + }); - test("should return medium complexity in yellow", () => { - const result = getComplexityWithColor(5); - expect(result).toMatch(/5/); - expect(result).toContain("โ—"); - }); + test('should return medium complexity in yellow', () => { + const result = getComplexityWithColor(5); + expect(result).toMatch(/5/); + expect(result).toContain('โ—'); + }); - test("should return low complexity in green", () => { - const result = getComplexityWithColor(3); - expect(result).toMatch(/3/); - expect(result).toContain("โ—"); - }); + test('should return low complexity in green', () => { + const result = getComplexityWithColor(3); + expect(result).toMatch(/3/); + expect(result).toContain('โ—'); + }); - test("should handle non-numeric inputs", () => { - const result = getComplexityWithColor("high"); - expect(result).toMatch(/high/); - expect(result).toContain("โ—"); - }); - }); + test('should handle non-numeric inputs', () => { + const result = getComplexityWithColor('high'); + expect(result).toMatch(/high/); + expect(result).toContain('โ—'); + }); + }); }); From 2e55757b2698ba20b78f09ec0286951297510b8e Mon Sep 17 00:00:00 2001 From: Eyal Toledano <eyal@microangel.so> Date: Sat, 7 Jun 2025 22:07:35 -0400 Subject: [PATCH 09/10] ninja(sync): add sync-readme command for GitHub README export with UTM tracking and professional markdown formatting. Experimental --- .changeset/vast-shrimps-happen.md | 22 + .taskmaster/config.json | 2 +- README.md | 1204 +++++++++++++++++++- scripts/modules/commands.js | 49 + scripts/modules/sync-readme.js | 184 +++ scripts/modules/task-manager/list-tasks.js | 421 +++++-- scripts/modules/ui.js | 5 + 7 files changed, 1798 insertions(+), 89 deletions(-) create mode 100644 .changeset/vast-shrimps-happen.md create mode 100644 scripts/modules/sync-readme.js diff --git a/.changeset/vast-shrimps-happen.md b/.changeset/vast-shrimps-happen.md new file mode 100644 index 00000000..3c7895e1 --- /dev/null +++ b/.changeset/vast-shrimps-happen.md @@ -0,0 +1,22 @@ +--- +"task-master-ai": minor +--- + +Add sync-readme command for a task export to GitHub README + +Introduces a new `sync-readme` command that exports your task list to your project's README.md file. + +**Features:** + +- **Flexible filtering**: Supports `--status` filtering (e.g., pending, done) and `--with-subtasks` flag +- **Smart content management**: Automatically replaces existing exports or appends to new READMEs +- **Metadata display**: Shows export timestamp, subtask inclusion status, and filter settings + +**Usage:** + +- `task-master sync-readme` - Export tasks without subtasks +- `task-master sync-readme --with-subtasks` - Include subtasks in export +- `task-master sync-readme --status=pending` - Only export pending tasks +- `task-master sync-readme --status=done --with-subtasks` - Export completed tasks with subtasks + +Perfect for showcasing project progress on GitHub with professional presentation and traffic analytics. diff --git a/.taskmaster/config.json b/.taskmaster/config.json index a61d10d5..af2fcea8 100644 --- a/.taskmaster/config.json +++ b/.taskmaster/config.json @@ -20,6 +20,7 @@ } }, "global": { + "userId": "1234567890", "logLevel": "info", "debug": false, "defaultSubtasks": 5, @@ -27,7 +28,6 @@ "projectName": "Taskmaster", "ollamaBaseURL": "http://localhost:11434/api", "bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com", - "userId": "1234567890", "azureBaseURL": "https://your-endpoint.azure.com/" } } diff --git a/README.md b/README.md index 74dc6741..2bc9754f 100644 --- a/README.md +++ b/README.md @@ -66,10 +66,10 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor. "OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE", "XAI_API_KEY": "YOUR_XAI_KEY_HERE", "AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE", - "OLLAMA_API_KEY": "YOUR_OLLAMA_API_KEY_HERE" - } - } - } + "OLLAMA_API_KEY": "YOUR_OLLAMA_API_KEY_HERE", + }, + }, + }, } ``` @@ -91,11 +91,11 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor. "MISTRAL_API_KEY": "YOUR_MISTRAL_KEY_HERE", "OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE", "XAI_API_KEY": "YOUR_XAI_KEY_HERE", - "AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE" + "AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE", }, - "type": "stdio" - } - } + "type": "stdio", + }, + }, } ``` @@ -248,3 +248,1191 @@ Task Master is licensed under the MIT License with Commons Clause. This means yo - Create competing products based on Task Master See the [LICENSE](LICENSE) file for the complete license text and [licensing details](docs/licensing.md) for more information. + +<!-- TASKMASTER_EXPORT_START --> +> ๐ŸŽฏ **Taskmaster Export** - 2025-06-08 02:02:35 UTC +> ๐Ÿ“‹ Export: with subtasks โ€ข Status filter: none +> ๐Ÿ”— Powered by [Task Master](https://task-master.dev?utm_source=github-readme&utm_medium=readme-export&utm_campaign=claude-task-master&utm_content=task-export-link) + +``` +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎโ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โ”‚โ”‚ โ”‚ +โ”‚ Project Dashboard โ”‚โ”‚ Dependency Status & Next Task โ”‚ +โ”‚ Tasks Progress: โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ 56% โ”‚โ”‚ Dependency Metrics: โ”‚ +โ”‚ 56% โ”‚โ”‚ โ€ข Tasks with no dependencies: 28 โ”‚ +โ”‚ Done: 52 In Progress: 0 Pending: 37 Blocked: 0 โ”‚โ”‚ โ€ข Tasks ready to work on: 34 โ”‚ +โ”‚ Deferred: 2 Cancelled: 1 โ”‚โ”‚ โ€ข Tasks blocked by dependencies: 7 โ”‚ +โ”‚ โ”‚โ”‚ โ€ข Most depended-on task: #1 (14 dependents) โ”‚ +โ”‚ Subtasks Progress: โ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–ˆโ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘โ–‘ โ”‚โ”‚ โ€ข Avg dependencies per task: 0.9 โ”‚ +โ”‚ 60% 60% โ”‚โ”‚ โ”‚ +โ”‚ Completed: 279/468 In Progress: 1 Pending: 183 โ”‚โ”‚ Next Task to Work On: โ”‚ +โ”‚ Blocked: 0 Deferred: 3 Cancelled: 2 โ”‚โ”‚ ID: 67 - Add CLI JSON output and Cursor keybin... โ”‚ +โ”‚ โ”‚โ”‚ Priority: high Dependencies: None โ”‚ +โ”‚ Priority Breakdown: โ”‚โ”‚ Complexity: โ— 5 โ”‚ +โ”‚ โ€ข High priority: 25 โ”‚โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ”‚ โ€ข Medium priority: 64 โ”‚ +โ”‚ โ€ข Low priority: 4 โ”‚ +โ”‚ โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ ID โ”‚ Title โ”‚ Status โ”‚ Priority โ”‚ Dependencies โ”‚ Complexiโ€ฆ โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 1 โ”‚ Implement Task Data Structure โ”‚ โœ“ done โ”‚ high โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 2 โ”‚ Develop Command Line Interface Found โ”‚ โœ“ done โ”‚ high โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 3 โ”‚ Implement Basic Task Operations โ”‚ โœ“ done โ”‚ high โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 4 โ”‚ Create Task File Generation System โ”‚ โœ“ done โ”‚ medium โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 4.1 โ”‚ โ””โ”€ Design Task File Template Structu โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 4.2 โ”‚ โ””โ”€ Implement Task File Generation Lo โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 4.3 โ”‚ โ””โ”€ Implement File Naming and Organiz โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 4.4 โ”‚ โ””โ”€ Implement Task File to JSON Synch โ”‚ โœ“ done โ”‚ - โ”‚ 1, 3, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 4.5 โ”‚ โ””โ”€ Implement Change Detection and Up โ”‚ โœ“ done โ”‚ - โ”‚ 1, 3, 4, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 5 โ”‚ Integrate Anthropic Claude API โ”‚ โœ“ done โ”‚ high โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 5.1 โ”‚ โ””โ”€ Configure API Authentication Syst โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 5.2 โ”‚ โ””โ”€ Develop Prompt Template System โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 5.3 โ”‚ โ””โ”€ Implement Response Handling and P โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 5.4 โ”‚ โ””โ”€ Build Error Management with Retry โ”‚ โœ“ done โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 5.5 โ”‚ โ””โ”€ Implement Token Usage Tracking โ”‚ โœ“ done โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 5.6 โ”‚ โ””โ”€ Create Model Parameter Configurat โ”‚ โœ“ done โ”‚ - โ”‚ 1, 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 6 โ”‚ Build PRD Parsing System โ”‚ โœ“ done โ”‚ high โ”‚ 1, 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 6.1 โ”‚ โ””โ”€ Implement PRD File Reading Module โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 6.2 โ”‚ โ””โ”€ Design and Engineer Effective PRD โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 6.3 โ”‚ โ””โ”€ Implement PRD to Task Conversion โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 6.4 โ”‚ โ””โ”€ Build Intelligent Dependency Infe โ”‚ โœ“ done โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 6.5 โ”‚ โ””โ”€ Implement Priority Assignment Log โ”‚ โœ“ done โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 6.6 โ”‚ โ””โ”€ Implement PRD Chunking for Large โ”‚ โœ“ done โ”‚ - โ”‚ 1, 5, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 7 โ”‚ Implement Task Expansion with Claude โ”‚ โœ“ done โ”‚ medium โ”‚ 3, 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 7.1 โ”‚ โ””โ”€ Design and Implement Subtask Gene โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 7.2 โ”‚ โ””โ”€ Develop Task Expansion Workflow a โ”‚ โœ“ done โ”‚ - โ”‚ 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 7.3 โ”‚ โ””โ”€ Implement Context-Aware Expansion โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 7.4 โ”‚ โ””โ”€ Build Parent-Child Relationship M โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 7.5 โ”‚ โ””โ”€ Implement Subtask Regeneration Me โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 8 โ”‚ Develop Implementation Drift Handlin โ”‚ โœ“ done โ”‚ medium โ”‚ 3, 5, 7 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 8.1 โ”‚ โ””โ”€ Create Task Update Mechanism Base โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 8.2 โ”‚ โ””โ”€ Implement AI-Powered Task Rewriti โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 8.3 โ”‚ โ””โ”€ Build Dependency Chain Update Sys โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 8.4 โ”‚ โ””โ”€ Implement Completed Work Preserva โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 8.5 โ”‚ โ””โ”€ Create Update Analysis and Sugges โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 9 โ”‚ Integrate Perplexity API โ”‚ โœ“ done โ”‚ low โ”‚ 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 9.1 โ”‚ โ””โ”€ Implement Perplexity API Authenti โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 9.2 โ”‚ โ””โ”€ Develop Research-Oriented Prompt โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 9.3 โ”‚ โ””โ”€ Create Perplexity Response Handle โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 9.4 โ”‚ โ””โ”€ Implement Claude Fallback Mechani โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 9.5 โ”‚ โ””โ”€ Develop Response Quality Comparis โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 10 โ”‚ Create Research-Backed Subtask Gener โ”‚ โœ“ done โ”‚ low โ”‚ 7, 9 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 10.1 โ”‚ โ””โ”€ Design Domain-Specific Research P โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 10.2 โ”‚ โ””โ”€ Implement Research Query Executio โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 10.3 โ”‚ โ””โ”€ Develop Context Enrichment Pipeli โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 10.4 โ”‚ โ””โ”€ Implement Domain-Specific Knowled โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 10.5 โ”‚ โ””โ”€ Enhance Subtask Generation with T โ”‚ โœ“ done โ”‚ - โ”‚ 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 10.6 โ”‚ โ””โ”€ Implement Reference and Resource โ”‚ โœ“ done โ”‚ - โ”‚ 3, 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 11 โ”‚ Implement Batch Operations โ”‚ โœ“ done โ”‚ medium โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 11.1 โ”‚ โ””โ”€ Implement Multi-Task Status Updat โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 11.2 โ”‚ โ””โ”€ Develop Bulk Subtask Generation S โ”‚ โœ“ done โ”‚ - โ”‚ 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 11.3 โ”‚ โ””โ”€ Implement Advanced Task Filtering โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 11.4 โ”‚ โ””โ”€ Create Advanced Dependency Manage โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 11.5 โ”‚ โ””โ”€ Implement Batch Task Prioritizati โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 12 โ”‚ Develop Project Initialization Syste โ”‚ โœ“ done โ”‚ medium โ”‚ 1, 3, 4, 6 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 12.1 โ”‚ โ””โ”€ Create Project Template Structure โ”‚ โœ“ done โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 12.2 โ”‚ โ””โ”€ Implement Interactive Setup Wizar โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 12.3 โ”‚ โ””โ”€ Generate Environment Configuratio โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 12.4 โ”‚ โ””โ”€ Implement Directory Structure Cre โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 12.5 โ”‚ โ””โ”€ Generate Example Tasks.json โ”‚ โœ“ done โ”‚ - โ”‚ 6 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 12.6 โ”‚ โ””โ”€ Implement Default Configuration S โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 13 โ”‚ Create Cursor Rules Implementation โ”‚ โœ“ done โ”‚ medium โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 13.1 โ”‚ โ””โ”€ Set up .cursor Directory Structur โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 13.2 โ”‚ โ””โ”€ Create dev_workflow.mdc Documenta โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 13.3 โ”‚ โ””โ”€ Implement cursor_rules.mdc โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 13.4 โ”‚ โ””โ”€ Add self_improve.mdc Documentatio โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 13.5 โ”‚ โ””โ”€ Create Cursor AI Integration Docu โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 14 โ”‚ Develop Agent Workflow Guidelines โ”‚ โœ“ done โ”‚ medium โ”‚ 13 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 14.1 โ”‚ โ””โ”€ Document Task Discovery Workflow โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 14.2 โ”‚ โ””โ”€ Implement Task Selection Algorith โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 14.3 โ”‚ โ””โ”€ Create Implementation Guidance Ge โ”‚ โœ“ done โ”‚ - โ”‚ 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 14.4 โ”‚ โ””โ”€ Develop Verification Procedure Fr โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 14.5 โ”‚ โ””โ”€ Implement Dynamic Task Prioritiza โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 15 โ”‚ Optimize Agent Integration with Curs โ”‚ โœ“ done โ”‚ medium โ”‚ 14 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 15.1 โ”‚ โ””โ”€ Document Existing Agent Interacti โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 15.2 โ”‚ โ””โ”€ Enhance Integration Between Curso โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 15.3 โ”‚ โ””โ”€ Optimize Command Responses for Ag โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 15.4 โ”‚ โ””โ”€ Improve Agent Workflow Documentat โ”‚ โœ“ done โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 15.5 โ”‚ โ””โ”€ Add Agent-Specific Features to Ex โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 15.6 โ”‚ โ””โ”€ Create Agent Usage Examples and P โ”‚ โœ“ done โ”‚ - โ”‚ 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 16 โ”‚ Create Configuration Management Syst โ”‚ โœ“ done โ”‚ high โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 16.1 โ”‚ โ””โ”€ Implement Environment Variable Lo โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 16.2 โ”‚ โ””โ”€ Implement .env File Support โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 16.3 โ”‚ โ””โ”€ Implement Configuration Validatio โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 16.4 โ”‚ โ””โ”€ Create Configuration Defaults and โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 16.5 โ”‚ โ””โ”€ Create .env.example Template โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 16.6 โ”‚ โ””โ”€ Implement Secure API Key Handling โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 17 โ”‚ Implement Comprehensive Logging Syst โ”‚ โœ“ done โ”‚ medium โ”‚ 16 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 17.1 โ”‚ โ””โ”€ Implement Core Logging Framework โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 17.2 โ”‚ โ””โ”€ Implement Configurable Output Des โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 17.3 โ”‚ โ””โ”€ Implement Command and API Interac โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 17.4 โ”‚ โ””โ”€ Implement Error Tracking and Perf โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 17.5 โ”‚ โ””โ”€ Implement Log File Rotation and M โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 18 โ”‚ Create Comprehensive User Documentat โ”‚ โœ“ done โ”‚ medium โ”‚ 1, 3, 4, 5, 6, 7, 11, โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 18.1 โ”‚ โ””โ”€ Create Detailed README with Insta โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 18.2 โ”‚ โ””โ”€ Develop Command Reference Documen โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 18.3 โ”‚ โ””โ”€ Create Configuration and Environm โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 18.4 โ”‚ โ””โ”€ Develop Example Workflows and Use โ”‚ โœ“ done โ”‚ - โ”‚ 3, 6 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 18.5 โ”‚ โ””โ”€ Create Troubleshooting Guide and โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 18.6 โ”‚ โ””โ”€ Develop API Integration and Exten โ”‚ โœ“ done โ”‚ - โ”‚ 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 19 โ”‚ Implement Error Handling and Recover โ”‚ โœ“ done โ”‚ high โ”‚ 1, 3, 5, 9, 16, 17 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 19.1 โ”‚ โ””โ”€ Define Error Message Format and S โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 19.2 โ”‚ โ””โ”€ Implement API Error Handling with โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 19.3 โ”‚ โ””โ”€ Develop File System Error Recover โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 19.4 โ”‚ โ””โ”€ Enhance Data Validation with Deta โ”‚ โœ“ done โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 19.5 โ”‚ โ””โ”€ Implement Command Syntax Error Ha โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 19.6 โ”‚ โ””โ”€ Develop System State Recovery Aft โ”‚ โœ“ done โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 20 โ”‚ Create Token Usage Tracking and Cost โ”‚ โœ“ done โ”‚ medium โ”‚ 5, 9, 17 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 20.1 โ”‚ โ””โ”€ Implement Token Usage Tracking fo โ”‚ โœ“ done โ”‚ - โ”‚ 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 20.2 โ”‚ โ””โ”€ Develop Configurable Usage Limits โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 20.3 โ”‚ โ””โ”€ Implement Token Usage Reporting a โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 20.4 โ”‚ โ””โ”€ Optimize Token Usage in Prompts โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 20.5 โ”‚ โ””โ”€ Develop Token Usage Alert System โ”‚ โœ“ done โ”‚ - โ”‚ 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 21 โ”‚ Refactor dev.js into Modular Compone โ”‚ โœ“ done โ”‚ high โ”‚ 3, 16, 17 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 21.1 โ”‚ โ””โ”€ Analyze Current dev.js Structure โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 21.2 โ”‚ โ””โ”€ Create Core Module Structure and โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 21.3 โ”‚ โ””โ”€ Implement Core Module Functionali โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 21.4 โ”‚ โ””โ”€ Implement Error Handling and Comp โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 21.5 โ”‚ โ””โ”€ Test, Document, and Finalize Modu โ”‚ โœ“ done โ”‚ - โ”‚ 21.4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 22 โ”‚ Create Comprehensive Test Suite for โ”‚ โœ“ done โ”‚ high โ”‚ 21 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 22.1 โ”‚ โ””โ”€ Set Up Jest Testing Environment โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 22.2 โ”‚ โ””โ”€ Implement Unit Tests for Core Com โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 22.3 โ”‚ โ””โ”€ Develop Integration and End-to-En โ”‚ x deferred โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23 โ”‚ Complete MCP Server Implementation f โ”‚ โœ“ done โ”‚ medium โ”‚ 22 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.1 โ”‚ โ””โ”€ Create Core MCP Server Module and โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.2 โ”‚ โ””โ”€ Implement Context Management Syst โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.3 โ”‚ โ””โ”€ Implement MCP Endpoints and API H โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.6 โ”‚ โ””โ”€ Refactor MCP Server to Leverage M โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.8 โ”‚ โ””โ”€ Implement Direct Function Imports โ”‚ โœ“ done โ”‚ - โ”‚ 23.13 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.9 โ”‚ โ””โ”€ Implement Context Management and โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.10 โ”‚ โ””โ”€ Enhance Tool Registration and Res โ”‚ โœ“ done โ”‚ - โ”‚ 1, 23.8 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.11 โ”‚ โ””โ”€ Implement Comprehensive Error Han โ”‚ โœ“ done โ”‚ - โ”‚ 23.1, 23.3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.12 โ”‚ โ””โ”€ Implement Structured Logging Syst โ”‚ โœ“ done โ”‚ - โ”‚ 23.1, 23.3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.13 โ”‚ โ””โ”€ Create Testing Framework and Test โ”‚ โœ“ done โ”‚ - โ”‚ 23.1, 23.3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.14 โ”‚ โ””โ”€ Add MCP.json to the Init Workflow โ”‚ โœ“ done โ”‚ - โ”‚ 23.1, 23.3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.15 โ”‚ โ””โ”€ Implement SSE Support for Real-ti โ”‚ โœ“ done โ”‚ - โ”‚ 23.1, 23.3, 23.11 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.16 โ”‚ โ””โ”€ Implement parse-prd MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.17 โ”‚ โ””โ”€ Implement update MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.18 โ”‚ โ””โ”€ Implement update-task MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.19 โ”‚ โ””โ”€ Implement update-subtask MCP comm โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.20 โ”‚ โ””โ”€ Implement generate MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.21 โ”‚ โ””โ”€ Implement set-status MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.22 โ”‚ โ””โ”€ Implement show-task MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.23 โ”‚ โ””โ”€ Implement next-task MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.24 โ”‚ โ””โ”€ Implement expand-task MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.25 โ”‚ โ””โ”€ Implement add-task MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.26 โ”‚ โ””โ”€ Implement add-subtask MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.27 โ”‚ โ””โ”€ Implement remove-subtask MCP comm โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.28 โ”‚ โ””โ”€ Implement analyze MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.29 โ”‚ โ””โ”€ Implement clear-subtasks MCP comm โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.30 โ”‚ โ””โ”€ Implement expand-all MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.31 โ”‚ โ””โ”€ Create Core Direct Function Struc โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.32 โ”‚ โ””โ”€ Refactor Existing Direct Function โ”‚ โœ“ done โ”‚ - โ”‚ 23.31 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.33 โ”‚ โ””โ”€ Implement Naming Convention Stand โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.34 โ”‚ โ””โ”€ Review functionality of all MCP d โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.35 โ”‚ โ””โ”€ Review commands.js to ensure all โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.36 โ”‚ โ””โ”€ Finish setting up addResearch in โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.37 โ”‚ โ””โ”€ Finish setting up addTemplates in โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.38 โ”‚ โ””โ”€ Implement robust project root han โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.39 โ”‚ โ””โ”€ Implement add-dependency MCP comm โ”‚ โœ“ done โ”‚ - โ”‚ 23.31 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.40 โ”‚ โ””โ”€ Implement remove-dependency MCP c โ”‚ โœ“ done โ”‚ - โ”‚ 23.31 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.41 โ”‚ โ””โ”€ Implement validate-dependencies M โ”‚ โœ“ done โ”‚ - โ”‚ 23.31, 23.39, 23.40 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.42 โ”‚ โ””โ”€ Implement fix-dependencies MCP co โ”‚ โœ“ done โ”‚ - โ”‚ 23.31, 23.41 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.43 โ”‚ โ””โ”€ Implement complexity-report MCP c โ”‚ โœ“ done โ”‚ - โ”‚ 23.31 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.44 โ”‚ โ””โ”€ Implement init MCP command โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.45 โ”‚ โ””โ”€ Support setting env variables thr โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 23.46 โ”‚ โ””โ”€ adjust rules so it prioritizes mc โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 24 โ”‚ Implement AI-Powered Test Generation โ”‚ โ—‹ pending โ”‚ high โ”‚ 22 โ”‚ โ— 7 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 24.1 โ”‚ โ””โ”€ Create command structure for 'gen โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 24.2 โ”‚ โ””โ”€ Implement AI prompt construction โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 24.3 โ”‚ โ””โ”€ Implement test file generation an โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 24.4 โ”‚ โ””โ”€ Implement MCP tool integration fo โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 24.5 โ”‚ โ””โ”€ Add testing framework configurati โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 25 โ”‚ Implement 'add-subtask' Command for โ”‚ โœ“ done โ”‚ medium โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 25.1 โ”‚ โ””โ”€ Update Data Model to Support Pare โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 25.2 โ”‚ โ””โ”€ Implement Core addSubtask Functio โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 25.3 โ”‚ โ””โ”€ Implement add-subtask Command in โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 25.4 โ”‚ โ””โ”€ Create Unit Test for add-subtask โ”‚ โœ“ done โ”‚ - โ”‚ 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 25.5 โ”‚ โ””โ”€ Implement remove-subtask Command โ”‚ โœ“ done โ”‚ - โ”‚ 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 26 โ”‚ Implement Context Foundation for AI โ”‚ โ—‹ pending โ”‚ high โ”‚ 5, 6, 7 โ”‚ โ— 6 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 26.1 โ”‚ โ””โ”€ Implement --context-file Flag for โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 26.2 โ”‚ โ””โ”€ Implement --context Flag for AI C โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 26.3 โ”‚ โ””โ”€ Implement Cursor Rules Integratio โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 26.4 โ”‚ โ””โ”€ Implement Basic Context File Extr โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 27 โ”‚ Implement Context Enhancements for A โ”‚ โ—‹ pending โ”‚ high โ”‚ 26 โ”‚ โ— 7 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 27.1 โ”‚ โ””โ”€ Implement Code Context Extraction โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 27.2 โ”‚ โ””โ”€ Implement Task History Context In โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 27.3 โ”‚ โ””โ”€ Add PRD Context Integration โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 27.4 โ”‚ โ””โ”€ Create Standardized Context Forma โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 28 โ”‚ Implement Advanced ContextManager Sy โ”‚ โ—‹ pending โ”‚ high โ”‚ 26, 27 โ”‚ โ— 8 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 28.1 โ”‚ โ””โ”€ Implement Core ContextManager Cla โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 28.2 โ”‚ โ””โ”€ Develop Context Optimization Pipe โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 28.3 โ”‚ โ””โ”€ Create Command Interface Enhancem โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 28.4 โ”‚ โ””โ”€ Integrate ContextManager with AI โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 28.5 โ”‚ โ””โ”€ Implement Performance Monitoring โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 29 โ”‚ Update Claude 3.7 Sonnet Integration โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 30 โ”‚ Enhance parse-prd Command to Support โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 31 โ”‚ Add Config Flag Support to task-mast โ”‚ โœ“ done โ”‚ low โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32 โ”‚ Implement "learn" Command for Automa โ”‚ x deferred โ”‚ high โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.1 โ”‚ โ””โ”€ Create Initial File Structure โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.2 โ”‚ โ””โ”€ Implement Cursor Path Helper โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.3 โ”‚ โ””โ”€ Create Chat History Analyzer Base โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.4 โ”‚ โ””โ”€ Implement Chat History Extraction โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.5 โ”‚ โ””โ”€ Create CursorRulesManager Base โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.6 โ”‚ โ””โ”€ Implement Template Validation โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.7 โ”‚ โ””โ”€ Add Rule Categorization Logic โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.8 โ”‚ โ””โ”€ Implement Pattern Analysis โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.9 โ”‚ โ””โ”€ Create AI Prompt Builder โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.10 โ”‚ โ””โ”€ Implement Learn Command Core โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.11 โ”‚ โ””โ”€ Add Auto-trigger Support โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.12 โ”‚ โ””โ”€ Implement CLI Integration โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.13 โ”‚ โ””โ”€ Add Progress Logging โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.14 โ”‚ โ””โ”€ Implement Error Recovery โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 32.15 โ”‚ โ””โ”€ Add Performance Optimization โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 33 โ”‚ Create and Integrate Windsurf Rules โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 34 โ”‚ Implement updateTask Command for Sin โ”‚ โœ“ done โ”‚ high โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 34.1 โ”‚ โ””โ”€ Create updateTaskById function in โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 34.2 โ”‚ โ””โ”€ Implement updateTask command in c โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 34.3 โ”‚ โ””โ”€ Add comprehensive error handling โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 34.4 โ”‚ โ””โ”€ Write comprehensive tests for upd โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 34.5 โ”‚ โ””โ”€ Update CLI documentation and help โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 35 โ”‚ Integrate Grok3 API for Research Cap โ”‚ x cancelled โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 36 โ”‚ Add Ollama Support for AI Services a โ”‚ x deferred โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 37 โ”‚ Add Gemini Support for Main AI Servi โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 38 โ”‚ Implement Version Check System with โ”‚ โœ“ done โ”‚ high โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 39 โ”‚ Update Project Licensing to Dual Lic โ”‚ โœ“ done โ”‚ high โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 39.1 โ”‚ โ””โ”€ Remove MIT License and Create Dua โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 39.2 โ”‚ โ””โ”€ Update Source Code License Header โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 39.3 โ”‚ โ””โ”€ Update Documentation and Create L โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 40 โ”‚ Implement 'plan' Command for Task Im โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 5 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 40.1 โ”‚ โ””โ”€ Retrieve Task Content โ”‚ โ–บ in-progress โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 40.2 โ”‚ โ””โ”€ Generate Implementation Plan with โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 40.3 โ”‚ โ””โ”€ Format Plan in XML โ”‚ โ—‹ pending โ”‚ - โ”‚ 2, 40.2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 40.4 โ”‚ โ””โ”€ Error Handling and Output โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 41 โ”‚ Implement Visual Task Dependency Gra โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 8 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 41.1 โ”‚ โ””โ”€ CLI Command Setup โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 41.2 โ”‚ โ””โ”€ Graph Layout Algorithms โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 41.3 โ”‚ โ””โ”€ ASCII/Unicode Rendering Engine โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 41.4 โ”‚ โ””โ”€ Color Coding Support โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 41.5 โ”‚ โ””โ”€ Circular Dependency Detection โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 41.6 โ”‚ โ””โ”€ Filtering and Search Functionalit โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 41.7 โ”‚ โ””โ”€ Accessibility Features โ”‚ โ—‹ pending โ”‚ - โ”‚ 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 41.8 โ”‚ โ””โ”€ Performance Optimization โ”‚ โ—‹ pending โ”‚ - โ”‚ 2, 3, 4, 5, 6 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 41.9 โ”‚ โ””โ”€ Documentation โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2, 3, 4, 5, 6, 7, โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 41.10 โ”‚ โ””โ”€ Testing and Validation โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2, 3, 4, 5, 6, 7, โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 42 โ”‚ Implement MCP-to-MCP Communication P โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 9 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 42.42-1 โ”‚ โ””โ”€ Define MCP-to-MCP communication p โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 42.42-2 โ”‚ โ””โ”€ Implement adapter pattern for MCP โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 42.42-3 โ”‚ โ””โ”€ Develop client module for MCP too โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 42.42-4 โ”‚ โ””โ”€ Provide reference implementation โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 42.42-5 โ”‚ โ””โ”€ Add support for solo/local and mu โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 42.42-6 โ”‚ โ””โ”€ Update core modules to support dy โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 42.42-7 โ”‚ โ””โ”€ Document protocol and mode-switch โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 42.42-8 โ”‚ โ””โ”€ Update terminology to reflect MCP โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 43 โ”‚ Add Research Flag to Add-Task Comman โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 44 โ”‚ Implement Task Automation with Webho โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 8 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 44.1 โ”‚ โ””โ”€ Design webhook registration API e โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 44.2 โ”‚ โ””โ”€ Implement webhook authentication โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 44.3 โ”‚ โ””โ”€ Create event trigger definition i โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 44.4 โ”‚ โ””โ”€ Build event processing and queuin โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 44.5 โ”‚ โ””โ”€ Develop webhook delivery and retr โ”‚ โ—‹ pending โ”‚ - โ”‚ 2, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 44.6 โ”‚ โ””โ”€ Implement comprehensive error han โ”‚ โ—‹ pending โ”‚ - โ”‚ 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 44.7 โ”‚ โ””โ”€ Create webhook testing and simula โ”‚ โ—‹ pending โ”‚ - โ”‚ 3, 5, 6 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 45 โ”‚ Implement GitHub Issue Import Featur โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 6 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 45.1 โ”‚ โ””โ”€ Design GitHub API integration arc โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 45.2 โ”‚ โ””โ”€ Implement GitHub URL parsing and โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 45.3 โ”‚ โ””โ”€ Develop GitHub API client for iss โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 45.4 โ”‚ โ””โ”€ Create task formatter for GitHub โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 45.5 โ”‚ โ””โ”€ Implement end-to-end import flow โ”‚ โ—‹ pending โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 46 โ”‚ Implement ICE Analysis Command for T โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 7 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 46.1 โ”‚ โ””โ”€ Design ICE scoring algorithm โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 46.2 โ”‚ โ””โ”€ Implement AI integration for ICE โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 46.3 โ”‚ โ””โ”€ Create report file generator โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 46.4 โ”‚ โ””โ”€ Implement CLI rendering for ICE a โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 46.5 โ”‚ โ””โ”€ Integrate with existing complexit โ”‚ โ—‹ pending โ”‚ - โ”‚ 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 47 โ”‚ Enhance Task Suggestion Actions Card โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 6 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 47.1 โ”‚ โ””โ”€ Design Task Expansion UI Componen โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 47.2 โ”‚ โ””โ”€ Implement State Management for Ta โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 47.3 โ”‚ โ””โ”€ Build Context Addition Functional โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 47.4 โ”‚ โ””โ”€ Develop Task Management Controls โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 47.5 โ”‚ โ””โ”€ Integrate with Existing Task Syst โ”‚ โ—‹ pending โ”‚ - โ”‚ 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 47.6 โ”‚ โ””โ”€ Test and Optimize User Experience โ”‚ โ—‹ pending โ”‚ - โ”‚ 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 48 โ”‚ Refactor Prompts into Centralized St โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 4 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 48.1 โ”‚ โ””โ”€ Create prompts directory structur โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 48.2 โ”‚ โ””โ”€ Extract prompts into individual f โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 48.3 โ”‚ โ””โ”€ Update functions to import prompt โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 49 โ”‚ Implement Code Quality Analysis Comm โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 8 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 49.1 โ”‚ โ””โ”€ Design pattern recognition algori โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 49.2 โ”‚ โ””โ”€ Implement best practice verificat โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 49.3 โ”‚ โ””โ”€ Develop AI integration for code a โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 49.4 โ”‚ โ””โ”€ Create recommendation generation โ”‚ โ—‹ pending โ”‚ - โ”‚ 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 49.5 โ”‚ โ””โ”€ Implement task creation functiona โ”‚ โ—‹ pending โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 49.6 โ”‚ โ””โ”€ Create comprehensive reporting in โ”‚ โ—‹ pending โ”‚ - โ”‚ 4, 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 50 โ”‚ Implement Test Coverage Tracking Sys โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 9 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 50.1 โ”‚ โ””โ”€ Design and implement tests.json d โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 50.2 โ”‚ โ””โ”€ Develop coverage report parser an โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 50.3 โ”‚ โ””โ”€ Build coverage tracking and updat โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 50.4 โ”‚ โ””โ”€ Implement CLI commands for covera โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 50.5 โ”‚ โ””โ”€ Develop AI-powered test generatio โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2, 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 51 โ”‚ Implement Perplexity Research Comman โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 6 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 51.1 โ”‚ โ””โ”€ Create Perplexity API Client Serv โ”‚ x cancelled โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 51.2 โ”‚ โ””โ”€ Implement Task Context Extraction โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 51.3 โ”‚ โ””โ”€ Build Research Command CLI Interf โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 51.4 โ”‚ โ””โ”€ Implement Results Processing and โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 51.5 โ”‚ โ””โ”€ Implement Caching and Results Man โ”‚ x cancelled โ”‚ - โ”‚ 1, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 51.6 โ”‚ โ””โ”€ Implement Project Context Generat โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 51.7 โ”‚ โ””โ”€ Create REPL Command System โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 51.8 โ”‚ โ””โ”€ Integrate with AI Services Unifie โ”‚ โ—‹ pending โ”‚ - โ”‚ 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 52 โ”‚ Implement Task Suggestion Command fo โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 6 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 52.1 โ”‚ โ””โ”€ Design data collection mechanism โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 52.2 โ”‚ โ””โ”€ Implement AI integration for task โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 52.3 โ”‚ โ””โ”€ Build interactive CLI interface f โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 52.4 โ”‚ โ””โ”€ Implement suggestion selection an โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 52.5 โ”‚ โ””โ”€ Add configuration options and fla โ”‚ โ—‹ pending โ”‚ - โ”‚ 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 53 โ”‚ Implement Subtask Suggestion Feature โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 6 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 53.1 โ”‚ โ””โ”€ Implement parent task validation โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 53.2 โ”‚ โ””โ”€ Build context gathering mechanism โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 53.3 โ”‚ โ””โ”€ Develop AI suggestion logic for s โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 53.4 โ”‚ โ””โ”€ Create interactive CLI interface โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 53.5 โ”‚ โ””โ”€ Implement subtask linking functio โ”‚ โ—‹ pending โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 53.6 โ”‚ โ””โ”€ Perform comprehensive testing โ”‚ โ—‹ pending โ”‚ - โ”‚ 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 54 โ”‚ Add Research Flag to Add-Task Comman โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 55 โ”‚ Implement Positional Arguments Suppo โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 5 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 55.1 โ”‚ โ””โ”€ Analyze current CLI argument pars โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 55.2 โ”‚ โ””โ”€ Design positional argument specif โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 55.3 โ”‚ โ””โ”€ Implement core positional argumen โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 55.4 โ”‚ โ””โ”€ Handle edge cases and error condi โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 55.5 โ”‚ โ””โ”€ Update documentation and create u โ”‚ โ—‹ pending โ”‚ - โ”‚ 2, 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 56 โ”‚ Refactor Task-Master Files into Node โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 57 โ”‚ Enhance Task-Master CLI User Experie โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 7 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 57.1 โ”‚ โ””โ”€ Implement Configurable Log Levels โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 57.2 โ”‚ โ””โ”€ Design Terminal Color Scheme and โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 57.3 โ”‚ โ””โ”€ Implement Progress Indicators and โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 57.4 โ”‚ โ””โ”€ Develop Interactive Selection Men โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 57.5 โ”‚ โ””โ”€ Design Tabular and Structured Out โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 57.6 โ”‚ โ””โ”€ Create Help System and Interactiv โ”‚ โ—‹ pending โ”‚ - โ”‚ 2, 4, 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 58 โ”‚ Implement Elegant Package Update Mec โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 59 โ”‚ Remove Manual Package.json Modificat โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 59.1 โ”‚ โ””โ”€ Conduct Code Audit for Dependency โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 59.2 โ”‚ โ””โ”€ Remove Manual Dependency Modifica โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 59.3 โ”‚ โ””โ”€ Update npm Dependencies โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 59.4 โ”‚ โ””โ”€ Update Initialization and Install โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 59.5 โ”‚ โ””โ”€ Update Documentation โ”‚ โœ“ done โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 59.6 โ”‚ โ””โ”€ Perform Regression Testing โ”‚ โœ“ done โ”‚ - โ”‚ 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 60 โ”‚ Implement Mentor System with Round-T โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 8 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 60.1 โ”‚ โ””โ”€ Design Mentor System Architecture โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 60.2 โ”‚ โ””โ”€ Implement Mentor Profile Manageme โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 60.3 โ”‚ โ””โ”€ Develop Round-Table Discussion Fr โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 60.4 โ”‚ โ””โ”€ Implement LLM Integration for AI โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 60.5 โ”‚ โ””โ”€ Build Discussion Output Formatter โ”‚ โ—‹ pending โ”‚ - โ”‚ 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 60.6 โ”‚ โ””โ”€ Integrate Mentor System with Task โ”‚ โ—‹ pending โ”‚ - โ”‚ 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 60.7 โ”‚ โ””โ”€ Test and Optimize Round-Table Dis โ”‚ โ—‹ pending โ”‚ - โ”‚ 4, 5, 6 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61 โ”‚ Implement Flexible AI Model Manageme โ”‚ โœ“ done โ”‚ high โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.1 โ”‚ โ””โ”€ Create Configuration Management M โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.2 โ”‚ โ””โ”€ Implement CLI Command Parser for โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.3 โ”‚ โ””โ”€ Integrate Vercel AI SDK and Creat โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.4 โ”‚ โ””โ”€ Develop Centralized AI Services M โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.5 โ”‚ โ””โ”€ Implement Environment Variable Ma โ”‚ โœ“ done โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.6 โ”‚ โ””โ”€ Implement Model Listing Command โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.7 โ”‚ โ””โ”€ Implement Model Setting Commands โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 4, 6 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.8 โ”‚ โ””โ”€ Update Main Task Processing Logic โ”‚ โœ“ done โ”‚ - โ”‚ 4, 5, 61.18 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.9 โ”‚ โ””โ”€ Update Research Processing Logic โ”‚ โœ“ done โ”‚ - โ”‚ 4, 5, 8, 61.18 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.10 โ”‚ โ””โ”€ Create Comprehensive Documentatio โ”‚ โœ“ done โ”‚ - โ”‚ 6, 7, 8, 9 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.11 โ”‚ โ””โ”€ Refactor PRD Parsing to use gener โ”‚ โœ“ done โ”‚ - โ”‚ 61.23 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.12 โ”‚ โ””โ”€ Refactor Basic Subtask Generation โ”‚ โœ“ done โ”‚ - โ”‚ 61.23 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.13 โ”‚ โ””โ”€ Refactor Research Subtask Generat โ”‚ โœ“ done โ”‚ - โ”‚ 61.23 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.14 โ”‚ โ””โ”€ Refactor Research Task Descriptio โ”‚ โœ“ done โ”‚ - โ”‚ 61.23 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.15 โ”‚ โ””โ”€ Refactor Complexity Analysis AI C โ”‚ โœ“ done โ”‚ - โ”‚ 61.23 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.16 โ”‚ โ””โ”€ Refactor Task Addition AI Call to โ”‚ โœ“ done โ”‚ - โ”‚ 61.23 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.17 โ”‚ โ””โ”€ Refactor General Chat/Update AI C โ”‚ โœ“ done โ”‚ - โ”‚ 61.23 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.18 โ”‚ โ””โ”€ Refactor Callers of AI Parsing Ut โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.19 โ”‚ โ””โ”€ Refactor `updateSubtaskById` AI C โ”‚ โœ“ done โ”‚ - โ”‚ 61.23 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.20 โ”‚ โ””โ”€ Implement `anthropic.js` Provider โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.21 โ”‚ โ””โ”€ Implement `perplexity.js` Provide โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.22 โ”‚ โ””โ”€ Implement `openai.js` Provider Mo โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.23 โ”‚ โ””โ”€ Implement Conditional Provider Lo โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.24 โ”‚ โ””โ”€ Implement `google.js` Provider Mo โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.25 โ”‚ โ””โ”€ Implement `ollama.js` Provider Mo โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.26 โ”‚ โ””โ”€ Implement `mistral.js` Provider M โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.27 โ”‚ โ””โ”€ Implement `azure.js` Provider Mod โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.28 โ”‚ โ””โ”€ Implement `openrouter.js` Provide โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.29 โ”‚ โ””โ”€ Implement `xai.js` Provider Modul โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.30 โ”‚ โ””โ”€ Update Configuration Management f โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.31 โ”‚ โ””โ”€ Implement Integration Tests for U โ”‚ โœ“ done โ”‚ - โ”‚ 61.18 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.32 โ”‚ โ””โ”€ Update Documentation for New AI A โ”‚ โœ“ done โ”‚ - โ”‚ 61.31 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.33 โ”‚ โ””โ”€ Cleanup Old AI Service Files โ”‚ โœ“ done โ”‚ - โ”‚ 61.31, 61.32 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.34 โ”‚ โ””โ”€ Audit and Standardize Env Variabl โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.35 โ”‚ โ””โ”€ Refactor add-task.js for Unified โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.36 โ”‚ โ””โ”€ Refactor analyze-task-complexity. โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.37 โ”‚ โ””โ”€ Refactor expand-task.js for Unifi โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.38 โ”‚ โ””โ”€ Refactor expand-all-tasks.js for โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.39 โ”‚ โ””โ”€ Refactor get-subtasks-from-ai.js โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.40 โ”‚ โ””โ”€ Refactor update-task-by-id.js for โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.41 โ”‚ โ””โ”€ Refactor update-tasks.js for Unif โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.42 โ”‚ โ””โ”€ Remove all unused imports โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.43 โ”‚ โ””โ”€ Remove all unnecessary console lo โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.44 โ”‚ โ””โ”€ Add setters for temperature, max โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 61.45 โ”‚ โ””โ”€ Add support for Bedrock provider โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 62 โ”‚ Add --simple Flag to Update Commands โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 4 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 62.1 โ”‚ โ””โ”€ Update command parsers to recogni โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 62.2 โ”‚ โ””โ”€ Implement conditional logic to by โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 62.3 โ”‚ โ””โ”€ Format user input with timestamp โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 62.4 โ”‚ โ””โ”€ Add visual indicator for manual u โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 62.5 โ”‚ โ””โ”€ Implement storage of simple updat โ”‚ โ—‹ pending โ”‚ - โ”‚ 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 62.6 โ”‚ โ””โ”€ Update help documentation for the โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 62.7 โ”‚ โ””โ”€ Implement integration tests for t โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2, 3, 4, 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 62.8 โ”‚ โ””โ”€ Perform final validation and docu โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2, 3, 4, 5, 6, 7 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 63 โ”‚ Add pnpm Support for the Taskmaster โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ โ— 5 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 63.1 โ”‚ โ””โ”€ Update Documentation for pnpm Sup โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 63.2 โ”‚ โ””โ”€ Ensure Package Scripts Compatibil โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 63.3 โ”‚ โ””โ”€ Generate and Validate pnpm Lockfi โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 63.4 โ”‚ โ””โ”€ Test Taskmaster Installation and โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 63.5 โ”‚ โ””โ”€ Integrate pnpm into CI/CD Pipelin โ”‚ โœ“ done โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 63.6 โ”‚ โ””โ”€ Verify Installation UI/Website Co โ”‚ โœ“ done โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 63.7 โ”‚ โ””โ”€ Test init.js Script with pnpm โ”‚ โœ“ done โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 63.8 โ”‚ โ””โ”€ Verify Binary Links with pnpm โ”‚ โœ“ done โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 64 โ”‚ Add Yarn Support for Taskmaster Inst โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ โ— 5 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 64.1 โ”‚ โ””โ”€ Update package.json for Yarn Comp โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 64.2 โ”‚ โ””โ”€ Add Yarn-Specific Configuration F โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 64.3 โ”‚ โ””โ”€ Test and Fix Yarn Compatibility f โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 64.4 โ”‚ โ””โ”€ Update Documentation for Yarn Ins โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 64.5 โ”‚ โ””โ”€ Implement and Test Package Manage โ”‚ โœ“ done โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 64.6 โ”‚ โ””โ”€ Verify Installation UI/Website Co โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 64.7 โ”‚ โ””โ”€ Test init.js Script with Yarn โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 64.8 โ”‚ โ””โ”€ Verify Binary Links with Yarn โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 64.9 โ”‚ โ””โ”€ Test Website Account Setup with Y โ”‚ โœ“ done โ”‚ - โ”‚ 6 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 65 โ”‚ Add Bun Support for Taskmaster Insta โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ โ— 6 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 65.1 โ”‚ โ””โ”€ Research Bun compatibility requir โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 65.2 โ”‚ โ””โ”€ Update installation scripts for B โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 65.3 โ”‚ โ””โ”€ Create Bun-specific installation โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 65.4 โ”‚ โ””โ”€ Test Taskmaster installation with โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 65.5 โ”‚ โ””โ”€ Test Taskmaster operation with Bu โ”‚ โœ“ done โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 65.6 โ”‚ โ””โ”€ Update documentation for Bun supp โ”‚ โœ“ done โ”‚ - โ”‚ 4, 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 66 โ”‚ Support Status Filtering in Show Com โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 67 โ”‚ Add CLI JSON output and Cursor keybi โ”‚ โ—‹ pending โ”‚ high โ”‚ None โ”‚ โ— 5 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 67.1 โ”‚ โ””โ”€ Implement Core JSON Output Logic โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 67.2 โ”‚ โ””โ”€ Extend JSON Output to All Relevan โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 67.3 โ”‚ โ””โ”€ Create `install-keybindings` Comm โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 67.4 โ”‚ โ””โ”€ Implement Keybinding File Handlin โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 67.5 โ”‚ โ””โ”€ Add Taskmaster Keybindings, Preve โ”‚ โ—‹ pending โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 68 โ”‚ Ability to create tasks without pars โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ โ— 3 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 68.1 โ”‚ โ””โ”€ Design task creation form without โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 68.2 โ”‚ โ””โ”€ Implement task saving functionali โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 69 โ”‚ Enhance Analyze Complexity for Speci โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ โ— 7 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 69.1 โ”‚ โ””โ”€ Modify core complexity analysis l โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 69.2 โ”‚ โ””โ”€ Update CLI interface for task-spe โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 69.3 โ”‚ โ””โ”€ Integrate task-specific analysis โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 69.4 โ”‚ โ””โ”€ Create comprehensive tests for ta โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 70 โ”‚ Implement 'diagram' command for Merm โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 6 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 70.1 โ”‚ โ””โ”€ Design the 'diagram' command inte โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 70.2 โ”‚ โ””โ”€ Implement Mermaid diagram generat โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 70.3 โ”‚ โ””โ”€ Develop output handling mechanism โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 70.4 โ”‚ โ””โ”€ Create documentation and examples โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 71 โ”‚ Add Model-Specific maxTokens Overrid โ”‚ โœ“ done โ”‚ high โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 72 โ”‚ Implement PDF Generation for Project โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 7 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 72.1 โ”‚ โ””โ”€ Research and select PDF generatio โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 72.2 โ”‚ โ””โ”€ Design PDF template and layout โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 72.3 โ”‚ โ””โ”€ Implement project progress data c โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 72.4 โ”‚ โ””โ”€ Integrate with dependency visuali โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 72.5 โ”‚ โ””โ”€ Build PDF generation core functio โ”‚ โ—‹ pending โ”‚ - โ”‚ 2, 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 72.6 โ”‚ โ””โ”€ Create export options and command โ”‚ โ—‹ pending โ”‚ - โ”‚ 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 73 โ”‚ Implement Custom Model ID Support fo โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 74 โ”‚ PR Review: better-model-management โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 74.1 โ”‚ โ””โ”€ pull out logWrapper into utils โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 75 โ”‚ Integrate Google Search Grounding fo โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 5 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 75.1 โ”‚ โ””โ”€ Modify AI service layer to suppor โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 75.2 โ”‚ โ””โ”€ Implement conditional logic for r โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 75.3 โ”‚ โ””โ”€ Update supported models configura โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 75.4 โ”‚ โ””โ”€ Create end-to-end testing suite f โ”‚ โ—‹ pending โ”‚ - โ”‚ 1, 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 76 โ”‚ Develop E2E Test Framework for Taskm โ”‚ โ—‹ pending โ”‚ high โ”‚ None โ”‚ โ— 8 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 76.1 โ”‚ โ””โ”€ Design E2E Test Framework Archite โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 76.2 โ”‚ โ””โ”€ Implement FastMCP Server Launcher โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 76.3 โ”‚ โ””โ”€ Develop Message Protocol Handler โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 76.4 โ”‚ โ””โ”€ Create Request/Response Correlati โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 76.5 โ”‚ โ””โ”€ Build Test Assertion Framework โ”‚ โ—‹ pending โ”‚ - โ”‚ 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 76.6 โ”‚ โ””โ”€ Implement Test Cases โ”‚ โ—‹ pending โ”‚ - โ”‚ 2, 4, 5 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 76.7 โ”‚ โ””โ”€ Create CI Integration and Documen โ”‚ โ—‹ pending โ”‚ - โ”‚ 6 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77 โ”‚ Implement AI Usage Telemetry for Tas โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ โ— 7 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.1 โ”‚ โ””โ”€ Implement telemetry utility and d โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.2 โ”‚ โ””โ”€ Implement secure telemetry transm โ”‚ x deferred โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.3 โ”‚ โ””โ”€ Develop user consent and privacy โ”‚ x deferred โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.4 โ”‚ โ””โ”€ Integrate telemetry into Taskmast โ”‚ โœ“ done โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.5 โ”‚ โ””โ”€ Implement usage summary display โ”‚ โœ“ done โ”‚ - โ”‚ 1, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.6 โ”‚ โ””โ”€ Telemetry Integration for parse-p โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.7 โ”‚ โ””โ”€ Telemetry Integration for expand- โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.8 โ”‚ โ””โ”€ Telemetry Integration for expand- โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.9 โ”‚ โ””โ”€ Telemetry Integration for update- โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.10 โ”‚ โ””โ”€ Telemetry Integration for update- โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.11 โ”‚ โ””โ”€ Telemetry Integration for update- โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.12 โ”‚ โ””โ”€ Telemetry Integration for analyze โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.13 โ”‚ โ””โ”€ Update google.js for Telemetry Co โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.14 โ”‚ โ””โ”€ Update openai.js for Telemetry Co โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.15 โ”‚ โ””โ”€ Update openrouter.js for Telemetr โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.16 โ”‚ โ””โ”€ Update perplexity.js for Telemetr โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.17 โ”‚ โ””โ”€ Update xai.js for Telemetry Compa โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 77.18 โ”‚ โ””โ”€ Create dedicated telemetry transm โ”‚ โœ“ done โ”‚ - โ”‚ 1, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 80 โ”‚ Implement Unique User ID Generation โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 4 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 80.1 โ”‚ โ””โ”€ Create post-install script struct โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 80.2 โ”‚ โ””โ”€ Implement UUID generation functio โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 80.3 โ”‚ โ””โ”€ Develop config file handling logi โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 80.4 โ”‚ โ””โ”€ Integrate user ID generation with โ”‚ โ—‹ pending โ”‚ - โ”‚ 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 80.5 โ”‚ โ””โ”€ Add documentation and telemetry s โ”‚ โ—‹ pending โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 82 โ”‚ Update supported-models.json with to โ”‚ โ—‹ pending โ”‚ high โ”‚ None โ”‚ โ— 3 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 83 โ”‚ Update config-manager.js defaults an โ”‚ โ—‹ pending โ”‚ high โ”‚ 82 โ”‚ โ— 4 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 83.1 โ”‚ โ””โ”€ Update config-manager.js with spe โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 84 โ”‚ Implement token counting utility โ”‚ โ—‹ pending โ”‚ high โ”‚ 82 โ”‚ โ— 5 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 85 โ”‚ Update ai-services-unified.js for dy โ”‚ โ—‹ pending โ”‚ medium โ”‚ 83, 84 โ”‚ โ— 7 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 86 โ”‚ Update .taskmasterconfig schema and โ”‚ โ—‹ pending โ”‚ medium โ”‚ 83 โ”‚ โ— 6 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 87 โ”‚ Implement validation and error handl โ”‚ โ—‹ pending โ”‚ low โ”‚ 85 โ”‚ โ— 5 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 88 โ”‚ Enhance Add-Task Functionality to Co โ”‚ โœ“ done โ”‚ medium โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 88.1 โ”‚ โ””โ”€ Review Current Add-Task Implement โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 88.2 โ”‚ โ””โ”€ Modify Add-Task to Recursively An โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 88.3 โ”‚ โ””โ”€ Ensure Correct Order of Dependenc โ”‚ โœ“ done โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 88.4 โ”‚ โ””โ”€ Integrate with Existing Validatio โ”‚ โœ“ done โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 88.5 โ”‚ โ””โ”€ Optimize Performance for Large Pr โ”‚ โœ“ done โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 89 โ”‚ Introduce Prioritize Command with En โ”‚ โ—‹ pending โ”‚ medium โ”‚ None โ”‚ โ— 6 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 90 โ”‚ Implement Subtask Progress Analyzer โ”‚ โ—‹ pending โ”‚ medium โ”‚ 1, 3 โ”‚ โ— 8 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 91 โ”‚ Implement Move Command for Tasks and โ”‚ โœ“ done โ”‚ medium โ”‚ 1, 3 โ”‚ โ— 7 โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 91.1 โ”‚ โ””โ”€ Design and implement core move lo โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 91.2 โ”‚ โ””โ”€ Implement edge case handling โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 91.3 โ”‚ โ””โ”€ Update CLI interface for move com โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 91.4 โ”‚ โ””โ”€ Ensure data integrity during move โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 91.5 โ”‚ โ””โ”€ Create comprehensive test suite โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 91.6 โ”‚ โ””โ”€ Export and integrate the move fun โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 92 โ”‚ Implement Project Root Environment V โ”‚ ? review โ”‚ medium โ”‚ 1, 3, 17 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 92.1 โ”‚ โ””โ”€ Update configuration loader to ch โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 92.2 โ”‚ โ””โ”€ Add support for 'projectRoot' in โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 92.3 โ”‚ โ””โ”€ Refactor project root resolution โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 92.4 โ”‚ โ””โ”€ Update all MCP tools to use the n โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 92.5 โ”‚ โ””โ”€ Add comprehensive tests for the n โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 92.6 โ”‚ โ””โ”€ Update documentation with new con โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 92.7 โ”‚ โ””โ”€ Implement validation for project โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 92.8 โ”‚ โ””โ”€ Implement support for loading env โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 93 โ”‚ Implement Google Vertex AI Provider โ”‚ โ—‹ pending โ”‚ medium โ”‚ 19, 94 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 93.1 โ”‚ โ””โ”€ Create Google Vertex AI Provider โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 93.2 โ”‚ โ””โ”€ Integrate Vercel AI SDK Google Ve โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 93.3 โ”‚ โ””โ”€ Implement Provider Interface Meth โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 93.4 โ”‚ โ””โ”€ Handle Vertex AI Configuration an โ”‚ โ—‹ pending โ”‚ - โ”‚ 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 93.5 โ”‚ โ””โ”€ Update Exports, Documentation, an โ”‚ โ—‹ pending โ”‚ - โ”‚ 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 94 โ”‚ Implement Azure OpenAI Provider Inte โ”‚ โœ“ done โ”‚ medium โ”‚ 19, 26 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 94.1 โ”‚ โ””โ”€ Create Azure Provider Class โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 94.2 โ”‚ โ””โ”€ Implement Configuration Managemen โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 94.3 โ”‚ โ””โ”€ Update Provider Integration โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 94.4 โ”‚ โ””โ”€ Implement Azure-Specific Error Ha โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 94.5 โ”‚ โ””โ”€ Update Documentation โ”‚ โœ“ done โ”‚ - โ”‚ 1, 2, 3, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 95 โ”‚ Implement .taskmaster Directory Stru โ”‚ โœ“ done โ”‚ high โ”‚ 1, 3, 4, 17 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 95.1 โ”‚ โ””โ”€ Create .taskmaster directory stru โ”‚ โœ“ done โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 95.2 โ”‚ โ””โ”€ Update Task Master code for new u โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 95.3 โ”‚ โ””โ”€ Update task file generation syste โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 95.4 โ”‚ โ””โ”€ Implement backward compatibility โ”‚ โœ“ done โ”‚ - โ”‚ 2, 3 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 95.5 โ”‚ โ””โ”€ Create migration command for user โ”‚ โœ“ done โ”‚ - โ”‚ 1, 4 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 95.6 โ”‚ โ””โ”€ Update project initialization pro โ”‚ โœ“ done โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 95.7 โ”‚ โ””โ”€ Update PRD and report file handli โ”‚ โœ“ done โ”‚ - โ”‚ 2, 6 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 95.8 โ”‚ โ””โ”€ Update documentation and create m โ”‚ โœ“ done โ”‚ - โ”‚ 5, 6, 7 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 95.9 โ”‚ โ””โ”€ Add templates directory support โ”‚ โœ“ done โ”‚ - โ”‚ 2, 6 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 95.10 โ”‚ โ””โ”€ Verify clean user project directo โ”‚ โœ“ done โ”‚ - โ”‚ 8, 9 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 96 โ”‚ Create Export Command for On-Demand โ”‚ โ—‹ pending โ”‚ medium โ”‚ 2, 4, 95 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 96.1 โ”‚ โ””โ”€ Remove Automatic Task File Genera โ”‚ โ—‹ pending โ”‚ - โ”‚ None โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 96.2 โ”‚ โ””โ”€ Implement Export Command Infrastr โ”‚ โ—‹ pending โ”‚ - โ”‚ 1 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 96.3 โ”‚ โ””โ”€ Implement Comprehensive PDF Expor โ”‚ โ—‹ pending โ”‚ - โ”‚ 2 โ”‚ N/A โ”‚ +โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค +โ”‚ 96.4 โ”‚ โ””โ”€ Update Documentation, Tests, and โ”‚ โ—‹ pending โ”‚ - โ”‚ 2, 3 โ”‚ N/A โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โšก RECOMMENDED NEXT TASK โšก โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โ”‚ +โ”‚ ๐Ÿ”ฅ Next Task to Work On: #67 - Add CLI JSON output and Cursor keybindings integration โ”‚ +โ”‚ โ”‚ +โ”‚ Priority: high Status: โ—‹ pending โ”‚ +โ”‚ Dependencies: None โ”‚ +โ”‚ โ”‚ +โ”‚ Description: Enhance Taskmaster CLI with JSON output option and add a new command to install pre-configured Cursor keybindings โ”‚ +โ”‚ โ”‚ +โ”‚ Subtasks: โ”‚ +โ”‚ 67.1 [pending] Implement Core JSON Output Logic for `next` and `show` Commands โ”‚ +โ”‚ 67.2 [pending] Extend JSON Output to All Relevant Commands and Ensure Schema Consistency โ”‚ +โ”‚ 67.3 [pending] Create `install-keybindings` Command Structure and OS Detection โ”‚ +โ”‚ 67.4 [pending] Implement Keybinding File Handling and Backup Logic โ”‚ +โ”‚ 67.5 [pending] Add Taskmaster Keybindings, Prevent Duplicates, and Support Customization โ”‚ +โ”‚ โ”‚ +โ”‚ Start working: task-master set-status --id=67 --status=in-progress โ”‚ +โ”‚ View details: task-master show 67 โ”‚ +โ”‚ โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + + +โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ +โ”‚ โ”‚ +โ”‚ Suggested Next Steps: โ”‚ +โ”‚ โ”‚ +โ”‚ 1. Run task-master next to see what to work on next โ”‚ +โ”‚ 2. Run task-master expand --id=<id> to break down a task into subtasks โ”‚ +โ”‚ 3. Run task-master set-status --id=<id> --status=done to mark a task as complete โ”‚ +โ”‚ โ”‚ +โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ + +> ๐Ÿ“‹ **End of Taskmaster Export** - Tasks are synced from your project using the `sync-readme` command. +<!-- TASKMASTER_EXPORT_END --> + diff --git a/scripts/modules/commands.js b/scripts/modules/commands.js index 2b6aa52b..0b4553b4 100644 --- a/scripts/modules/commands.js +++ b/scripts/modules/commands.js @@ -87,6 +87,7 @@ import { TASK_STATUS_OPTIONS } from '../../src/constants/task-status.js'; import { getTaskMasterVersion } from '../../src/utils/getVersion.js'; +import { syncTasksToReadme } from './sync-readme.js'; /** * Runs the interactive setup process for model configuration. @@ -2757,6 +2758,54 @@ Examples: } }); + // sync-readme command + programInstance + .command('sync-readme') + .description('Sync the current task list to README.md in the project root') + .option( + '-f, --file <file>', + 'Path to the tasks file', + TASKMASTER_TASKS_FILE + ) + .option('--with-subtasks', 'Include subtasks in the README output') + .option( + '-s, --status <status>', + 'Show only tasks matching this status (e.g., pending, done)' + ) + .action(async (options) => { + const tasksPath = options.file || TASKMASTER_TASKS_FILE; + const withSubtasks = options.withSubtasks || false; + const status = options.status || null; + + // Find project root + const projectRoot = findProjectRoot(); + if (!projectRoot) { + console.error( + chalk.red( + 'Error: Could not find project root. Make sure you are in a Task Master project directory.' + ) + ); + process.exit(1); + } + + console.log( + chalk.blue( + `๐Ÿ“ Syncing tasks to README.md${withSubtasks ? ' (with subtasks)' : ''}${status ? ` (status: ${status})` : ''}...` + ) + ); + + const success = await syncTasksToReadme(projectRoot, { + withSubtasks, + status, + tasksPath + }); + + if (!success) { + console.error(chalk.red('โŒ Failed to sync tasks to README.md')); + process.exit(1); + } + }); + return programInstance; } diff --git a/scripts/modules/sync-readme.js b/scripts/modules/sync-readme.js new file mode 100644 index 00000000..a13083ca --- /dev/null +++ b/scripts/modules/sync-readme.js @@ -0,0 +1,184 @@ +import fs from 'fs'; +import path from 'path'; +import chalk from 'chalk'; +import { log, findProjectRoot } from './utils.js'; +import { getProjectName } from './config-manager.js'; +import listTasks from './task-manager/list-tasks.js'; + +/** + * Creates a basic README structure if one doesn't exist + * @param {string} projectName - Name of the project + * @returns {string} - Basic README content + */ +function createBasicReadme(projectName) { + return `# ${projectName} + +This project is managed using Task Master. + +`; +} + +/** + * Create UTM tracking URL for task-master.dev + * @param {string} projectRoot - The project root path + * @returns {string} - UTM tracked URL + */ +function createTaskMasterUrl(projectRoot) { + // Get the actual folder name from the project root path + const folderName = path.basename(projectRoot); + + // Clean folder name for UTM (replace spaces/special chars with hyphens) + const cleanFolderName = folderName + .toLowerCase() + .replace(/[^a-z0-9]/g, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, ''); + + const utmParams = new URLSearchParams({ + utm_source: 'github-readme', + utm_medium: 'readme-export', + utm_campaign: cleanFolderName || 'task-sync', + utm_content: 'task-export-link' + }); + + return `https://task-master.dev?${utmParams.toString()}`; +} + +/** + * Create the start marker with metadata + * @param {Object} options - Export options + * @returns {string} - Formatted start marker + */ +function createStartMarker(options) { + const { timestamp, withSubtasks, status, projectRoot } = options; + + // Format status filter text + const statusText = status + ? `Status filter: ${status}` + : 'Status filter: none'; + const subtasksText = withSubtasks ? 'with subtasks' : 'without subtasks'; + + // Create the export info content + const exportInfo = + `๐ŸŽฏ **Taskmaster Export** - ${timestamp}\n` + + `๐Ÿ“‹ Export: ${subtasksText} โ€ข ${statusText}\n` + + `๐Ÿ”— Powered by [Task Master](${createTaskMasterUrl(projectRoot)})`; + + // Create a markdown box using code blocks and emojis to mimic our UI style + const boxContent = + `<!-- TASKMASTER_EXPORT_START -->\n` + + `> ${exportInfo.split('\n').join('\n> ')}\n\n`; + + return boxContent; +} + +/** + * Create the end marker + * @returns {string} - Formatted end marker + */ +function createEndMarker() { + return ( + `\n> ๐Ÿ“‹ **End of Taskmaster Export** - Tasks are synced from your project using the \`sync-readme\` command.\n` + + `<!-- TASKMASTER_EXPORT_END -->\n` + ); +} + +/** + * Syncs the current task list to README.md at the project root + * @param {string} projectRoot - Path to the project root directory + * @param {Object} options - Options for syncing + * @param {boolean} options.withSubtasks - Include subtasks in the output (default: false) + * @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 + */ +export async function syncTasksToReadme(projectRoot = null, options = {}) { + try { + const actualProjectRoot = projectRoot || findProjectRoot() || '.'; + const { withSubtasks = false, status, tasksPath } = options; + + // Get current tasks using the list-tasks functionality with markdown-readme format + const tasksOutput = await listTasks( + tasksPath || + path.join(actualProjectRoot, '.taskmaster', 'tasks', 'tasks.json'), + status, + null, + withSubtasks, + 'markdown-readme' + ); + + if (!tasksOutput) { + console.log(chalk.red('โŒ Failed to generate task output')); + return false; + } + + // Generate timestamp and metadata + const timestamp = + new Date().toISOString().replace('T', ' ').substring(0, 19) + ' UTC'; + const projectName = getProjectName(actualProjectRoot); + + // Create the export markers with metadata + const startMarker = createStartMarker({ + timestamp, + withSubtasks, + status, + projectRoot: actualProjectRoot + }); + + const endMarker = createEndMarker(); + + // Create the complete task section + const taskSection = startMarker + tasksOutput + endMarker; + + // Read current README content + const readmePath = path.join(actualProjectRoot, 'README.md'); + let readmeContent = ''; + try { + readmeContent = fs.readFileSync(readmePath, 'utf8'); + } catch (err) { + if (err.code === 'ENOENT') { + // Create basic README if it doesn't exist + readmeContent = createBasicReadme(projectName); + } else { + throw err; + } + } + + // Check if export markers exist and replace content between them + const startComment = '<!-- TASKMASTER_EXPORT_START -->'; + const endComment = '<!-- TASKMASTER_EXPORT_END -->'; + + let updatedContent; + const startIndex = readmeContent.indexOf(startComment); + const endIndex = readmeContent.indexOf(endComment); + + if (startIndex !== -1 && endIndex !== -1) { + // Replace existing task section + const beforeTasks = readmeContent.substring(0, startIndex); + const afterTasks = readmeContent.substring(endIndex + endComment.length); + updatedContent = beforeTasks + taskSection + afterTasks; + } else { + // Append to end of README + updatedContent = readmeContent + '\n' + taskSection; + } + + // Write updated content to README + fs.writeFileSync(readmePath, updatedContent, 'utf8'); + + console.log(chalk.green('โœ… Successfully synced tasks to README.md')); + console.log( + chalk.cyan( + `๐Ÿ“‹ Export details: ${withSubtasks ? 'with' : 'without'} subtasks${status ? `, status: ${status}` : ''}` + ) + ); + console.log(chalk.gray(`๐Ÿ“ Location: ${readmePath}`)); + + return true; + } catch (error) { + console.log(chalk.red('โŒ Failed to sync tasks to README:'), error.message); + log('error', `README sync error: ${error.message}`); + return false; + } +} + +export default syncTasksToReadme; diff --git a/scripts/modules/task-manager/list-tasks.js b/scripts/modules/task-manager/list-tasks.js index 51f37460..2b875b5d 100644 --- a/scripts/modules/task-manager/list-tasks.js +++ b/scripts/modules/task-manager/list-tasks.js @@ -120,86 +120,7 @@ function listTasks( const subtaskCompletionPercentage = totalSubtasks > 0 ? (completedSubtasks / totalSubtasks) * 100 : 0; - // For JSON output, return structured data - if (outputFormat === 'json') { - // *** Modification: Remove 'details' field for JSON output *** - const tasksWithoutDetails = filteredTasks.map((task) => { - // <-- USES filteredTasks! - // Omit 'details' from the parent task - const { details, ...taskRest } = task; - - // If subtasks exist, omit 'details' from them too - if (taskRest.subtasks && Array.isArray(taskRest.subtasks)) { - taskRest.subtasks = taskRest.subtasks.map((subtask) => { - const { details: subtaskDetails, ...subtaskRest } = subtask; - return subtaskRest; - }); - } - return taskRest; - }); - // *** End of Modification *** - - return { - tasks: tasksWithoutDetails, // <--- THIS IS THE ARRAY BEING RETURNED - filter: statusFilter || 'all', // Return the actual filter used - stats: { - total: totalTasks, - completed: doneCount, - inProgress: inProgressCount, - pending: pendingCount, - blocked: blockedCount, - deferred: deferredCount, - cancelled: cancelledCount, - completionPercentage, - subtasks: { - total: totalSubtasks, - completed: completedSubtasks, - inProgress: inProgressSubtasks, - pending: pendingSubtasks, - blocked: blockedSubtasks, - deferred: deferredSubtasks, - cancelled: cancelledSubtasks, - completionPercentage: subtaskCompletionPercentage - } - } - }; - } - - // ... existing code for text output ... - - // Calculate status breakdowns as percentages of total - const taskStatusBreakdown = { - 'in-progress': totalTasks > 0 ? (inProgressCount / totalTasks) * 100 : 0, - pending: totalTasks > 0 ? (pendingCount / totalTasks) * 100 : 0, - blocked: totalTasks > 0 ? (blockedCount / totalTasks) * 100 : 0, - deferred: totalTasks > 0 ? (deferredCount / totalTasks) * 100 : 0, - cancelled: totalTasks > 0 ? (cancelledCount / totalTasks) * 100 : 0 - }; - - const subtaskStatusBreakdown = { - 'in-progress': - totalSubtasks > 0 ? (inProgressSubtasks / totalSubtasks) * 100 : 0, - pending: totalSubtasks > 0 ? (pendingSubtasks / totalSubtasks) * 100 : 0, - blocked: totalSubtasks > 0 ? (blockedSubtasks / totalSubtasks) * 100 : 0, - deferred: - totalSubtasks > 0 ? (deferredSubtasks / totalSubtasks) * 100 : 0, - cancelled: - totalSubtasks > 0 ? (cancelledSubtasks / totalSubtasks) * 100 : 0 - }; - - // Create progress bars with status breakdowns - const taskProgressBar = createProgressBar( - completionPercentage, - 30, - taskStatusBreakdown - ); - const subtaskProgressBar = createProgressBar( - subtaskCompletionPercentage, - 30, - subtaskStatusBreakdown - ); - - // Calculate dependency statistics + // Calculate dependency statistics (moved up to be available for all output formats) const completedTaskIds = new Set( data.tasks .filter((t) => t.status === 'done' || t.status === 'completed') @@ -271,6 +192,118 @@ function listTasks( // Find next task to work on, passing the complexity report const nextItem = findNextTask(data.tasks, complexityReport); + // For JSON output, return structured data + if (outputFormat === 'json') { + // *** Modification: Remove 'details' field for JSON output *** + const tasksWithoutDetails = filteredTasks.map((task) => { + // <-- USES filteredTasks! + // Omit 'details' from the parent task + const { details, ...taskRest } = task; + + // If subtasks exist, omit 'details' from them too + if (taskRest.subtasks && Array.isArray(taskRest.subtasks)) { + taskRest.subtasks = taskRest.subtasks.map((subtask) => { + const { details: subtaskDetails, ...subtaskRest } = subtask; + return subtaskRest; + }); + } + return taskRest; + }); + // *** End of Modification *** + + return { + tasks: tasksWithoutDetails, // <--- THIS IS THE ARRAY BEING RETURNED + filter: statusFilter || 'all', // Return the actual filter used + stats: { + total: totalTasks, + completed: doneCount, + inProgress: inProgressCount, + pending: pendingCount, + blocked: blockedCount, + deferred: deferredCount, + cancelled: cancelledCount, + completionPercentage, + subtasks: { + total: totalSubtasks, + completed: completedSubtasks, + inProgress: inProgressSubtasks, + pending: pendingSubtasks, + blocked: blockedSubtasks, + deferred: deferredSubtasks, + cancelled: cancelledSubtasks, + completionPercentage: subtaskCompletionPercentage + } + } + }; + } + + // For markdown-readme output, return formatted markdown + if (outputFormat === 'markdown-readme') { + return generateMarkdownOutput(data, filteredTasks, { + totalTasks, + completedTasks, + completionPercentage, + doneCount, + inProgressCount, + pendingCount, + blockedCount, + deferredCount, + cancelledCount, + totalSubtasks, + completedSubtasks, + subtaskCompletionPercentage, + inProgressSubtasks, + pendingSubtasks, + blockedSubtasks, + deferredSubtasks, + cancelledSubtasks, + tasksWithNoDeps, + tasksReadyToWork, + tasksWithUnsatisfiedDeps, + mostDependedOnTask, + mostDependedOnTaskId, + maxDependents, + avgDependenciesPerTask, + complexityReport, + withSubtasks, + nextItem + }); + } + + // ... existing code for text output ... + + // Calculate status breakdowns as percentages of total + const taskStatusBreakdown = { + 'in-progress': totalTasks > 0 ? (inProgressCount / totalTasks) * 100 : 0, + pending: totalTasks > 0 ? (pendingCount / totalTasks) * 100 : 0, + blocked: totalTasks > 0 ? (blockedCount / totalTasks) * 100 : 0, + deferred: totalTasks > 0 ? (deferredCount / totalTasks) * 100 : 0, + cancelled: totalTasks > 0 ? (cancelledCount / totalTasks) * 100 : 0 + }; + + const subtaskStatusBreakdown = { + 'in-progress': + totalSubtasks > 0 ? (inProgressSubtasks / totalSubtasks) * 100 : 0, + pending: totalSubtasks > 0 ? (pendingSubtasks / totalSubtasks) * 100 : 0, + blocked: totalSubtasks > 0 ? (blockedSubtasks / totalSubtasks) * 100 : 0, + deferred: + totalSubtasks > 0 ? (deferredSubtasks / totalSubtasks) * 100 : 0, + cancelled: + totalSubtasks > 0 ? (cancelledSubtasks / totalSubtasks) * 100 : 0 + }; + + // Create progress bars with status breakdowns + const taskProgressBar = createProgressBar( + completionPercentage, + 30, + taskStatusBreakdown + ); + const subtaskProgressBar = createProgressBar( + subtaskCompletionPercentage, + 30, + subtaskStatusBreakdown + ); + // Get terminal width - more reliable method let terminalWidth; try { @@ -759,4 +792,232 @@ function getWorkItemDescription(item, allTasks) { } } +/** + * Generate markdown-formatted output for README files + * @param {Object} data - Full tasks data + * @param {Array} filteredTasks - Filtered tasks array + * @param {Object} stats - Statistics object + * @returns {string} - Formatted markdown string + */ +function generateMarkdownOutput(data, filteredTasks, stats) { + const { + totalTasks, + completedTasks, + completionPercentage, + doneCount, + inProgressCount, + pendingCount, + blockedCount, + deferredCount, + cancelledCount, + totalSubtasks, + completedSubtasks, + subtaskCompletionPercentage, + inProgressSubtasks, + pendingSubtasks, + blockedSubtasks, + deferredSubtasks, + cancelledSubtasks, + tasksWithNoDeps, + tasksReadyToWork, + tasksWithUnsatisfiedDeps, + mostDependedOnTask, + mostDependedOnTaskId, + maxDependents, + avgDependenciesPerTask, + complexityReport, + withSubtasks, + nextItem + } = stats; + + let markdown = ''; + + // Create progress bars for markdown (using Unicode block characters) + const createMarkdownProgressBar = (percentage, width = 20) => { + const filled = Math.round((percentage / 100) * width); + const empty = width - filled; + return 'โ–ˆ'.repeat(filled) + 'โ–‘'.repeat(empty); + }; + + // Dashboard section + markdown += '```\n'; + markdown += + 'โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎโ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n'; + markdown += + 'โ”‚ โ”‚โ”‚ โ”‚\n'; + markdown += + 'โ”‚ Project Dashboard โ”‚โ”‚ Dependency Status & Next Task โ”‚\n'; + markdown += `โ”‚ Tasks Progress: ${createMarkdownProgressBar(completionPercentage, 20)} ${Math.round(completionPercentage)}% โ”‚โ”‚ Dependency Metrics: โ”‚\n`; + markdown += `โ”‚ ${Math.round(completionPercentage)}% โ”‚โ”‚ โ€ข Tasks with no dependencies: ${tasksWithNoDeps} โ”‚\n`; + markdown += `โ”‚ Done: ${doneCount} In Progress: ${inProgressCount} Pending: ${pendingCount} Blocked: ${blockedCount} โ”‚โ”‚ โ€ข Tasks ready to work on: ${tasksReadyToWork} โ”‚\n`; + markdown += `โ”‚ Deferred: ${deferredCount} Cancelled: ${cancelledCount} โ”‚โ”‚ โ€ข Tasks blocked by dependencies: ${tasksWithUnsatisfiedDeps} โ”‚\n`; + markdown += `โ”‚ โ”‚โ”‚ โ€ข Most depended-on task: #${mostDependedOnTaskId} (${maxDependents} dependents) โ”‚\n`; + markdown += `โ”‚ Subtasks Progress: ${createMarkdownProgressBar(subtaskCompletionPercentage, 20)} โ”‚โ”‚ โ€ข Avg dependencies per task: ${avgDependenciesPerTask.toFixed(1)} โ”‚\n`; + markdown += `โ”‚ ${Math.round(subtaskCompletionPercentage)}% ${Math.round(subtaskCompletionPercentage)}% โ”‚โ”‚ โ”‚\n`; + markdown += `โ”‚ Completed: ${completedSubtasks}/${totalSubtasks} In Progress: ${inProgressSubtasks} Pending: ${pendingSubtasks} โ”‚โ”‚ Next Task to Work On: โ”‚\n`; + + const nextTaskTitle = nextItem + ? nextItem.title.length > 40 + ? nextItem.title.substring(0, 37) + '...' + : nextItem.title + : 'No task available'; + + markdown += `โ”‚ Blocked: ${blockedSubtasks} Deferred: ${deferredSubtasks} Cancelled: ${cancelledSubtasks} โ”‚โ”‚ ID: ${nextItem ? nextItem.id : 'N/A'} - ${nextTaskTitle} โ”‚\n`; + markdown += `โ”‚ โ”‚โ”‚ Priority: ${nextItem ? nextItem.priority || 'medium' : ''} Dependencies: ${nextItem && nextItem.dependencies && nextItem.dependencies.length > 0 ? 'Some' : 'None'} โ”‚\n`; + markdown += `โ”‚ Priority Breakdown: โ”‚โ”‚ Complexity: ${nextItem && nextItem.complexityScore ? 'โ— ' + nextItem.complexityScore : 'N/A'} โ”‚\n`; + markdown += `โ”‚ โ€ข High priority: ${data.tasks.filter((t) => t.priority === 'high').length} โ”‚โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ\n`; + markdown += `โ”‚ โ€ข Medium priority: ${data.tasks.filter((t) => t.priority === 'medium').length} โ”‚\n`; + markdown += `โ”‚ โ€ข Low priority: ${data.tasks.filter((t) => t.priority === 'low').length} โ”‚\n`; + markdown += 'โ”‚ โ”‚\n'; + markdown += 'โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ\n'; + + // Tasks table + markdown += + 'โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”\n'; + markdown += + 'โ”‚ ID โ”‚ Title โ”‚ Status โ”‚ Priority โ”‚ Dependencies โ”‚ Complexiโ€ฆ โ”‚\n'; + markdown += + 'โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n'; + + // Helper function to format status with symbols + const getStatusSymbol = (status) => { + switch (status) { + case 'done': + case 'completed': + return 'โœ“ done'; + case 'in-progress': + return 'โ–บ in-progress'; + case 'pending': + return 'โ—‹ pending'; + case 'blocked': + return 'โญ• blocked'; + case 'deferred': + return 'x deferred'; + case 'cancelled': + return 'x cancelled'; + case 'review': + return '? review'; + default: + return status || 'pending'; + } + }; + + // Helper function to format dependencies without color codes + const formatDependenciesForMarkdown = (deps, allTasks) => { + if (!deps || deps.length === 0) return 'None'; + return deps + .map((depId) => { + const depTask = allTasks.find((t) => t.id === depId); + return depTask ? depId.toString() : depId.toString(); + }) + .join(', '); + }; + + // Process all tasks + filteredTasks.forEach((task) => { + const taskTitle = task.title; // No truncation for README + const statusSymbol = getStatusSymbol(task.status); + const priority = task.priority || 'medium'; + const deps = formatDependenciesForMarkdown(task.dependencies, data.tasks); + const complexity = task.complexityScore + ? `โ— ${task.complexityScore}` + : 'N/A'; + + markdown += `โ”‚ ${task.id.toString().padEnd(9)} โ”‚ ${taskTitle.substring(0, 36).padEnd(36)} โ”‚ ${statusSymbol.padEnd(15)} โ”‚ ${priority.padEnd(12)} โ”‚ ${deps.substring(0, 21).padEnd(21)} โ”‚ ${complexity.padEnd(9)} โ”‚\n`; + + // Add subtasks if requested + if (withSubtasks && task.subtasks && task.subtasks.length > 0) { + task.subtasks.forEach((subtask) => { + const subtaskTitle = `โ””โ”€ ${subtask.title}`; // No truncation + const subtaskStatus = getStatusSymbol(subtask.status); + const subtaskDeps = formatDependenciesForMarkdown( + subtask.dependencies, + data.tasks + ); + const subtaskComplexity = subtask.complexityScore + ? subtask.complexityScore.toString() + : 'N/A'; + + markdown += + 'โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n'; + markdown += `โ”‚ ${task.id}.${subtask.id}${' '.padEnd(6)} โ”‚ ${subtaskTitle.substring(0, 36).padEnd(36)} โ”‚ ${subtaskStatus.padEnd(15)} โ”‚ - โ”‚ ${subtaskDeps.substring(0, 21).padEnd(21)} โ”‚ ${subtaskComplexity.padEnd(9)} โ”‚\n`; + }); + } + + markdown += + 'โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n'; + }); + + // Close the table + markdown = markdown.slice( + 0, + -1 * + 'โ”œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ผโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค\n' + .length + ); + markdown += + 'โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ดโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜\n'; + markdown += '```\n\n'; + + // Next task recommendation + if (nextItem) { + markdown += + 'โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โšก RECOMMENDED NEXT TASK โšก โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n'; + markdown += + 'โ”‚ โ”‚\n'; + markdown += `โ”‚ ๐Ÿ”ฅ Next Task to Work On: #${nextItem.id} - ${nextItem.title} โ”‚\n`; + markdown += + 'โ”‚ โ”‚\n'; + markdown += `โ”‚ Priority: ${nextItem.priority || 'medium'} Status: ${getStatusSymbol(nextItem.status)} โ”‚\n`; + markdown += `โ”‚ Dependencies: ${nextItem.dependencies && nextItem.dependencies.length > 0 ? formatDependenciesForMarkdown(nextItem.dependencies, data.tasks) : 'None'} โ”‚\n`; + markdown += + 'โ”‚ โ”‚\n'; + markdown += `โ”‚ Description: ${getWorkItemDescription(nextItem, data.tasks)} โ”‚\n`; + markdown += + 'โ”‚ โ”‚\n'; + + // Add subtasks if they exist + const parentTask = data.tasks.find((t) => t.id === nextItem.id); + if (parentTask && parentTask.subtasks && parentTask.subtasks.length > 0) { + markdown += + 'โ”‚ Subtasks: โ”‚\n'; + parentTask.subtasks.forEach((subtask) => { + markdown += `โ”‚ ${nextItem.id}.${subtask.id} [${subtask.status || 'pending'}] ${subtask.title} โ”‚\n`; + }); + markdown += + 'โ”‚ โ”‚\n'; + } + + markdown += `โ”‚ Start working: task-master set-status --id=${nextItem.id} --status=in-progress โ”‚\n`; + markdown += `โ”‚ View details: task-master show ${nextItem.id} โ”‚\n`; + markdown += + 'โ”‚ โ”‚\n'; + markdown += + 'โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ\n\n'; + } + + // Suggested next steps + markdown += '\n'; + markdown += + 'โ•ญโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฎ\n'; + markdown += + 'โ”‚ โ”‚\n'; + markdown += + 'โ”‚ Suggested Next Steps: โ”‚\n'; + markdown += + 'โ”‚ โ”‚\n'; + markdown += + 'โ”‚ 1. Run task-master next to see what to work on next โ”‚\n'; + markdown += + 'โ”‚ 2. Run task-master expand --id=<id> to break down a task into subtasks โ”‚\n'; + markdown += + 'โ”‚ 3. Run task-master set-status --id=<id> --status=done to mark a task as complete โ”‚\n'; + markdown += + 'โ”‚ โ”‚\n'; + markdown += + 'โ•ฐโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ•ฏ\n'; + + return markdown; +} + export default listTasks; diff --git a/scripts/modules/ui.js b/scripts/modules/ui.js index bcafbc5e..1279c8eb 100644 --- a/scripts/modules/ui.js +++ b/scripts/modules/ui.js @@ -525,6 +525,11 @@ function displayHelp() { args: '--id=<id> --status=<status>', desc: `Update task status (${TASK_STATUS_OPTIONS.join(', ')})` }, + { + name: 'sync-readme', + args: '[--with-subtasks] [--status=<status>]', + desc: 'Export tasks to README.md with professional formatting' + }, { name: 'update', args: '--from=<id> --prompt="<context>"', From 51dd4f625bad7b7f1471b374f5d39dec444e0647 Mon Sep 17 00:00:00 2001 From: Eyal Toledano <eyal@microangel.so> Date: Sat, 7 Jun 2025 22:13:11 -0400 Subject: [PATCH 10/10] chore: changeset adjustment --- .changeset/vast-shrimps-happen.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/vast-shrimps-happen.md b/.changeset/vast-shrimps-happen.md index 3c7895e1..67da4094 100644 --- a/.changeset/vast-shrimps-happen.md +++ b/.changeset/vast-shrimps-happen.md @@ -19,4 +19,4 @@ Introduces a new `sync-readme` command that exports your task list to your proje - `task-master sync-readme --status=pending` - Only export pending tasks - `task-master sync-readme --status=done --with-subtasks` - Export completed tasks with subtasks -Perfect for showcasing project progress on GitHub with professional presentation and traffic analytics. +Perfect for showcasing project progress on GitHub. Experimental. Open to feedback.