feat(mcp): Add tagInfo to responses and integrate ContextGatherer
Enhances the MCP server to include 'tagInfo' (currentTag, availableTags) in all tool responses, providing better client-side context.
- Introduces a new 'ContextGatherer' utility to standardize the collection of file, task, and project context for AI-powered commands. This refactors several task-manager modules ('expand-task', 'research', 'update-task', etc.) to use the new utility.
- Fixes an issue in 'get-task' and 'get-tasks' MCP tools where the 'projectRoot' was not being passed correctly, preventing tag information from being included in their responses.
- Adds subtask '103.17' to track the implementation of the task template importing feature.
- Updates documentation ('.cursor/rules', 'docs/') to align with the new tagged task system and context gatherer logic.
This commit is contained in:
@@ -12,12 +12,14 @@ import {
|
||||
stopLoadingIndicator,
|
||||
succeedLoadingIndicator,
|
||||
failLoadingIndicator,
|
||||
displayAiUsageSummary
|
||||
displayAiUsageSummary,
|
||||
displayContextAnalysis
|
||||
} from '../ui.js';
|
||||
import { readJSON, writeJSON, log as consoleLog, truncate } from '../utils.js';
|
||||
import { generateObjectService } from '../ai-services-unified.js';
|
||||
import { getDefaultPriority } from '../config-manager.js';
|
||||
import generateTaskFiles from './generate-task-files.js';
|
||||
import ContextGatherer from '../utils/contextGatherer.js';
|
||||
|
||||
// Define Zod schema for the expected AI output object
|
||||
const AiTaskDataSchema = z.object({
|
||||
@@ -199,7 +201,9 @@ async function addTask(
|
||||
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);
|
||||
return (
|
||||
Number.isNaN(numDepId) || !data.tasks.some((t) => t.id === numDepId)
|
||||
);
|
||||
});
|
||||
|
||||
if (invalidDeps.length > 0) {
|
||||
@@ -262,561 +266,27 @@ async function addTask(
|
||||
// --- 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
|
||||
};
|
||||
// --- Use the new ContextGatherer ---
|
||||
const contextGatherer = new ContextGatherer(projectRoot);
|
||||
const gatherResult = await contextGatherer.gather({
|
||||
semanticQuery: prompt,
|
||||
dependencyTasks: numericDependencies,
|
||||
format: 'research'
|
||||
});
|
||||
|
||||
// CLI-only feedback for the dependency analysis
|
||||
if (outputFormat === 'text') {
|
||||
console.log(
|
||||
boxen(chalk.cyan.bold('Task Context Analysis'), {
|
||||
padding: { top: 0, bottom: 0, left: 1, right: 1 },
|
||||
margin: { top: 0, bottom: 0 },
|
||||
borderColor: 'cyan',
|
||||
borderStyle: 'round'
|
||||
})
|
||||
);
|
||||
const gatheredContext = gatherResult.context;
|
||||
const analysisData = gatherResult.analysisData;
|
||||
|
||||
// Display context analysis if not in silent mode
|
||||
if (outputFormat === 'text' && analysisData) {
|
||||
displayContextAnalysis(analysisData, prompt, gatheredContext.length);
|
||||
}
|
||||
|
||||
// Initialize variables that will be used in either branch
|
||||
let uniqueDetailedTasks = [];
|
||||
let dependentTasks = [];
|
||||
let promptCategory = null;
|
||||
|
||||
if (numericDependencies.length > 0) {
|
||||
// If specific dependencies were provided, focus on them
|
||||
// Get all tasks that were found in the dependency graph
|
||||
dependentTasks = Array.from(allRelatedTaskIds)
|
||||
.map((id) => data.tasks.find((t) => t.id === id))
|
||||
.filter(Boolean);
|
||||
|
||||
// Sort by depth in the dependency chain
|
||||
dependentTasks.sort((a, b) => {
|
||||
const depthA = depthMap.get(a.id) || 0;
|
||||
const depthB = depthMap.get(b.id) || 0;
|
||||
return depthA - depthB; // Lowest depth (root dependencies) first
|
||||
});
|
||||
|
||||
// Limit the number of detailed tasks to avoid context explosion
|
||||
uniqueDetailedTasks = dependentTasks.slice(0, 8);
|
||||
|
||||
contextTasks = `\nThis task relates to a dependency structure with ${dependentTasks.length} related tasks in the chain.\n\nDirect dependencies:`;
|
||||
const directDeps = data.tasks.filter((t) =>
|
||||
numericDependencies.includes(t.id)
|
||||
);
|
||||
contextTasks += `\n${directDeps.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`).join('\n')}`;
|
||||
|
||||
// Add an overview of indirect dependencies if present
|
||||
const indirectDeps = dependentTasks.filter(
|
||||
(t) => !numericDependencies.includes(t.id)
|
||||
);
|
||||
if (indirectDeps.length > 0) {
|
||||
contextTasks += `\n\nIndirect dependencies (dependencies of dependencies):`;
|
||||
contextTasks += `\n${indirectDeps
|
||||
.slice(0, 5)
|
||||
.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`)
|
||||
.join('\n')}`;
|
||||
if (indirectDeps.length > 5) {
|
||||
contextTasks += `\n- ... and ${indirectDeps.length - 5} more indirect dependencies`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add more details about each dependency, prioritizing direct dependencies
|
||||
contextTasks += `\n\nDetailed information about dependencies:`;
|
||||
for (const depTask of uniqueDetailedTasks) {
|
||||
const depthInfo = depthMap.get(depTask.id)
|
||||
? ` (depth: ${depthMap.get(depTask.id)})`
|
||||
: '';
|
||||
const isDirect = numericDependencies.includes(depTask.id)
|
||||
? ' [DIRECT DEPENDENCY]'
|
||||
: '';
|
||||
|
||||
contextTasks += `\n\n------ Task ${depTask.id}${isDirect}${depthInfo}: ${depTask.title} ------\n`;
|
||||
contextTasks += `Description: ${depTask.description}\n`;
|
||||
contextTasks += `Status: ${depTask.status || 'pending'}\n`;
|
||||
contextTasks += `Priority: ${depTask.priority || 'medium'}\n`;
|
||||
|
||||
// List its dependencies
|
||||
if (depTask.dependencies && depTask.dependencies.length > 0) {
|
||||
const depDeps = depTask.dependencies.map((dId) => {
|
||||
const depDepTask = data.tasks.find((t) => t.id === dId);
|
||||
return depDepTask
|
||||
? `Task ${dId}: ${depDepTask.title}`
|
||||
: `Task ${dId}`;
|
||||
});
|
||||
contextTasks += `Dependencies: ${depDeps.join(', ')}\n`;
|
||||
} else {
|
||||
contextTasks += `Dependencies: None\n`;
|
||||
}
|
||||
|
||||
// Add implementation details but truncate if too long
|
||||
if (depTask.details) {
|
||||
const truncatedDetails =
|
||||
depTask.details.length > 400
|
||||
? depTask.details.substring(0, 400) + '... (truncated)'
|
||||
: depTask.details;
|
||||
contextTasks += `Implementation Details: ${truncatedDetails}\n`;
|
||||
}
|
||||
}
|
||||
|
||||
// Add dependency chain visualization
|
||||
if (dependencyGraphs.length > 0) {
|
||||
contextTasks += '\n\nDependency Chain Visualization:';
|
||||
|
||||
// Helper function to format dependency chain as text
|
||||
function formatDependencyChain(
|
||||
node,
|
||||
prefix = '',
|
||||
isLast = true,
|
||||
depth = 0
|
||||
) {
|
||||
if (depth > 3) return ''; // Limit depth to avoid excessive nesting
|
||||
|
||||
const connector = isLast ? '└── ' : '├── ';
|
||||
const childPrefix = isLast ? ' ' : '│ ';
|
||||
|
||||
let result = `\n${prefix}${connector}Task ${node.id}: ${node.title}`;
|
||||
|
||||
if (node.dependencies && node.dependencies.length > 0) {
|
||||
for (let i = 0; i < node.dependencies.length; i++) {
|
||||
const isLastChild = i === node.dependencies.length - 1;
|
||||
result += formatDependencyChain(
|
||||
node.dependencies[i],
|
||||
prefix + childPrefix,
|
||||
isLastChild,
|
||||
depth + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Format each dependency graph
|
||||
for (const graph of dependencyGraphs) {
|
||||
contextTasks += formatDependencyChain(graph);
|
||||
}
|
||||
}
|
||||
|
||||
// Show dependency analysis in CLI mode
|
||||
if (outputFormat === 'text') {
|
||||
if (directDeps.length > 0) {
|
||||
console.log(chalk.gray(` Explicitly specified dependencies:`));
|
||||
directDeps.forEach((t) => {
|
||||
console.log(
|
||||
chalk.yellow(` • Task ${t.id}: ${truncate(t.title, 50)}`)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (indirectDeps.length > 0) {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
`\n Indirect dependencies (${indirectDeps.length} total):`
|
||||
)
|
||||
);
|
||||
indirectDeps.slice(0, 3).forEach((t) => {
|
||||
const depth = depthMap.get(t.id) || 0;
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
` • Task ${t.id} [depth ${depth}]: ${truncate(t.title, 45)}`
|
||||
)
|
||||
);
|
||||
});
|
||||
if (indirectDeps.length > 3) {
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
` • ... and ${indirectDeps.length - 3} more indirect dependencies`
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Visualize the dependency chain
|
||||
if (dependencyGraphs.length > 0) {
|
||||
console.log(chalk.gray(`\n Dependency chain visualization:`));
|
||||
|
||||
// Convert dependency graph to ASCII art for terminal
|
||||
function visualizeDependencyGraph(
|
||||
node,
|
||||
prefix = '',
|
||||
isLast = true,
|
||||
depth = 0
|
||||
) {
|
||||
if (depth > 2) return; // Limit depth for display
|
||||
|
||||
const connector = isLast ? '└── ' : '├── ';
|
||||
const childPrefix = isLast ? ' ' : '│ ';
|
||||
|
||||
console.log(
|
||||
chalk.blue(
|
||||
` ${prefix}${connector}Task ${node.id}: ${truncate(node.title, 40)}`
|
||||
)
|
||||
);
|
||||
|
||||
if (node.dependencies && node.dependencies.length > 0) {
|
||||
for (let i = 0; i < node.dependencies.length; i++) {
|
||||
const isLastChild = i === node.dependencies.length - 1;
|
||||
visualizeDependencyGraph(
|
||||
node.dependencies[i],
|
||||
prefix + childPrefix,
|
||||
isLastChild,
|
||||
depth + 1
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Visualize each dependency graph
|
||||
for (const graph of dependencyGraphs) {
|
||||
visualizeDependencyGraph(graph);
|
||||
}
|
||||
}
|
||||
|
||||
console.log(); // Add spacing
|
||||
}
|
||||
} else {
|
||||
// If no dependencies provided, use Fuse.js to find semantically related tasks
|
||||
// Create fuzzy search index for all tasks
|
||||
const searchOptions = {
|
||||
includeScore: true, // Return match scores
|
||||
threshold: 0.4, // Lower threshold = stricter matching (range 0-1)
|
||||
keys: [
|
||||
{ name: 'title', weight: 1.5 }, // Title is most important
|
||||
{ name: 'description', weight: 2 }, // Description is very important
|
||||
{ name: 'details', weight: 3 }, // Details is most important
|
||||
// Search dependencies to find tasks that depend on similar things
|
||||
{ name: 'dependencyTitles', weight: 0.5 }
|
||||
],
|
||||
// Sort matches by score (lower is better)
|
||||
shouldSort: true,
|
||||
// Allow searching in nested properties
|
||||
useExtendedSearch: true,
|
||||
// Return up to 50 matches
|
||||
limit: 50
|
||||
};
|
||||
|
||||
// Prepare task data with dependencies expanded as titles for better semantic search
|
||||
const searchableTasks = data.tasks.map((task) => {
|
||||
// Get titles of this task's dependencies if they exist
|
||||
const dependencyTitles =
|
||||
task.dependencies?.length > 0
|
||||
? task.dependencies
|
||||
.map((depId) => {
|
||||
const depTask = data.tasks.find((t) => t.id === depId);
|
||||
return depTask ? depTask.title : '';
|
||||
})
|
||||
.filter((title) => title)
|
||||
.join(' ')
|
||||
: '';
|
||||
|
||||
return {
|
||||
...task,
|
||||
dependencyTitles
|
||||
};
|
||||
});
|
||||
|
||||
// Create search index using Fuse.js
|
||||
const fuse = new Fuse(searchableTasks, searchOptions);
|
||||
|
||||
// Extract significant words and phrases from the prompt
|
||||
const promptWords = prompt
|
||||
.toLowerCase()
|
||||
.replace(/[^\w\s-]/g, ' ') // Replace non-alphanumeric chars with spaces
|
||||
.split(/\s+/)
|
||||
.filter((word) => word.length > 3); // Words at least 4 chars
|
||||
|
||||
// Use the user's prompt for fuzzy search
|
||||
const fuzzyResults = fuse.search(prompt);
|
||||
|
||||
// Also search for each significant word to catch different aspects
|
||||
let wordResults = [];
|
||||
for (const word of promptWords) {
|
||||
if (word.length > 5) {
|
||||
// Only use significant words
|
||||
const results = fuse.search(word);
|
||||
if (results.length > 0) {
|
||||
wordResults.push(...results);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Merge and deduplicate results
|
||||
const mergedResults = [...fuzzyResults];
|
||||
|
||||
// Add word results that aren't already in fuzzyResults
|
||||
for (const wordResult of wordResults) {
|
||||
if (!mergedResults.some((r) => r.item.id === wordResult.item.id)) {
|
||||
mergedResults.push(wordResult);
|
||||
}
|
||||
}
|
||||
|
||||
// Group search results by relevance
|
||||
const highRelevance = mergedResults
|
||||
.filter((result) => result.score < 0.25)
|
||||
.map((result) => result.item);
|
||||
|
||||
const mediumRelevance = mergedResults
|
||||
.filter((result) => result.score >= 0.25 && result.score < 0.4)
|
||||
.map((result) => result.item);
|
||||
|
||||
// Get recent tasks (newest first)
|
||||
const recentTasks = [...data.tasks]
|
||||
.sort((a, b) => b.id - a.id)
|
||||
.slice(0, 5);
|
||||
|
||||
// Combine high relevance, medium relevance, and recent tasks
|
||||
// Prioritize high relevance first
|
||||
const allRelevantTasks = [...highRelevance];
|
||||
|
||||
// Add medium relevance if not already included
|
||||
for (const task of mediumRelevance) {
|
||||
if (!allRelevantTasks.some((t) => t.id === task.id)) {
|
||||
allRelevantTasks.push(task);
|
||||
}
|
||||
}
|
||||
|
||||
// Add recent tasks if not already included
|
||||
for (const task of recentTasks) {
|
||||
if (!allRelevantTasks.some((t) => t.id === task.id)) {
|
||||
allRelevantTasks.push(task);
|
||||
}
|
||||
}
|
||||
|
||||
// Get top N results for context
|
||||
const relatedTasks = allRelevantTasks.slice(0, 8);
|
||||
|
||||
// Format basic task overviews
|
||||
if (relatedTasks.length > 0) {
|
||||
contextTasks = `\nRelevant tasks identified by semantic similarity:\n${relatedTasks
|
||||
.map((t, i) => {
|
||||
const relevanceMarker = i < highRelevance.length ? '⭐ ' : '';
|
||||
return `- ${relevanceMarker}Task ${t.id}: ${t.title} - ${t.description}`;
|
||||
})
|
||||
.join('\n')}`;
|
||||
}
|
||||
|
||||
if (
|
||||
recentTasks.length > 0 &&
|
||||
!contextTasks.includes('Recently created tasks')
|
||||
) {
|
||||
contextTasks += `\n\nRecently created tasks:\n${recentTasks
|
||||
.filter((t) => !relatedTasks.some((rt) => rt.id === t.id))
|
||||
.slice(0, 3)
|
||||
.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`)
|
||||
.join('\n')}`;
|
||||
}
|
||||
|
||||
// Add detailed information about the most relevant tasks
|
||||
const allDetailedTasks = [...relatedTasks.slice(0, 25)];
|
||||
uniqueDetailedTasks = Array.from(
|
||||
new Map(allDetailedTasks.map((t) => [t.id, t])).values()
|
||||
).slice(0, 20);
|
||||
|
||||
if (uniqueDetailedTasks.length > 0) {
|
||||
contextTasks += `\n\nDetailed information about relevant tasks:`;
|
||||
for (const task of uniqueDetailedTasks) {
|
||||
contextTasks += `\n\n------ Task ${task.id}: ${task.title} ------\n`;
|
||||
contextTasks += `Description: ${task.description}\n`;
|
||||
contextTasks += `Status: ${task.status || 'pending'}\n`;
|
||||
contextTasks += `Priority: ${task.priority || 'medium'}\n`;
|
||||
if (task.dependencies && task.dependencies.length > 0) {
|
||||
// Format dependency list with titles
|
||||
const depList = task.dependencies.map((depId) => {
|
||||
const depTask = data.tasks.find((t) => t.id === depId);
|
||||
return depTask
|
||||
? `Task ${depId} (${depTask.title})`
|
||||
: `Task ${depId}`;
|
||||
});
|
||||
contextTasks += `Dependencies: ${depList.join(', ')}\n`;
|
||||
}
|
||||
// Add implementation details but truncate if too long
|
||||
if (task.details) {
|
||||
const truncatedDetails =
|
||||
task.details.length > 400
|
||||
? task.details.substring(0, 400) + '... (truncated)'
|
||||
: task.details;
|
||||
contextTasks += `Implementation Details: ${truncatedDetails}\n`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Add a concise view of the task dependency structure
|
||||
contextTasks += '\n\nSummary of task dependencies in the project:';
|
||||
|
||||
// Get pending/in-progress tasks that might be most relevant based on fuzzy search
|
||||
// Prioritize tasks from our similarity search
|
||||
const relevantTaskIds = new Set(uniqueDetailedTasks.map((t) => t.id));
|
||||
const relevantPendingTasks = data.tasks
|
||||
.filter(
|
||||
(t) =>
|
||||
(t.status === 'pending' || t.status === 'in-progress') &&
|
||||
// Either in our relevant set OR has relevant words in title/description
|
||||
(relevantTaskIds.has(t.id) ||
|
||||
promptWords.some(
|
||||
(word) =>
|
||||
t.title.toLowerCase().includes(word) ||
|
||||
t.description.toLowerCase().includes(word)
|
||||
))
|
||||
)
|
||||
.slice(0, 10);
|
||||
|
||||
for (const task of relevantPendingTasks) {
|
||||
const depsStr =
|
||||
task.dependencies && task.dependencies.length > 0
|
||||
? task.dependencies.join(', ')
|
||||
: 'None';
|
||||
contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`;
|
||||
}
|
||||
|
||||
// Additional analysis of common patterns
|
||||
const similarPurposeTasks = data.tasks.filter((t) =>
|
||||
prompt.toLowerCase().includes(t.title.toLowerCase())
|
||||
);
|
||||
|
||||
let commonDeps = []; // Initialize commonDeps
|
||||
|
||||
if (similarPurposeTasks.length > 0) {
|
||||
contextTasks += `\n\nCommon patterns for similar tasks:`;
|
||||
|
||||
// Collect dependencies from similar purpose tasks
|
||||
const similarDeps = similarPurposeTasks
|
||||
.filter((t) => t.dependencies && t.dependencies.length > 0)
|
||||
.map((t) => t.dependencies)
|
||||
.flat();
|
||||
|
||||
// Count frequency of each dependency
|
||||
const depCounts = {};
|
||||
similarDeps.forEach((dep) => {
|
||||
depCounts[dep] = (depCounts[dep] || 0) + 1;
|
||||
});
|
||||
|
||||
// Get most common dependencies for similar tasks
|
||||
commonDeps = Object.entries(depCounts)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 10);
|
||||
|
||||
if (commonDeps.length > 0) {
|
||||
contextTasks += '\nMost common dependencies for similar tasks:';
|
||||
commonDeps.forEach(([depId, count]) => {
|
||||
const depTask = data.tasks.find((t) => t.id === parseInt(depId));
|
||||
if (depTask) {
|
||||
contextTasks += `\n- Task ${depId} (used by ${count} similar tasks): ${depTask.title}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Show fuzzy search analysis in CLI mode
|
||||
if (outputFormat === 'text') {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
` Context search across ${data.tasks.length} tasks using full prompt and ${promptWords.length} keywords`
|
||||
)
|
||||
);
|
||||
|
||||
if (highRelevance.length > 0) {
|
||||
console.log(
|
||||
chalk.gray(`\n High relevance matches (score < 0.25):`)
|
||||
);
|
||||
highRelevance.slice(0, 25).forEach((t) => {
|
||||
console.log(
|
||||
chalk.yellow(` • ⭐ Task ${t.id}: ${truncate(t.title, 50)}`)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
if (mediumRelevance.length > 0) {
|
||||
console.log(
|
||||
chalk.gray(`\n Medium relevance matches (score < 0.4):`)
|
||||
);
|
||||
mediumRelevance.slice(0, 10).forEach((t) => {
|
||||
console.log(
|
||||
chalk.green(` • Task ${t.id}: ${truncate(t.title, 50)}`)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
// Show dependency patterns
|
||||
if (commonDeps && commonDeps.length > 0) {
|
||||
console.log(
|
||||
chalk.gray(`\n Common dependency patterns for similar tasks:`)
|
||||
);
|
||||
commonDeps.slice(0, 3).forEach(([depId, count]) => {
|
||||
const depTask = data.tasks.find((t) => t.id === parseInt(depId));
|
||||
if (depTask) {
|
||||
console.log(
|
||||
chalk.blue(
|
||||
` • Task ${depId} (${count}x): ${truncate(depTask.title, 45)}`
|
||||
)
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Add information about which tasks will be provided in detail
|
||||
if (uniqueDetailedTasks.length > 0) {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
`\n Providing detailed context for ${uniqueDetailedTasks.length} most relevant tasks:`
|
||||
)
|
||||
);
|
||||
uniqueDetailedTasks.forEach((t) => {
|
||||
const isHighRelevance = highRelevance.some(
|
||||
(ht) => ht.id === t.id
|
||||
);
|
||||
const relevanceIndicator = isHighRelevance ? '⭐ ' : '';
|
||||
console.log(
|
||||
chalk.cyan(
|
||||
` • ${relevanceIndicator}Task ${t.id}: ${truncate(t.title, 40)}`
|
||||
)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
console.log(); // Add spacing
|
||||
}
|
||||
}
|
||||
|
||||
// DETERMINE THE ACTUAL COUNT OF DETAILED TASKS BEING USED FOR AI CONTEXT
|
||||
let actualDetailedTasksCount = 0;
|
||||
if (numericDependencies.length > 0) {
|
||||
// In explicit dependency mode, we used 'uniqueDetailedTasks' derived from 'dependentTasks'
|
||||
// Ensure 'uniqueDetailedTasks' from THAT scope is used or re-evaluate.
|
||||
// For simplicity, let's assume 'dependentTasks' reflects the detailed tasks.
|
||||
actualDetailedTasksCount = dependentTasks.length;
|
||||
} else {
|
||||
// In fuzzy search mode, 'uniqueDetailedTasks' from THIS scope is correct.
|
||||
actualDetailedTasksCount = uniqueDetailedTasks
|
||||
? uniqueDetailedTasks.length
|
||||
: 0;
|
||||
}
|
||||
|
||||
// Add a visual transition to show we're moving to AI generation - only for CLI
|
||||
if (outputFormat === 'text') {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.white.bold('AI Task Generation') +
|
||||
`\n\n${chalk.gray('Analyzing context and generating task details using AI...')}` +
|
||||
`\n${chalk.cyan('Context size: ')}${chalk.yellow(contextTasks.length.toLocaleString())} characters` +
|
||||
`\n${chalk.cyan('Dependency detection: ')}${chalk.yellow(numericDependencies.length > 0 ? 'Explicit dependencies' : 'Auto-discovery mode')}` +
|
||||
`\n${chalk.cyan('Detailed tasks: ')}${chalk.yellow(
|
||||
numericDependencies.length > 0
|
||||
? dependentTasks.length // Use length of tasks from explicit dependency path
|
||||
: uniqueDetailedTasks.length // Use length of tasks from fuzzy search path
|
||||
)}`,
|
||||
`\n\n${chalk.gray('Analyzing context and generating task details using AI...')}`,
|
||||
{
|
||||
padding: { top: 0, bottom: 1, left: 1, right: 1 },
|
||||
margin: { top: 1, bottom: 0 },
|
||||
@@ -825,7 +295,6 @@ async function addTask(
|
||||
}
|
||||
)
|
||||
);
|
||||
console.log(); // Add spacing
|
||||
}
|
||||
|
||||
// System Prompt - Enhanced for dependency awareness
|
||||
@@ -866,8 +335,7 @@ async function addTask(
|
||||
// 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}` : ''}
|
||||
${gatheredContext}
|
||||
|
||||
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.
|
||||
|
||||
@@ -975,7 +443,9 @@ async function addTask(
|
||||
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);
|
||||
return (
|
||||
!Number.isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId)
|
||||
);
|
||||
});
|
||||
|
||||
if (!allValidDeps) {
|
||||
@@ -985,7 +455,9 @@ async function addTask(
|
||||
);
|
||||
newTask.dependencies = taskData.dependencies.filter((depId) => {
|
||||
const numDepId = parseInt(depId, 10);
|
||||
return !isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId);
|
||||
return (
|
||||
!Number.isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId)
|
||||
);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1032,7 +504,6 @@ async function addTask(
|
||||
return 'red';
|
||||
case 'low':
|
||||
return 'gray';
|
||||
case 'medium':
|
||||
default:
|
||||
return 'yellow';
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user