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:
@@ -15,6 +15,9 @@ import { generateTextService } from '../ai-services-unified.js';
|
||||
import { getDefaultSubtasks, getDebugFlag } from '../config-manager.js';
|
||||
import generateTaskFiles from './generate-task-files.js';
|
||||
import { COMPLEXITY_REPORT_FILE } from '../../../src/constants/paths.js';
|
||||
import { ContextGatherer } from '../utils/contextGatherer.js';
|
||||
import { FuzzyTaskSearch } from '../utils/fuzzyTaskSearch.js';
|
||||
import { flattenTasksWithSubtasks, findProjectRoot } from '../utils.js';
|
||||
|
||||
// --- Zod Schemas (Keep from previous step) ---
|
||||
const subtaskSchema = z
|
||||
@@ -285,9 +288,9 @@ function parseSubtasksFromText(
|
||||
const patternStartIndex = jsonToParse.indexOf(targetPattern);
|
||||
|
||||
if (patternStartIndex !== -1) {
|
||||
let openBraces = 0;
|
||||
let firstBraceFound = false;
|
||||
let extractedJsonBlock = '';
|
||||
const openBraces = 0;
|
||||
const firstBraceFound = false;
|
||||
const extractedJsonBlock = '';
|
||||
// ... (loop for brace counting as before) ...
|
||||
// ... (if successful, jsonToParse = extractedJsonBlock) ...
|
||||
// ... (if that fails, fallbacks as before) ...
|
||||
@@ -349,7 +352,8 @@ function parseSubtasksFromText(
|
||||
? rawSubtask.dependencies
|
||||
.map((dep) => (typeof dep === 'string' ? parseInt(dep, 10) : dep))
|
||||
.filter(
|
||||
(depId) => !isNaN(depId) && depId >= startId && depId < currentId
|
||||
(depId) =>
|
||||
!Number.isNaN(depId) && depId >= startId && depId < currentId
|
||||
)
|
||||
: [],
|
||||
status: 'pending'
|
||||
@@ -418,7 +422,9 @@ async function expandTask(
|
||||
|
||||
// Determine projectRoot: Use from context if available, otherwise derive from tasksPath
|
||||
const projectRoot =
|
||||
contextProjectRoot || path.dirname(path.dirname(tasksPath));
|
||||
contextProjectRoot ||
|
||||
findProjectRoot() ||
|
||||
path.dirname(path.dirname(tasksPath));
|
||||
|
||||
// Use mcpLog if available, otherwise use the default console log wrapper
|
||||
const logger = mcpLog || {
|
||||
@@ -436,7 +442,7 @@ async function expandTask(
|
||||
try {
|
||||
// --- Task Loading/Filtering (Unchanged) ---
|
||||
logger.info(`Reading tasks from ${tasksPath}`);
|
||||
const data = readJSON(tasksPath);
|
||||
const data = readJSON(tasksPath, projectRoot);
|
||||
if (!data || !data.tasks)
|
||||
throw new Error(`Invalid tasks data in ${tasksPath}`);
|
||||
const taskIndex = data.tasks.findIndex(
|
||||
@@ -458,6 +464,35 @@ async function expandTask(
|
||||
}
|
||||
// --- End Force Flag Handling ---
|
||||
|
||||
// --- Context Gathering ---
|
||||
let gatheredContext = '';
|
||||
try {
|
||||
const contextGatherer = new ContextGatherer(projectRoot);
|
||||
const allTasksFlat = flattenTasksWithSubtasks(data.tasks);
|
||||
const fuzzySearch = new FuzzyTaskSearch(allTasksFlat, 'expand-task');
|
||||
const searchQuery = `${task.title} ${task.description}`;
|
||||
const searchResults = fuzzySearch.findRelevantTasks(searchQuery, {
|
||||
maxResults: 5,
|
||||
includeSelf: true
|
||||
});
|
||||
const relevantTaskIds = fuzzySearch.getTaskIds(searchResults);
|
||||
|
||||
const finalTaskIds = [
|
||||
...new Set([taskId.toString(), ...relevantTaskIds])
|
||||
];
|
||||
|
||||
if (finalTaskIds.length > 0) {
|
||||
const contextResult = await contextGatherer.gather({
|
||||
tasks: finalTaskIds,
|
||||
format: 'research'
|
||||
});
|
||||
gatheredContext = contextResult;
|
||||
}
|
||||
} catch (contextError) {
|
||||
logger.warn(`Could not gather context: ${contextError.message}`);
|
||||
}
|
||||
// --- End Context Gathering ---
|
||||
|
||||
// --- Complexity Report Integration ---
|
||||
let finalSubtaskCount;
|
||||
let promptContent = '';
|
||||
@@ -498,7 +533,7 @@ async function expandTask(
|
||||
|
||||
// Determine final subtask count
|
||||
const explicitNumSubtasks = parseInt(numSubtasks, 10);
|
||||
if (!isNaN(explicitNumSubtasks) && explicitNumSubtasks > 0) {
|
||||
if (!Number.isNaN(explicitNumSubtasks) && explicitNumSubtasks > 0) {
|
||||
finalSubtaskCount = explicitNumSubtasks;
|
||||
logger.info(
|
||||
`Using explicitly provided subtask count: ${finalSubtaskCount}`
|
||||
@@ -512,7 +547,7 @@ async function expandTask(
|
||||
finalSubtaskCount = getDefaultSubtasks(session);
|
||||
logger.info(`Using default number of subtasks: ${finalSubtaskCount}`);
|
||||
}
|
||||
if (isNaN(finalSubtaskCount) || finalSubtaskCount <= 0) {
|
||||
if (Number.isNaN(finalSubtaskCount) || finalSubtaskCount <= 0) {
|
||||
logger.warn(
|
||||
`Invalid subtask count determined (${finalSubtaskCount}), defaulting to 3.`
|
||||
);
|
||||
@@ -528,6 +563,9 @@ async function expandTask(
|
||||
// Append additional context and reasoning
|
||||
promptContent += `\n\n${additionalContext}`.trim();
|
||||
promptContent += `${complexityReasoningContext}`.trim();
|
||||
if (gatheredContext) {
|
||||
promptContent += `\n\n# Project Context\n\n${gatheredContext}`;
|
||||
}
|
||||
|
||||
// --- Use Simplified System Prompt for Report Prompts ---
|
||||
systemPrompt = `You are an AI assistant helping with task breakdown. Generate exactly ${finalSubtaskCount} subtasks based on the provided prompt and context. Respond ONLY with a valid JSON object containing a single key "subtasks" whose value is an array of the generated subtask objects. Each subtask object in the array must have keys: "id", "title", "description", "dependencies", "details", "status". Ensure the 'id' starts from ${nextSubtaskId} and is sequential. Ensure 'dependencies' only reference valid prior subtask IDs generated in this response (starting from ${nextSubtaskId}). Ensure 'status' is 'pending'. Do not include any other text or explanation.`;
|
||||
@@ -537,8 +575,13 @@ async function expandTask(
|
||||
// --- End Simplified System Prompt ---
|
||||
} else {
|
||||
// Use standard prompt generation
|
||||
const combinedAdditionalContext =
|
||||
let combinedAdditionalContext =
|
||||
`${additionalContext}${complexityReasoningContext}`.trim();
|
||||
if (gatheredContext) {
|
||||
combinedAdditionalContext =
|
||||
`${combinedAdditionalContext}\n\n# Project Context\n\n${gatheredContext}`.trim();
|
||||
}
|
||||
|
||||
if (useResearch) {
|
||||
promptContent = generateResearchUserPrompt(
|
||||
task,
|
||||
|
||||
Reference in New Issue
Block a user