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.
327 lines
11 KiB
Plaintext
327 lines
11 KiB
Plaintext
---
|
|
description: Guidelines for implementing task management operations
|
|
globs: scripts/modules/task-manager.js
|
|
alwaysApply: false
|
|
---
|
|
# Task Management Guidelines
|
|
|
|
## Tagged Task Lists System
|
|
|
|
Task Master now uses a **tagged task lists system** for multi-context task management:
|
|
|
|
- **Data Structure**: Tasks are organized into separate contexts (tags) within `tasks.json`
|
|
- **Legacy Format**: `{"tasks": [...]}`
|
|
- **Tagged Format**: `{"master": {"tasks": [...]}, "feature-branch": {"tasks": [...]}}`
|
|
- **Silent Migration**: Legacy format automatically converts to tagged format on first use
|
|
- **Tag Resolution**: Core functions receive legacy format for 100% backward compatibility
|
|
- **Default Tag**: "master" is used for all existing and new tasks unless otherwise specified
|
|
|
|
## Task Structure Standards
|
|
|
|
- **Core Task Properties**:
|
|
- ✅ DO: Include all required properties in each task object
|
|
- ✅ DO: Provide default values for optional properties
|
|
- ❌ DON'T: Add extra properties that aren't in the standard schema
|
|
|
|
```javascript
|
|
// ✅ DO: Follow this structure for task objects
|
|
const task = {
|
|
id: nextId,
|
|
title: "Task title",
|
|
description: "Brief task description",
|
|
status: "pending", // "pending", "in-progress", "done", etc.
|
|
dependencies: [], // Array of task IDs
|
|
priority: "medium", // "high", "medium", "low"
|
|
details: "Detailed implementation instructions",
|
|
testStrategy: "Verification approach",
|
|
subtasks: [] // Array of subtask objects
|
|
};
|
|
```
|
|
|
|
- **Tagged Data Structure**:
|
|
- ✅ DO: Access tasks through tag resolution layer
|
|
- ✅ DO: Use `getTasksForTag(data, tagName)` to retrieve tasks for a specific tag
|
|
- ✅ DO: Use `setTasksForTag(data, tagName, tasks)` to update tasks for a specific tag
|
|
- ❌ DON'T: Directly manipulate the tagged structure in core functions
|
|
|
|
```javascript
|
|
// ✅ DO: Use tag resolution functions
|
|
const tasksData = readJSON(tasksPath);
|
|
const currentTag = getCurrentTag() || 'master';
|
|
const tasks = getTasksForTag(tasksData, currentTag);
|
|
|
|
// Manipulate tasks as normal...
|
|
|
|
// Save back to the tagged structure
|
|
setTasksForTag(tasksData, currentTag, tasks);
|
|
writeJSON(tasksPath, tasksData);
|
|
```
|
|
|
|
- **Subtask Structure**:
|
|
- ✅ DO: Use consistent properties across subtasks
|
|
- ✅ DO: Maintain simple numeric IDs within parent tasks
|
|
- ❌ DON'T: Duplicate parent task properties in subtasks
|
|
|
|
```javascript
|
|
// ✅ DO: Structure subtasks consistently
|
|
const subtask = {
|
|
id: nextSubtaskId, // Simple numeric ID, unique within the parent task
|
|
title: "Subtask title",
|
|
description: "Brief subtask description",
|
|
status: "pending",
|
|
dependencies: [], // Can include numeric IDs (other subtasks) or full task IDs
|
|
details: "Detailed implementation instructions"
|
|
};
|
|
```
|
|
|
|
## Task Creation and Parsing
|
|
|
|
- **ID Management**:
|
|
- ✅ DO: Assign unique sequential IDs to tasks within each tag context
|
|
- ✅ DO: Calculate the next ID based on existing tasks in the current tag
|
|
- ❌ DON'T: Hardcode or reuse IDs within the same tag
|
|
|
|
```javascript
|
|
// ✅ DO: Calculate the next available ID within the current tag
|
|
const tasksData = readJSON(tasksPath);
|
|
const currentTag = getCurrentTag() || 'master';
|
|
const tasks = getTasksForTag(tasksData, currentTag);
|
|
const highestId = Math.max(...tasks.map(t => t.id));
|
|
const nextTaskId = highestId + 1;
|
|
```
|
|
|
|
- **PRD Parsing**:
|
|
- ✅ DO: Extract tasks from PRD documents using AI
|
|
- ✅ DO: Create tasks in the current tag context (defaults to "master")
|
|
- ✅ DO: Provide clear prompts to guide AI task generation
|
|
- ✅ DO: Validate and clean up AI-generated tasks
|
|
|
|
```javascript
|
|
// ✅ DO: Parse into current tag context
|
|
const tasksData = readJSON(tasksPath) || {};
|
|
const currentTag = getCurrentTag() || 'master';
|
|
|
|
// Parse tasks and add to current tag
|
|
const newTasks = await parseTasksFromPRD(prdContent);
|
|
setTasksForTag(tasksData, currentTag, newTasks);
|
|
writeJSON(tasksPath, tasksData);
|
|
```
|
|
|
|
## Task Updates and Modifications
|
|
|
|
- **Status Management**:
|
|
- ✅ DO: Provide functions for updating task status within current tag context
|
|
- ✅ DO: Handle both individual tasks and subtasks
|
|
- ✅ DO: Consider subtask status when updating parent tasks
|
|
|
|
```javascript
|
|
// ✅ DO: Handle status updates within tagged context
|
|
async function setTaskStatus(tasksPath, taskIdInput, newStatus) {
|
|
const tasksData = readJSON(tasksPath);
|
|
const currentTag = getCurrentTag() || 'master';
|
|
const tasks = getTasksForTag(tasksData, currentTag);
|
|
|
|
// Check if it's a subtask (e.g., "1.2")
|
|
if (taskIdInput.includes('.')) {
|
|
const [parentId, subtaskId] = taskIdInput.split('.').map(id => parseInt(id, 10));
|
|
|
|
// Find the parent task and subtask
|
|
const parentTask = tasks.find(t => t.id === parentId);
|
|
const subtask = parentTask.subtasks.find(st => st.id === subtaskId);
|
|
|
|
// Update subtask status
|
|
subtask.status = newStatus;
|
|
|
|
// Check if all subtasks are done
|
|
if (newStatus === 'done') {
|
|
const allSubtasksDone = parentTask.subtasks.every(st => st.status === 'done');
|
|
if (allSubtasksDone) {
|
|
// Suggest updating parent task
|
|
}
|
|
}
|
|
} else {
|
|
// Handle regular task
|
|
const task = tasks.find(t => t.id === parseInt(taskIdInput, 10));
|
|
task.status = newStatus;
|
|
|
|
// If marking as done, also mark subtasks
|
|
if (newStatus === 'done' && task.subtasks && task.subtasks.length > 0) {
|
|
task.subtasks.forEach(subtask => {
|
|
subtask.status = newStatus;
|
|
});
|
|
}
|
|
}
|
|
|
|
// Save updated tasks back to tagged structure
|
|
setTasksForTag(tasksData, currentTag, tasks);
|
|
writeJSON(tasksPath, tasksData);
|
|
}
|
|
```
|
|
|
|
- **Task Expansion**:
|
|
- ✅ DO: Use AI to generate detailed subtasks within current tag context
|
|
- ✅ DO: Consider complexity analysis for subtask counts
|
|
- ✅ DO: Ensure proper IDs for newly created subtasks
|
|
|
|
```javascript
|
|
// ✅ DO: Generate appropriate subtasks based on complexity
|
|
const tasksData = readJSON(tasksPath);
|
|
const currentTag = getCurrentTag() || 'master';
|
|
const tasks = getTasksForTag(tasksData, currentTag);
|
|
|
|
if (taskAnalysis) {
|
|
log('info', `Found complexity analysis for task ${taskId}: Score ${taskAnalysis.complexityScore}/10`);
|
|
|
|
// Use recommended number of subtasks if available
|
|
if (taskAnalysis.recommendedSubtasks && numSubtasks === CONFIG.defaultSubtasks) {
|
|
numSubtasks = taskAnalysis.recommendedSubtasks;
|
|
log('info', `Using recommended number of subtasks: ${numSubtasks}`);
|
|
}
|
|
}
|
|
|
|
// Generate subtasks and save back
|
|
// ... subtask generation logic ...
|
|
setTasksForTag(tasksData, currentTag, tasks);
|
|
writeJSON(tasksPath, tasksData);
|
|
```
|
|
|
|
## Task File Generation
|
|
|
|
- **File Formatting**:
|
|
- ✅ DO: Use consistent formatting for task files
|
|
- ✅ DO: Include all task properties in text files
|
|
- ✅ DO: Format dependencies with status indicators
|
|
|
|
```javascript
|
|
// ✅ DO: Use consistent file formatting
|
|
let content = `# Task ID: ${task.id}\n`;
|
|
content += `# Title: ${task.title}\n`;
|
|
content += `# Status: ${task.status || 'pending'}\n`;
|
|
|
|
// Format dependencies with their status
|
|
if (task.dependencies && task.dependencies.length > 0) {
|
|
content += `# Dependencies: ${formatDependenciesWithStatus(task.dependencies, tasks)}\n`;
|
|
} else {
|
|
content += '# Dependencies: None\n';
|
|
}
|
|
```
|
|
|
|
- **Tagged Context Awareness**:
|
|
- ✅ DO: Generate task files from current tag context
|
|
- ✅ DO: Include tag information in generated files
|
|
- ❌ DON'T: Mix tasks from different tags in file generation
|
|
|
|
```javascript
|
|
// ✅ DO: Generate files for current tag context
|
|
async function generateTaskFiles(tasksPath, outputDir) {
|
|
const tasksData = readJSON(tasksPath);
|
|
const currentTag = getCurrentTag() || 'master';
|
|
const tasks = getTasksForTag(tasksData, currentTag);
|
|
|
|
// Add tag context to file header
|
|
let content = `# Tag Context: ${currentTag}\n`;
|
|
content += `# Task ID: ${task.id}\n`;
|
|
// ... rest of file generation
|
|
}
|
|
```
|
|
|
|
## Task Listing and Display
|
|
|
|
- **Filtering and Organization**:
|
|
- ✅ DO: Allow filtering tasks by status within current tag context
|
|
- ✅ DO: Handle subtask display in lists
|
|
- ✅ DO: Use consistent table formats
|
|
|
|
```javascript
|
|
// ✅ DO: Implement clear filtering within tag context
|
|
const tasksData = readJSON(tasksPath);
|
|
const currentTag = getCurrentTag() || 'master';
|
|
const tasks = getTasksForTag(tasksData, currentTag);
|
|
|
|
// Filter tasks by status if specified
|
|
const filteredTasks = statusFilter
|
|
? tasks.filter(task =>
|
|
task.status && task.status.toLowerCase() === statusFilter.toLowerCase())
|
|
: tasks;
|
|
```
|
|
|
|
- **Progress Tracking**:
|
|
- ✅ DO: Calculate and display completion statistics for current tag
|
|
- ✅ DO: Track both task and subtask completion
|
|
- ✅ DO: Use visual progress indicators
|
|
|
|
```javascript
|
|
// ✅ DO: Track and display progress within tag context
|
|
const tasksData = readJSON(tasksPath);
|
|
const currentTag = getCurrentTag() || 'master';
|
|
const tasks = getTasksForTag(tasksData, currentTag);
|
|
|
|
// Calculate completion statistics
|
|
const totalTasks = tasks.length;
|
|
const completedTasks = tasks.filter(task =>
|
|
task.status === 'done' || task.status === 'completed').length;
|
|
const completionPercentage = totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0;
|
|
|
|
// Count subtasks
|
|
let totalSubtasks = 0;
|
|
let completedSubtasks = 0;
|
|
|
|
tasks.forEach(task => {
|
|
if (task.subtasks && task.subtasks.length > 0) {
|
|
totalSubtasks += task.subtasks.length;
|
|
completedSubtasks += task.subtasks.filter(st =>
|
|
st.status === 'done' || st.status === 'completed').length;
|
|
}
|
|
});
|
|
```
|
|
|
|
## Migration and Compatibility
|
|
|
|
- **Silent Migration Handling**:
|
|
- ✅ DO: Implement silent migration in `readJSON()` function
|
|
- ✅ DO: Detect legacy format and convert automatically
|
|
- ✅ DO: Preserve all existing task data during migration
|
|
|
|
```javascript
|
|
// ✅ DO: Handle silent migration (implemented in utils.js)
|
|
function readJSON(filepath) {
|
|
let data = JSON.parse(fs.readFileSync(filepath, 'utf8'));
|
|
|
|
// Silent migration for tasks.json files
|
|
if (data.tasks && Array.isArray(data.tasks) && !data.master && isTasksFile) {
|
|
const migratedData = {
|
|
master: {
|
|
tasks: data.tasks
|
|
}
|
|
};
|
|
writeJSON(filepath, migratedData);
|
|
data = migratedData;
|
|
}
|
|
|
|
return data;
|
|
}
|
|
```
|
|
|
|
- **Tag Resolution**:
|
|
- ✅ DO: Use tag resolution functions to maintain backward compatibility
|
|
- ✅ DO: Return legacy format to core functions
|
|
- ❌ DON'T: Expose tagged structure to existing core logic
|
|
|
|
```javascript
|
|
// ✅ DO: Use tag resolution layer
|
|
function getTasksForTag(data, tagName) {
|
|
if (data.tasks && Array.isArray(data.tasks)) {
|
|
// Legacy format - return as-is
|
|
return data.tasks;
|
|
}
|
|
|
|
if (data[tagName] && data[tagName].tasks) {
|
|
// Tagged format - return tasks for specified tag
|
|
return data[tagName].tasks;
|
|
}
|
|
|
|
return [];
|
|
}
|
|
```
|
|
|
|
Refer to [`task-manager.js`](mdc:scripts/modules/task-manager.js) for implementation examples and [`new_features.mdc`](mdc:.cursor/rules/new_features.mdc) for integration guidelines. |