From 5e9bc28abea36ec7cd25489af7fcc6cbea51038b Mon Sep 17 00:00:00 2001 From: Eyal Toledano Date: Thu, 22 May 2025 01:09:40 -0400 Subject: [PATCH] feat(add-task): enhance dependency detection with semantic search This commit significantly improves the functionality by implementing fuzzy semantic search to find contextually relevant dependencies: - Add Fuse.js for powerful fuzzy search capability with weighted multi-field matching - Implement score-based relevance ranking with high/medium relevance tiers - Enhance context generation to include detailed information about similar tasks - Fix context shadowing issue that prevented detailed task information from reaching the AI model - Add informative CLI output showing semantic search results and dependency patterns - Improve formatting of dependency information in prompts with task titles The result is that newly created tasks are automatically placed within the correct dependency structure without manual intervention, with the AI having much better context about which tasks are most relevant to the new one being created. This significantly improves the user experience by reducing the need to manually update task dependencies after creation, all without increasing token usage or costs. --- .changeset/gentle-parents-film.md | 9 + context/chats/add-task-dependencies-1.md | 1748 ++++++++++++++++++++++ package-lock.json | 6 +- package.json | 2 +- scripts/modules/task-manager/add-task.js | 698 ++++++++- tasks/task_089.txt | 2 +- tasks/task_090.txt | 67 + tasks/task_091.txt | 11 + tasks/task_092.txt | 32 + tasks/tasks.json | 51 +- 10 files changed, 2595 insertions(+), 31 deletions(-) create mode 100644 .changeset/gentle-parents-film.md create mode 100644 context/chats/add-task-dependencies-1.md create mode 100644 tasks/task_090.txt create mode 100644 tasks/task_091.txt create mode 100644 tasks/task_092.txt diff --git a/.changeset/gentle-parents-film.md b/.changeset/gentle-parents-film.md new file mode 100644 index 00000000..f1b1564f --- /dev/null +++ b/.changeset/gentle-parents-film.md @@ -0,0 +1,9 @@ +--- +'task-master-ai': patch +--- + +This change significantly enhances the `add-task` command's intelligence. When you add a new task, Taskmaster now automatically: + - Analyzes your existing tasks to find those most relevant to your new task's description. + - Provides the AI with detailed context from these relevant tasks. + +This results in newly created tasks being more accurately placed within your project's dependency structure, saving you time and any need to update tasks just for dependencies, all without significantly increasing AI costs. You'll get smarter, more connected tasks right from the start. diff --git a/context/chats/add-task-dependencies-1.md b/context/chats/add-task-dependencies-1.md new file mode 100644 index 00000000..067803c0 --- /dev/null +++ b/context/chats/add-task-dependencies-1.md @@ -0,0 +1,1748 @@ +# Task Title Formatting Issue Discussion +_Exported on 5/22/2025 at 00:05:09 EDT from Cursor (0.50.5)_ + +--- + +**User** + +in add-task, i noticed that with some providers, the title comes out with the id in it. i dont know if this is a parsing issue (@ollama.js) or if it this is an improvement we ought to make to the system prompt in @add-task.js + +--- + +**Cursor** + +Okay, I've looked into this. The issue where the task ID is sometimes included in the generated task title (e.g., "Task #89: Introduce Prioritize Command...") likely stems from the prompt construction in `scripts/modules/task-manager/add-task.js`. + +The current user prompt (around line 208) is: +`Create a comprehensive new task (Task #${newTaskId}) for a software development project based on this description: "${prompt}"` + +The AI might be interpreting `(Task #${newTaskId})` as part of the content it needs to generate, especially for the title. The `ollama.js` provider itself doesn't seem to be modifying the content in a way that would add the ID; it primarily passes the prompt and schema to the Ollama service. + +I'll adjust the user prompt in `scripts/modules/task-manager/add-task.js` to clarify that the task ID is contextual information, not part of the title to be generated. + +I'll change this part of the prompt: +`Create a comprehensive new task (Task #${newTaskId}) for a software development project based on this description: "${prompt}"` + +to something like: +`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.` + +This rephrasing should guide the AI to use the ID as context rather than including it directly in the `title` field of the generated JSON object. + +Here's the proposed change: + + +```javascript:scripts/modules/task-manager/add-task.js +// ... existing code ... + 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. + + ${contextTasks} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific.`; + + // Start the loading indicator - only for text mode + if (outputFormat === 'text') { +// ... existing code ... +``` +This change clarifies the role of `newTaskId` for the AI. Let me know if you'd like to test this or make further adjustments! + +```javascript +// ... existing code ... + 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. + + ${contextTasks} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific.`; + + // Start the loading indicator - only for text mode + if (outputFormat === 'text') { +// ... existing code ... +``` + +--- + +**User** + +get the details of task id 88 -- i wonder if we can - in the above place, be smart about passing in the ids, titles, descriptions (but not details) and dependencies of all the other tasks + +i don't want it to take a ton of context, but the goal here is to also fill in the dependency field when creating a task. right now it's not being filled in and i have to update all the tasks for it to be populated. so i want it to be fixed at the source in add-task ideally and i want a lightweight but accurate way to pass in the other tasks and their dependencies, and not passing in the actual details of the task and only the above as i explained might be enough. + +--- + +**Cursor** + +Let me get the details of task ID 88 first: + +Now I'll look at the current implementation of add-task.js to understand how we can improve the context provided to the AI: + +Based on the task details and the current implementation of add-task.js, I'll create an improved version that provides more context about other tasks and their dependency relationships without overwhelming the token limit. Here's my proposed change: + +```javascript +// ... existing code ... + 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 + }; + }); + + if (numericDependencies.length > 0) { + // If specific dependencies were provided, focus on them + const dependentTasks = data.tasks.filter((t) => + numericDependencies.includes(t.id) + ); + contextTasks = `\nThis task depends on the following tasks:\n${dependentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + + // Add dependency chain information (what these tasks depend on) + const dependencyChains = []; + for (const depTask of dependentTasks) { + if (depTask.dependencies && depTask.dependencies.length > 0) { + const subDeps = depTask.dependencies.map(subDepId => { + const subDepTask = data.tasks.find(t => t.id === subDepId); + return subDepTask ? `Task ${subDepId}: ${subDepTask.title}` : `Task ${subDepId}`; + }); + dependencyChains.push(`Task ${depTask.id} depends on: ${subDeps.join(', ')}`); + } + } + + if (dependencyChains.length > 0) { + contextTasks += `\n\nDependency chains:\n${dependencyChains.join('\n')}`; + } + } else { + // If no dependencies provided, show recent and related tasks + // Get recent tasks (newest first) + const recentTasks = [...data.tasks] + .sort((a, b) => b.id - a.id) + .slice(0, 5); + + // Get tasks that might be related based on simple keyword matching + const keywords = prompt.toLowerCase().split(/\s+/).filter(word => + word.length > 4 && !['the', 'and', 'that', 'with', 'this', 'for', 'from'].includes(word) + ); + + const relatedTasks = keywords.length > 0 + ? data.tasks.filter(t => + keywords.some(keyword => + t.title.toLowerCase().includes(keyword) || + t.description.toLowerCase().includes(keyword) + ) && + !recentTasks.some(rt => rt.id === t.id) + ).slice(0, 3) + : []; + + if (recentTasks.length > 0) { + contextTasks = `\nRecent tasks in the project:\n${recentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + if (relatedTasks.length > 0) { + contextTasks += `\n\nPotentially related tasks based on keywords:\n${relatedTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + // Add a concise view of the task dependency structure + contextTasks += '\n\nSummary of task dependencies in the project:'; + const pendingTasks = data.tasks.filter(t => t.status !== 'done').slice(-10); + for (const task of pendingTasks) { + const depsStr = task.dependencies && task.dependencies.length > 0 + ? task.dependencies.join(', ') + : 'None'; + contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`; + } + } + + // 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."; + + // 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", + "details": "In-depth implementation details, considerations, and guidance.", + "testStrategy": "Detailed approach for verifying task completion." + }`; + + // 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. + + ${contextTasks} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + + Based on the information about existing tasks and their dependencies, suggest appropriate dependencies for this task if any are needed. The task IDs mentioned above can be referenced in your implementation details. + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific. Don't include the task ID in the title.`; +// ... existing code ... +``` + +Now let's test the changes to see if they improve the task creation process, especially around dependency handling: + +--- + +**User** + +this will probably need to be adjusted so from a structured output pov the ai knows to add the info in that field + +--- + +**Cursor** + +You're absolutely right. While our changes improve the context provided to the AI, they don't provide a structured way for the AI to include the dependencies in its output schema. Let's modify the Zod schema to explicitly include a `dependencies` field: + +```javascript +// 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)') +}); + +// ... existing code ... + + // 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", + "details": "In-depth implementation details, considerations, and guidance.", + "testStrategy": "Detailed approach for verifying task completion.", + "dependencies": [1, 2, 3] // Optional: Array of existing task IDs that this task depends on + }`; + + // ... existing code ... + + // 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}` : ''} + + Based on the information about existing tasks and their dependencies, include appropriate dependencies in the "dependencies" array if any are needed. Only include task IDs that this new task directly depends on. + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific. Don't include the task ID in the title.`; + +// ... existing code ... + + // 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 + }; +``` + +Let's make one more small fix - I notice there's still a reference to `numericDependencies` in the success message output that should be adjusted to use the task's actual dependencies: + +```javascript +// ... existing code ... + 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' + + (newTask.dependencies.length > 0 + ? chalk.white(`Dependencies: ${newTask.dependencies.join(', ')}`) + + '\n' + : '') + + '\n' + +// ... existing code ... +``` + +Now let's test our changes to make sure they work properly: + +--- + +**User** + +looks like it worked. i would like -- in the case of the CLI only, to also show some additional stuff in @add-task.js + +we just did a bunch of magic, and it would be too bad not to show it to the user as we do it, without it being too much + +(ignore the failed ollama call) + +i think there could be a line or two that shows the identified dependencies or something. its sexy stuff, let's make sure the user knows about it elegantly. @ui.js + +--- + +**Cursor** + +Let's enhance the CLI experience to elegantly showcase the dependency analysis we're performing. I'll add a small, informative display in add-task.js that shows the intelligent context we're building: + +```javascript +// ... existing code ... + // 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' + } + )); + } + + if (numericDependencies.length > 0) { + // If specific dependencies were provided, focus on them + const dependentTasks = data.tasks.filter((t) => + numericDependencies.includes(t.id) + ); + contextTasks = `\nThis task depends on the following tasks:\n${dependentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + + // Add dependency chain information (what these tasks depend on) + const dependencyChains = []; + for (const depTask of dependentTasks) { + if (depTask.dependencies && depTask.dependencies.length > 0) { + const subDeps = depTask.dependencies.map(subDepId => { + const subDepTask = data.tasks.find(t => t.id === subDepId); + return subDepTask ? `Task ${subDepId}: ${subDepTask.title}` : `Task ${subDepId}`; + }); + dependencyChains.push(`Task ${depTask.id} depends on: ${subDeps.join(', ')}`); + } + } + + if (dependencyChains.length > 0) { + contextTasks += `\n\nDependency chains:\n${dependencyChains.join('\n')}`; + } + + // Show dependency chains in CLI mode + if (outputFormat === 'text' && dependentTasks.length > 0) { + console.log(chalk.gray(` Explicitly specified dependencies:`)); + dependentTasks.forEach(t => { + console.log(chalk.yellow(` • Task ${t.id}: ${truncate(t.title, 50)}`)); + }); + + if (dependencyChains.length > 0) { + console.log(chalk.gray(`\n Analyzing dependency chains:`)); + dependencyChains.forEach(chain => { + console.log(chalk.cyan(` • ${truncate(chain, 60)}`)); + }); + } + console.log(); // Add spacing + } + } else { + // If no dependencies provided, show recent and related tasks + // Get recent tasks (newest first) + const recentTasks = [...data.tasks] + .sort((a, b) => b.id - a.id) + .slice(0, 5); + + // Get tasks that might be related based on simple keyword matching + const keywords = prompt.toLowerCase().split(/\s+/).filter(word => + word.length > 4 && !['the', 'and', 'that', 'with', 'this', 'for', 'from'].includes(word) + ); + + const relatedTasks = keywords.length > 0 + ? data.tasks.filter(t => + keywords.some(keyword => + t.title.toLowerCase().includes(keyword) || + t.description.toLowerCase().includes(keyword) + ) && + !recentTasks.some(rt => rt.id === t.id) + ).slice(0, 3) + : []; + + if (recentTasks.length > 0) { + contextTasks = `\nRecent tasks in the project:\n${recentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + if (relatedTasks.length > 0) { + contextTasks += `\n\nPotentially related tasks based on keywords:\n${relatedTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + // Add a concise view of the task dependency structure + contextTasks += '\n\nSummary of task dependencies in the project:'; + const pendingTasks = data.tasks.filter(t => t.status !== 'done').slice(-10); + for (const task of pendingTasks) { + const depsStr = task.dependencies && task.dependencies.length > 0 + ? task.dependencies.join(', ') + : 'None'; + contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`; + } + + // Show keyword-based analysis in CLI mode + if (outputFormat === 'text') { + if (keywords.length > 0) { + console.log(chalk.gray(` Keywords extracted for analysis: ${keywords.join(', ')}`)); + + if (relatedTasks.length > 0) { + console.log(chalk.gray(`\n Potentially related tasks by keyword match:`)); + relatedTasks.forEach(t => { + console.log(chalk.yellow(` • Task ${t.id}: ${truncate(t.title, 50)}`)); + }); + } + } + + // Show the recent tasks used for context + if (recentTasks.length > 0) { + console.log(chalk.gray(`\n Using recent tasks for context:`)); + recentTasks.slice(0, 3).forEach(t => { + console.log(chalk.cyan(` • Task ${t.id}: ${truncate(t.title, 50)}`)); + }); + } + + // Show dependency structure visualization + if (pendingTasks.length > 0) { + const depsToShow = Math.min(3, pendingTasks.length); + console.log(chalk.gray(`\n Task dependency patterns analyzed (${depsToShow} example${depsToShow > 1 ? 's' : ''}):`)); + pendingTasks.slice(0, depsToShow).forEach(task => { + const depsStr = task.dependencies && task.dependencies.length > 0 + ? chalk.yellow(task.dependencies.join(', ')) + : chalk.gray('None'); + console.log(chalk.blue(` • Task ${task.id} → [${depsStr}]`)); + }); + } + console.log(); // Add spacing + } + } + + // System Prompt - Enhanced for dependency awareness +// ... existing code ... +``` + +Now let's also add a visual indicator before the AI call to make the transition clearer and make the experience more engaging: + +```javascript +// ... existing code ... + 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."; + + // 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", + "details": "In-depth implementation details, considerations, and guidance.", + "testStrategy": "Detailed approach for verifying task completion.", + "dependencies": [1, 2, 3] // Optional: Array of existing task IDs that this task depends on + }`; + + // 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. + + ${contextTasks} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + + Based on the information about existing tasks and their dependencies, include appropriate dependencies in the "dependencies" array if any are needed. Only include task IDs that this new task directly depends on. + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific. Don't include the task ID in the title.`; + + // Start the loading indicator - only for text mode + if (outputFormat === 'text') { + // Add a visual transition to show we're moving to AI generation + 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')}`, + { + padding: 1, + borderColor: 'blue', + borderStyle: 'round', + margin: { top: 0, bottom: 1 } + } + )); + + loadingIndicator = startLoadingIndicator( + `Generating new task with ${useResearch ? 'Research' : 'Main'} AI...\n` + ); + } + + try { +// ... existing code ... +``` + +Finally, let's enhance the success message to highlight the AI's dependency analysis and any dependencies added by the AI: + +```javascript +// ... existing code ... + truncate(newTask.description, 47) + ]); + + 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'; + } + }; + + // Check if AI added new dependencies that weren't explicitly provided + const aiAddedDeps = newTask.dependencies.filter( + dep => !numericDependencies.includes(dep) + ); + + // 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' + + (newTask.dependencies.length > 0 + ? chalk.white(`Dependencies: ${newTask.dependencies.join(', ')}`) + + (aiAddedDeps.length > 0 + ? chalk.gray(` (${aiAddedDeps.length} auto-detected by AI)`) + : '') + + '\n' + : '') + + '\n' + + chalk.white.bold('Next Steps:') + + '\n' + +// ... existing code ... +``` + +Let's test the changes to see our enhanced CLI experience in action: + +--- + +**User** + +it seems to have worked, but its now the second task that used 1 and 3 as dependencies. across 90 tasks, i find that a bit odd (but not impossible as most tasks are in fact net new) + +let's, for the sake of research, also include the details of the other tasks. let's see what happens with the context size + +--- + +**Cursor** + +I agree it's interesting that the AI consistently selects tasks 1 and 3 as dependencies. It found these tasks through the keyword matching (showing "Task 1: Implement Task Data Structure" and "Task 3: Implement Basic Task Operations"), but we should provide more comprehensive context to help it make better dependency selections. + +Let's enhance the task context by including task details for relevant tasks, not just their titles and descriptions: + +```javascript +// ... existing code ... + 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' + } + )); + } + + if (numericDependencies.length > 0) { + // If specific dependencies were provided, focus on them + const dependentTasks = data.tasks.filter((t) => + numericDependencies.includes(t.id) + ); + contextTasks = `\nThis task depends on the following tasks:\n${dependentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + + // Add more details about each dependency + contextTasks += `\n\nDetailed information about dependencies:`; + for (const depTask of dependentTasks) { + contextTasks += `\n\n------ Task ${depTask.id}: ${depTask.title} ------\n`; + contextTasks += `Description: ${depTask.description}\n`; + contextTasks += `Status: ${depTask.status || 'pending'}\n`; + contextTasks += `Priority: ${depTask.priority || 'medium'}\n`; + // Add implementation details but truncate if too long + if (depTask.details) { + const truncatedDetails = depTask.details.length > 500 + ? depTask.details.substring(0, 500) + '... (truncated)' + : depTask.details; + contextTasks += `Implementation Details: ${truncatedDetails}\n`; + } + } + + // Add dependency chain information (what these tasks depend on) + const dependencyChains = []; + for (const depTask of dependentTasks) { + if (depTask.dependencies && depTask.dependencies.length > 0) { + const subDeps = depTask.dependencies.map(subDepId => { + const subDepTask = data.tasks.find(t => t.id === subDepId); + return subDepTask ? `Task ${subDepId}: ${subDepTask.title}` : `Task ${subDepId}`; + }); + dependencyChains.push(`Task ${depTask.id} depends on: ${subDeps.join(', ')}`); + } + } + + if (dependencyChains.length > 0) { + contextTasks += `\n\nDependency chains:\n${dependencyChains.join('\n')}`; + } + + // Show dependency chains in CLI mode + if (outputFormat === 'text' && dependentTasks.length > 0) { + console.log(chalk.gray(` Explicitly specified dependencies:`)); + dependentTasks.forEach(t => { + console.log(chalk.yellow(` • Task ${t.id}: ${truncate(t.title, 50)}`)); + }); + + if (dependencyChains.length > 0) { + console.log(chalk.gray(`\n Analyzing dependency chains:`)); + dependencyChains.forEach(chain => { + console.log(chalk.cyan(` • ${truncate(chain, 60)}`)); + }); + } + console.log(); // Add spacing + } + } else { + // If no dependencies provided, show recent and related tasks + // Get recent tasks (newest first) + const recentTasks = [...data.tasks] + .sort((a, b) => b.id - a.id) + .slice(0, 5); + + // Get tasks that might be related based on simple keyword matching + const keywords = prompt.toLowerCase().split(/\s+/).filter(word => + word.length > 4 && !['the', 'and', 'that', 'with', 'this', 'for', 'from'].includes(word) + ); + + // Get related tasks by keyword matching + const relatedTasks = keywords.length > 0 + ? data.tasks.filter(t => + keywords.some(keyword => + t.title.toLowerCase().includes(keyword) || + t.description.toLowerCase().includes(keyword) + ) && + !recentTasks.some(rt => rt.id === t.id) + ).slice(0, 5) // Increased from 3 to 5 + : []; + + // 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"} + ]; + + const 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 => + !recentTasks.some(rt => rt.id === t.id) && + !relatedTasks.some(rt => rt.id === t.id) + ) + .slice(0, 3) + : []; + + // Format basic task overviews + if (recentTasks.length > 0) { + contextTasks = `\nRecent tasks in the project:\n${recentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + if (relatedTasks.length > 0) { + contextTasks += `\n\nPotentially related tasks based on keywords:\n${relatedTasks + .map((t) => `- 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')}`; + } + + // Add detailed information about the most relevant tasks + const allDetailedTasks = [...relatedTasks, ...categoryTasks.slice(0, 2), ...recentTasks.slice(0, 2)]; + const uniqueDetailedTasks = Array.from(new Map(allDetailedTasks.map(t => [t.id, t])).values()).slice(0, 5); + + 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) { + contextTasks += `Dependencies: ${task.dependencies.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:'; + + // Focus on pending tasks with the most similar purposes or categories + // This helps identify patterns in how similar tasks are set up + const relevantPendingTasks = promptCategory + ? data.tasks + .filter(t => t.status !== 'done' && (promptCategory.pattern.test(t.title) || promptCategory.pattern.test(t.description))) + .slice(-10) + : data.tasks.filter(t => t.status !== 'done').slice(-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 commandTasks = data.tasks.filter(t => /command|cli/i.test(t.title) || /command|cli/i.test(t.description)); + if (commandTasks.length > 0 && /command|cli/i.test(prompt)) { + contextTasks += '\n\nCommon patterns for command implementation tasks:'; + const commandDeps = commandTasks + .filter(t => t.dependencies && t.dependencies.length > 0) + .map(t => t.dependencies) + .flat(); + + // Count frequency of each dependency + const depCounts = {}; + commandDeps.forEach(dep => { + depCounts[dep] = (depCounts[dep] || 0) + 1; + }); + + // Get most common dependencies for command tasks + const commonDeps = Object.entries(depCounts) + .sort((a, b) => b[1] - a[1]) + .slice(0, 3); + + 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 keyword-based analysis in CLI mode + if (outputFormat === 'text') { + if (keywords.length > 0) { + console.log(chalk.gray(` Keywords extracted for analysis: ${keywords.join(', ')}`)); + + if (relatedTasks.length > 0) { + console.log(chalk.gray(`\n Potentially related tasks by keyword match:`)); + relatedTasks.forEach(t => { + console.log(chalk.yellow(` • 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 the recent tasks used for context + if (recentTasks.length > 0) { + console.log(chalk.gray(`\n Using recent tasks for context:`)); + recentTasks.slice(0, 3).forEach(t => { + console.log(chalk.cyan(` • Task ${t.id}: ${truncate(t.title, 50)}`)); + }); + } + + // Show dependency structure visualization + if (relevantPendingTasks.length > 0) { + const depsToShow = Math.min(3, relevantPendingTasks.length); + console.log(chalk.gray(`\n Task dependency patterns analyzed (${depsToShow} example${depsToShow > 1 ? 's' : ''}):`)); + relevantPendingTasks.slice(0, depsToShow).forEach(task => { + const depsStr = task.dependencies && task.dependencies.length > 0 + ? chalk.yellow(task.dependencies.join(', ')) + : chalk.gray('None'); + console.log(chalk.blue(` • Task ${task.id} → [${depsStr}]`)); + }); + } + + // 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 => { + console.log(chalk.green(` • Task ${t.id}: ${truncate(t.title, 40)}`)); + }); + } + + console.log(); // Add spacing + } + } + + // System Prompt - Enhanced for dependency awareness +// ... existing code ... +``` + +Now let's modify the task generation display to include context size information and make it more transparent about what's being sent to the AI: + +```javascript +// ... existing code ... + // Add a visual transition to show we're moving to AI generation + 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 + : (uniqueDetailedTasks ? uniqueDetailedTasks.length : 0) + )}` + + (promptCategory + ? `\n${chalk.cyan('Task category: ')}${chalk.yellow(promptCategory.label)}` + : '') + + `\n${chalk.cyan('Dependency analysis depth: ')}${chalk.yellow('Full chain analysis')}`, + { + padding: 1, + borderColor: 'blue', + borderStyle: 'round', + margin: { top: 0, bottom: 1 } + } + )); + + loadingIndicator = startLoadingIndicator( + `Generating new task with ${useResearch ? 'Research' : 'Main'} AI...\n` + ); + } + + try { +// ... existing code ... +``` + +Now, let's update the system prompt to include specific guidance for the AI on how to make better dependency choices: + +```javascript +// ... existing code ... + // 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. Avoid defaulting to early tasks (like tasks #1-3) unless truly necessary foundation work.\n" + + "4. Consider implementation details of candidate dependent tasks, not just titles.\n" + + "5. Include the most specific dependencies rather than their parents when appropriate.\n" + + "6. Focus on technical dependencies rather than organizational ones."; + + // Task Structure Description (for user prompt) +// ... existing code ... +``` + +Let's test the enhanced context and dependency analysis: + +--- + +**User** + +error + +--- + +**Cursor** + +I see the error - we're trying to use `uniqueDetailedTasks` in our display, but it's only defined in one code branch. Let's fix that: + +```javascript +// ... existing code ... + // 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 dependentTasks = []; + let uniqueDetailedTasks = []; + let promptCategory = null; + + if (numericDependencies.length > 0) { + // If specific dependencies were provided, focus on them + dependentTasks = data.tasks.filter((t) => + numericDependencies.includes(t.id) + ); + + // These are our detailed tasks when explicit dependencies are provided + uniqueDetailedTasks = dependentTasks; + + contextTasks = `\nThis task depends on the following tasks:\n${dependentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; +// ... existing code ... + + // Add a visual transition to show we're moving to AI generation + 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(uniqueDetailedTasks.length)}` + + (promptCategory + ? `\n${chalk.cyan('Task category: ')}${chalk.yellow(promptCategory.label)}` + : '') + + `\n${chalk.cyan('Dependency analysis depth: ')}${chalk.yellow('Full chain analysis')}`, +// ... existing code ... +``` + +Let's also fix the variable that's still defined inside the else branch instead of using our initialization: + +```javascript +// ... existing code ... + 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"} + ]; + + 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)) + ) +// ... existing code ... +``` + +Let's test our fix: + +--- + +**User** + +despite ALL that, we still got 1 and 3 -- does that imply our previous implemenation was fine? + +we didnt use that many input tokens so im guessing no other tasks were added as dependencies. idk, maybe the issue is the way the keywords/search is working. it should be more fuzzy/smarter or rely more on the LLM even though we definitely would prefer to do as much as we can here to prepare the same information in smaller/condensed/curated format before passing it + +but in the end what really matters is to properly capture dependencies. it's possible we dont actually need to -- you can get the get-tasks tool taht you have access to get ALL tasks and make that determination for yourself so you can do a spot check + +--- + +**Cursor** + +You're right - despite our enhanced context and guidance, the AI still defaulted to those foundational tasks (1 and 3). It's particularly interesting that the output shows "Detailed tasks: 0" even though we were providing detailed information for 5 tasks. + +This suggests a few things: + +1. There might still be an issue in how we're tracking `uniqueDetailedTasks` in the code. + +2. The model might have a bias toward those early tasks since they are genuinely foundational (Task 1: "Implement Task Data Structure" and Task 3: "Implement Basic Task Operations"). + +3. Our keyword matching might not be catching the truly relevant semantic connections between tasks. + +You've made an excellent point about letting the AI have more direct access to tasks. Here are two approaches we could take: + +1. **AI-assisted pre-processing**: We could use an AI call to identify potential dependencies before the main task creation, specifically to analyze relevance. + +2. **Direct access approach**: As you suggested, we could rely on the AI's ability to access all tasks directly through tools like `get_tasks`. This approach leverages the model's semantic understanding while giving it full access to the data. + +Would you like me to implement the fix for the "Detailed tasks: 0" issue, or would you prefer to move toward the direct access approach where the AI uses tools to gather its own context? + +--- + +**User** + +first, do a get-tasks to get that context + +--- + +**Cursor** + +Let me get all the tasks to better understand the context: + +--- + +**User** + +no. use the mcp tool get_tasks + +--- + +**Cursor** + +Let me get the tasks using the MCP tool: + +Now, let me get the task details for task ID 88 as you requested: + +Looking at the details of task #88 and the issue you mentioned, I'll now examine the current dependency handling in add-task.js: + +Let me examine more of the file to understand how dependencies are handled in the final part: + +Based on my analysis of the code and Task #88's requirements, let me identify how we can enhance the add-task functionality to better consider all task dependencies. Here's what I've found: + +1. **Current Implementation**: + - The add-task feature mainly considers explicitly provided dependencies (via CLI flags) or AI-suggested dependencies + - It provides some context to the AI about dependency chains when explicit dependencies are provided + - It does surface information about tasks with similar keywords, categories, or recent tasks when no explicit dependencies are provided + - It has logic to validate dependencies and remove invalid ones + +2. **Areas for Improvement**: + - The current implementation doesn't recursively analyze the dependency structure beyond one level + - When determining new task dependencies, it doesn't analyze how dependencies of dependencies might affect the new task + - It could provide more comprehensive dependency chain information to the AI model + - The system could better detect and suggest indirect dependencies automatically + +Let me implement these improvements to enhance the add-task functionality: + +```javascript +// ... existing code ... + +// 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 + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + report('Invalid or missing tasks.json.', 'error'); + throw new Error('Invalid or missing tasks.json.'); + } + + // 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 dependentTasks = []; + let uniqueDetailedTasks = []; + 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 { +// ... existing code ... +} + +// ... existing code ... + +// 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); + }); + + 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); + +// ... existing code ... +``` + +Now, let's update the system prompt to better guide the AI in making accurate dependency recommendations using the enhanced dependency information we're providing: + +```javascript +// ... existing code ... + + // 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. Avoid defaulting to early tasks (like tasks #1-3) unless truly necessary foundation work.\n" + + "4. Consider implementation details of candidate dependent tasks, not just titles.\n" + + "5. Include the most specific dependencies rather than their parents when appropriate.\n" + + "6. Focus on technical dependencies rather than organizational ones."; + +// ... existing code to ... + + // System Prompt - Enhanced for comprehensive 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. Analyze the entire dependency chain to find the most specific and direct dependencies.\n" + + "3. Consider both direct and indirect (transitive) dependencies - a task may depend on another task that depends on yet another task.\n" + + "4. When possible, reference the most specific or leaf-node tasks in a dependency chain rather than their parents.\n" + + "5. Prioritize task dependencies that are semantically related to the functionality being built.\n" + + "6. Consider the depth of the dependency chain - tasks deeper in the chain might be more specific.\n" + + "7. Respect the existing dependency structure in the project to maintain consistency.\n" + + "8. Avoid defaulting to early foundational tasks (like tasks #1-3) unless absolutely necessary.\n" + + "9. Consider implementation details of candidate dependent tasks, not just their titles.\n" + + "10. Balance between too many dependencies (overly constrained) and too few (missing prerequisite work).\n" + + "11. Pay attention to tasks marked as 'done' vs 'pending' when considering dependencies.\n" + + "12. Base dependency decisions on technical requirements rather than organizational preferences."; + +// ... existing code ... +``` + +Now, let's update the user prompt to utilize the enhanced dependency information more effectively: + +```javascript +// ... existing code ... + + // 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}` : ''} + + Based on the information about existing tasks and their dependencies, include appropriate dependencies in the "dependencies" array if any are needed. Only include task IDs that this new task directly depends on. + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific. Don't include the task ID in the title.`; + +// ... existing code to ... + + // User Prompt - Enhanced for better dependency analysis + 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}` : ''} + + Based on the detailed information about existing tasks and their dependency relationships, carefully consider which dependencies this new task should have: + + 1. ANALYZE: Review the entire dependency structure provided above, including both direct and indirect dependencies. + 2. IDENTIFY: Determine which specific existing tasks must be completed before this new task can begin. + 3. CONSIDER: Think about both technical requirements and logical task ordering. + 4. DECIDE: Select only the most direct, specific dependencies - avoid including a parent task if you're already including its child task. + 5. VALIDATE: Ensure all selected dependencies are necessary and sufficient for this task to begin. + + Include these carefully selected task IDs in the "dependencies" array of your response. If there are no logical dependencies, return an empty array []. + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific. Don't include the task ID in the title.`; + +// ... existing code ... +``` + +Let's also enhance the CLI output to display dependency information for the newly created task: + +```javascript +// ... existing code ... + + // Check if AI added new dependencies that weren't explicitly provided + const aiAddedDeps = newTask.dependencies.filter( + dep => !numericDependencies.includes(dep) + ); + + // 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' + + (newTask.dependencies.length > 0 + ? chalk.white(`Dependencies: ${newTask.dependencies.join(', ')}`) + + (aiAddedDeps.length > 0 + ? chalk.gray(` (${aiAddedDeps.length} auto-detected by AI)`) + : '') + + '\n' + : '') + + '\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' } + ) + ); + +// ... existing code to ... + + // 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) + ); + + // 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'; + } + + // 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'; + } + } + + // 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' } + ) + ); + +// ... existing code ... +``` + +Let's test our changes to see how they improve the task dependency handling: + diff --git a/package-lock.json b/package-lock.json index 2a437a53..f6d1ac92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "task-master-ai", - "version": "0.13.2", + "version": "0.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "task-master-ai", - "version": "0.13.2", + "version": "0.14.0", "license": "MIT WITH Commons-Clause", "dependencies": { "@ai-sdk/anthropic": "^1.2.10", @@ -28,7 +28,7 @@ "express": "^4.21.2", "fastmcp": "^1.20.5", "figlet": "^1.8.0", - "fuse.js": "^7.0.0", + "fuse.js": "^7.1.0", "gradient-string": "^3.0.0", "helmet": "^8.1.0", "inquirer": "^12.5.0", diff --git a/package.json b/package.json index 5ff7adb3..52bbd6f4 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "express": "^4.21.2", "fastmcp": "^1.20.5", "figlet": "^1.8.0", - "fuse.js": "^7.0.0", + "fuse.js": "^7.1.0", "gradient-string": "^3.0.0", "helmet": "^8.1.0", "inquirer": "^12.5.0", diff --git a/scripts/modules/task-manager/add-task.js b/scripts/modules/task-manager/add-task.js index 65b9d3b6..305d36bc 100644 --- a/scripts/modules/task-manager/add-task.js +++ b/scripts/modules/task-manager/add-task.js @@ -3,6 +3,7 @@ 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, @@ -27,7 +28,9 @@ const AiTaskDataSchema = z.object({ .describe('In-depth implementation details, considerations, and guidance'), testStrategy: z .string() - .describe('Detailed approach for verifying task completion') + .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)') }); /** @@ -92,6 +95,55 @@ async function addTask( } }; + /** + * 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 const data = readJSON(tasksPath); @@ -137,6 +189,24 @@ async function addTask( // 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 @@ -163,36 +233,547 @@ async function addTask( // 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) { - const dependentTasks = data.tasks.filter((t) => - numericDependencies.includes(t.id) - ); - contextTasks = `\nThis task depends on the following tasks:\n${dependentTasks - .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) - .join('\n')}`; + // 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, 3); - if (recentTasks.length > 0) { - contextTasks = `\nRecent tasks in the project:\n${recentTasks + .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 + } } - // System Prompt + // 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 + 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."; + "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", - "details": "In-depth implementation details, considerations, and guidance.", - "testStrategy": "Detailed approach for verifying task completion." - }`; + "details": "Detailed implementation steps, considerations, code examples, or technical approach", + "testStrategy": "Specific steps to verify correct implementation and functionality", + "dependencies": [1, 3] // Example: IDs of tasks that must be completed before this task + } +`; // Add any manually provided details to the prompt for context let contextFromArgs = ''; @@ -206,15 +787,18 @@ async function addTask( contextFromArgs += `\n- Additional Test Strategy Context: "${manualTaskData.testStrategy}"`; // User Prompt - const userPrompt = `Create a comprehensive new task (Task #${newTaskId}) for a software development project based on this description: "${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}` : ''} + 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. + Return your answer as a single JSON object matching the schema precisely: ${taskStructureDesc} - Make sure the details and test strategy are thorough and specific.`; + 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') { @@ -287,11 +871,27 @@ async function addTask( details: taskData.details || '', testStrategy: taskData.testStrategy || '', status: 'pending', - dependencies: numericDependencies, // Use validated numeric dependencies + 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); + }); + + 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); @@ -340,6 +940,60 @@ async function addTask( } }; + // 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) + ); + + // 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'; + } + + // 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'; + } + } + // Show success message box console.log( boxen( @@ -352,11 +1006,9 @@ async function addTask( chalk.white( `Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}` ) + - '\n' + - (numericDependencies.length > 0 - ? chalk.white(`Dependencies: ${numericDependencies.join(', ')}`) + - '\n' - : '') + + '\n\n' + + dependencyDisplay + + dependencyAnalysis + '\n' + chalk.white.bold('Next Steps:') + '\n' + diff --git a/tasks/task_089.txt b/tasks/task_089.txt index 4bf9738a..47660d45 100644 --- a/tasks/task_089.txt +++ b/tasks/task_089.txt @@ -1,5 +1,5 @@ # Task ID: 89 -# Title: Task #89: Introduce Prioritize Command with Enhanced Priority Levels +# Title: Introduce Prioritize Command with Enhanced Priority Levels # Status: pending # Dependencies: None # Priority: medium diff --git a/tasks/task_090.txt b/tasks/task_090.txt new file mode 100644 index 00000000..69914e8c --- /dev/null +++ b/tasks/task_090.txt @@ -0,0 +1,67 @@ +# Task ID: 90 +# Title: Implement Subtask Progress Analyzer and Reporting System +# Status: pending +# Dependencies: 1, 3 +# Priority: medium +# Description: Develop a subtask analyzer that monitors the progress of all subtasks, validates their status, and generates comprehensive reports for users to track project advancement. +# Details: +The subtask analyzer should be implemented with the following components and considerations: + +1. Progress Tracking Mechanism: + - Create a function to scan the task data structure and identify all tasks with subtasks + - Implement logic to determine the completion status of each subtask + - Calculate overall progress percentages for tasks with multiple subtasks + +2. Status Validation: + - Develop validation rules to check if subtasks are progressing according to expected timelines + - Implement detection for stalled or blocked subtasks + - Create alerts for subtasks that are behind schedule or have dependency issues + +3. Reporting System: + - Design a structured report format that clearly presents: + - Overall project progress + - Task-by-task breakdown with subtask status + - Highlighted issues or blockers + - Support multiple output formats (console, JSON, exportable text) + - Include visual indicators for progress (e.g., progress bars in CLI) + +4. Integration Points: + - Hook into the existing task management system + - Ensure the analyzer can be triggered via CLI commands + - Make the reporting feature accessible through the main command interface + +5. Performance Considerations: + - Optimize for large task lists with many subtasks + - Implement caching if necessary to avoid redundant calculations + - Ensure reports generate quickly even for complex project structures + +The implementation should follow the existing code style and patterns, leveraging the task data structure already in place. The analyzer should be non-intrusive to existing functionality while providing valuable insights to users. + +# Test Strategy: +Testing for the subtask analyzer should include: + +1. Unit Tests: + - Test the progress calculation logic with various task/subtask configurations + - Verify status validation correctly identifies issues in different scenarios + - Ensure report generation produces consistent and accurate output + - Test edge cases (empty subtasks, all complete, all incomplete, mixed states) + +2. Integration Tests: + - Verify the analyzer correctly integrates with the existing task data structure + - Test CLI command integration and parameter handling + - Ensure reports reflect actual changes to task/subtask status + +3. Performance Tests: + - Benchmark report generation with large task sets (100+ tasks with multiple subtasks) + - Verify memory usage remains reasonable during analysis + +4. User Acceptance Testing: + - Create sample projects with various subtask configurations + - Generate reports and verify they provide clear, actionable information + - Confirm visual indicators accurately represent progress + +5. Regression Testing: + - Verify that the analyzer doesn't interfere with existing task management functionality + - Ensure backward compatibility with existing task data structures + +Documentation should be updated to include examples of how to use the new analyzer and interpret the reports. Success criteria include accurate progress tracking, clear reporting, and performance that scales with project size. diff --git a/tasks/task_091.txt b/tasks/task_091.txt new file mode 100644 index 00000000..a62bf955 --- /dev/null +++ b/tasks/task_091.txt @@ -0,0 +1,11 @@ +# Task ID: 91 +# Title: Implement Move Command for Tasks and Subtasks +# Status: pending +# Dependencies: 1, 3 +# Priority: medium +# Description: Introduce a 'move' command to enable moving tasks or subtasks to a different id, facilitating conflict resolution by allowing teams to assign new ids as needed. +# Details: +The move command should accept --id/-i and --to/-t flags, where --id specifies the task/subtask to be moved and --to indicates the new parent id for subtasks. Ensure that the command works seamlessly with both tasks and subtasks, allowing users to reassign them within the same project structure. The implementation should handle edge cases such as invalid ids, non-existent parents, and circular dependencies. + +# Test Strategy: +Verify the move command by testing various scenarios including moving a task to a new id, moving a subtask under a different parent while preserving its hierarchy, handling errors for invalid or non-existent ids, and ensuring that the command does not disrupt existing task relationships. diff --git a/tasks/task_092.txt b/tasks/task_092.txt new file mode 100644 index 00000000..d3b1c233 --- /dev/null +++ b/tasks/task_092.txt @@ -0,0 +1,32 @@ +# Task ID: 92 +# Title: Implement Scheduling System for Periodic Task Execution +# Status: pending +# Dependencies: 1, 3, 11, 17, 19 +# Priority: medium +# Description: Develop a system that allows tasks to be scheduled with custom time intervals and timezone configuration, enabling periodic execution of tasks at specified times. +# Details: +1. Design the scheduling system architecture to support: + - Custom time intervals (e.g., every X minutes/hours/days) + - Timezone configuration + - Task prioritization in scheduling +2. Implement the core scheduling functionality: + - Create a scheduler service that manages scheduled tasks + - Develop task execution logic based on schedules + - Integrate with existing task management system (Task 3) +3. Add user interface for scheduling tasks: + - Create command-line options for setting up scheduled tasks + - Implement configuration validation and error handling (Task 19) +4. Ensure proper logging of scheduled tasks and their execution (Task 17) +5. Handle timezone conversions accurately +6. Implement batch scheduling operations (Task 11) +7. Add support for recurring tasks with complex schedules (e.g., cron-like syntax) +8. Integrate with the existing task data structure (Task 1) + +# Test Strategy: +1. Verify that tasks can be scheduled with various time intervals and timezone configurations +2. Test scheduling edge cases (e.g., overlapping schedules, different timezones) +3. Check proper execution of scheduled tasks at specified times +4. Validate logging of scheduled task executions +5. Ensure error handling works correctly for invalid schedule configurations +6. Test batch scheduling operations +7. Verify timezone conversion accuracy diff --git a/tasks/tasks.json b/tasks/tasks.json index 7f745653..763049f7 100644 --- a/tasks/tasks.json +++ b/tasks/tasks.json @@ -5305,13 +5305,58 @@ }, { "id": 89, - "title": "Task #89: Introduce Prioritize Command with Enhanced Priority Levels", + "title": "Introduce Prioritize Command with Enhanced Priority Levels", "description": "Implement a prioritize command with --up/--down/--priority/--id flags and shorthand equivalents (-u/-d/-p/-i). Add 'lowest' and 'highest' priority levels, updating CLI output accordingly.", - "details": "The new prioritize command should allow users to adjust task priorities using the specified flags. The --up and --down flags will modify the priority relative to the current level, while --priority sets an absolute priority. The --id flag specifies which task to prioritize. Shorthand equivalents (-u/-d/-p/-i) should be supported for user convenience.\n\nThe priority levels should now include 'lowest', 'low', 'medium', 'high', and 'highest'. The CLI output should be updated to reflect these new priority levels accurately.\n\nConsiderations:\n- Ensure backward compatibility with existing commands and configurations.\n- Update the help documentation to include the new command and its usage.\n- Implement proper error handling for invalid priority levels or missing flags.", - "testStrategy": "To verify task completion, perform the following tests:\n1. Test each flag (--up, --down, --priority, --id) individually and in combination to ensure they function as expected.\n2. Verify that shorthand equivalents (-u, -d, -p, -i) work correctly.\n3. Check that the new priority levels ('lowest' and 'highest') are recognized and displayed properly in CLI output.\n4. Test error handling for invalid inputs (e.g., non-existent task IDs, invalid priority levels).\n5. Ensure that the help command displays accurate information about the new prioritize command.", "status": "pending", "dependencies": [], "priority": "medium", + "details": "The new prioritize command should allow users to adjust task priorities using the specified flags. The --up and --down flags will modify the priority relative to the current level, while --priority sets an absolute priority. The --id flag specifies which task to prioritize. Shorthand equivalents (-u/-d/-p/-i) should be supported for user convenience.\n\nThe priority levels should now include 'lowest', 'low', 'medium', 'high', and 'highest'. The CLI output should be updated to reflect these new priority levels accurately.\n\nConsiderations:\n- Ensure backward compatibility with existing commands and configurations.\n- Update the help documentation to include the new command and its usage.\n- Implement proper error handling for invalid priority levels or missing flags.", + "testStrategy": "To verify task completion, perform the following tests:\n1. Test each flag (--up, --down, --priority, --id) individually and in combination to ensure they function as expected.\n2. Verify that shorthand equivalents (-u, -d, -p, -i) work correctly.\n3. Check that the new priority levels ('lowest' and 'highest') are recognized and displayed properly in CLI output.\n4. Test error handling for invalid inputs (e.g., non-existent task IDs, invalid priority levels).\n5. Ensure that the help command displays accurate information about the new prioritize command.", + "subtasks": [] + }, + { + "id": 90, + "title": "Implement Subtask Progress Analyzer and Reporting System", + "description": "Develop a subtask analyzer that monitors the progress of all subtasks, validates their status, and generates comprehensive reports for users to track project advancement.", + "details": "The subtask analyzer should be implemented with the following components and considerations:\n\n1. Progress Tracking Mechanism:\n - Create a function to scan the task data structure and identify all tasks with subtasks\n - Implement logic to determine the completion status of each subtask\n - Calculate overall progress percentages for tasks with multiple subtasks\n\n2. Status Validation:\n - Develop validation rules to check if subtasks are progressing according to expected timelines\n - Implement detection for stalled or blocked subtasks\n - Create alerts for subtasks that are behind schedule or have dependency issues\n\n3. Reporting System:\n - Design a structured report format that clearly presents:\n - Overall project progress\n - Task-by-task breakdown with subtask status\n - Highlighted issues or blockers\n - Support multiple output formats (console, JSON, exportable text)\n - Include visual indicators for progress (e.g., progress bars in CLI)\n\n4. Integration Points:\n - Hook into the existing task management system\n - Ensure the analyzer can be triggered via CLI commands\n - Make the reporting feature accessible through the main command interface\n\n5. Performance Considerations:\n - Optimize for large task lists with many subtasks\n - Implement caching if necessary to avoid redundant calculations\n - Ensure reports generate quickly even for complex project structures\n\nThe implementation should follow the existing code style and patterns, leveraging the task data structure already in place. The analyzer should be non-intrusive to existing functionality while providing valuable insights to users.", + "testStrategy": "Testing for the subtask analyzer should include:\n\n1. Unit Tests:\n - Test the progress calculation logic with various task/subtask configurations\n - Verify status validation correctly identifies issues in different scenarios\n - Ensure report generation produces consistent and accurate output\n - Test edge cases (empty subtasks, all complete, all incomplete, mixed states)\n\n2. Integration Tests:\n - Verify the analyzer correctly integrates with the existing task data structure\n - Test CLI command integration and parameter handling\n - Ensure reports reflect actual changes to task/subtask status\n\n3. Performance Tests:\n - Benchmark report generation with large task sets (100+ tasks with multiple subtasks)\n - Verify memory usage remains reasonable during analysis\n\n4. User Acceptance Testing:\n - Create sample projects with various subtask configurations\n - Generate reports and verify they provide clear, actionable information\n - Confirm visual indicators accurately represent progress\n\n5. Regression Testing:\n - Verify that the analyzer doesn't interfere with existing task management functionality\n - Ensure backward compatibility with existing task data structures\n\nDocumentation should be updated to include examples of how to use the new analyzer and interpret the reports. Success criteria include accurate progress tracking, clear reporting, and performance that scales with project size.", + "status": "pending", + "dependencies": [ + 1, + 3 + ], + "priority": "medium", + "subtasks": [] + }, + { + "id": 91, + "title": "Implement Move Command for Tasks and Subtasks", + "description": "Introduce a 'move' command to enable moving tasks or subtasks to a different id, facilitating conflict resolution by allowing teams to assign new ids as needed.", + "details": "The move command should accept --id/-i and --to/-t flags, where --id specifies the task/subtask to be moved and --to indicates the new parent id for subtasks. Ensure that the command works seamlessly with both tasks and subtasks, allowing users to reassign them within the same project structure. The implementation should handle edge cases such as invalid ids, non-existent parents, and circular dependencies.", + "testStrategy": "Verify the move command by testing various scenarios including moving a task to a new id, moving a subtask under a different parent while preserving its hierarchy, handling errors for invalid or non-existent ids, and ensuring that the command does not disrupt existing task relationships.", + "status": "pending", + "dependencies": [ + 1, + 3 + ], + "priority": "medium", + "subtasks": [] + }, + { + "id": 92, + "title": "Implement Scheduling System for Periodic Task Execution", + "description": "Develop a system that allows tasks to be scheduled with custom time intervals and timezone configuration, enabling periodic execution of tasks at specified times.", + "details": "1. Design the scheduling system architecture to support:\n - Custom time intervals (e.g., every X minutes/hours/days)\n - Timezone configuration\n - Task prioritization in scheduling\n2. Implement the core scheduling functionality:\n - Create a scheduler service that manages scheduled tasks\n - Develop task execution logic based on schedules\n - Integrate with existing task management system (Task 3)\n3. Add user interface for scheduling tasks:\n - Create command-line options for setting up scheduled tasks\n - Implement configuration validation and error handling (Task 19)\n4. Ensure proper logging of scheduled tasks and their execution (Task 17)\n5. Handle timezone conversions accurately\n6. Implement batch scheduling operations (Task 11)\n7. Add support for recurring tasks with complex schedules (e.g., cron-like syntax)\n8. Integrate with the existing task data structure (Task 1)", + "testStrategy": "1. Verify that tasks can be scheduled with various time intervals and timezone configurations\n2. Test scheduling edge cases (e.g., overlapping schedules, different timezones)\n3. Check proper execution of scheduled tasks at specified times\n4. Validate logging of scheduled task executions\n5. Ensure error handling works correctly for invalid schedule configurations\n6. Test batch scheduling operations\n7. Verify timezone conversion accuracy", + "status": "pending", + "dependencies": [ + 1, + 3, + 11, + 17, + 19 + ], + "priority": "medium", "subtasks": [] } ]