67 KiB
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:
// ... 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!
// ... 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:
// ... 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:
// 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:
// ... 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:
// ... 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:
// ... 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:
// ... 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:
// ... 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:
// ... 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:
// ... 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:
// ... 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:
// ... 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:
-
There might still be an issue in how we're tracking
uniqueDetailedTasks
in the code. -
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").
-
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:
-
AI-assisted pre-processing: We could use an AI call to identify potential dependencies before the main task creation, specifically to analyze relevance.
-
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:
-
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
-
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:
// ... 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:
// ... 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:
// ... 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:
// ... 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: