Files
claude-task-master/assets/rules/dependencies.mdc
2025-05-09 01:26:33 -04:00

224 lines
7.5 KiB
Plaintext

---
description: Guidelines for managing task dependencies and relationships
globs: scripts/modules/dependency-manager.js
alwaysApply: false
---
# Dependency Management Guidelines
## Dependency Structure Principles
- **Dependency References**:
- ✅ DO: Represent task dependencies as arrays of task IDs
- ✅ DO: Use numeric IDs for direct task references
- ✅ DO: Use string IDs with dot notation (e.g., "1.2") for subtask references
- ❌ DON'T: Mix reference types without proper conversion
```javascript
// ✅ DO: Use consistent dependency formats
// For main tasks
task.dependencies = [1, 2, 3]; // Dependencies on other main tasks
// For subtasks
subtask.dependencies = [1, "3.2"]; // Dependency on main task 1 and subtask 2 of task 3
```
- **Subtask Dependencies**:
- ✅ DO: Allow numeric subtask IDs to reference other subtasks within the same parent
- ✅ DO: Convert between formats appropriately when needed
- ❌ DON'T: Create circular dependencies between subtasks
```javascript
// ✅ DO: Properly normalize subtask dependencies
// When a subtask refers to another subtask in the same parent
if (typeof depId === 'number' && depId < 100) {
// It's likely a reference to another subtask in the same parent task
const fullSubtaskId = `${parentId}.${depId}`;
// Now use fullSubtaskId for validation
}
```
## Dependency Validation
- **Existence Checking**:
- ✅ DO: Validate that referenced tasks exist before adding dependencies
- ✅ DO: Provide clear error messages for non-existent dependencies
- ✅ DO: Remove references to non-existent tasks during validation
```javascript
// ✅ DO: Check if the dependency exists before adding
if (!taskExists(data.tasks, formattedDependencyId)) {
log('error', `Dependency target ${formattedDependencyId} does not exist in tasks.json`);
process.exit(1);
}
```
- **Circular Dependency Prevention**:
- ✅ DO: Check for circular dependencies before adding new relationships
- ✅ DO: Use graph traversal algorithms (DFS) to detect cycles
- ✅ DO: Provide clear error messages explaining the circular chain
```javascript
// ✅ DO: Check for circular dependencies before adding
const dependencyChain = [formattedTaskId];
if (isCircularDependency(data.tasks, formattedDependencyId, dependencyChain)) {
log('error', `Cannot add dependency ${formattedDependencyId} to task ${formattedTaskId} as it would create a circular dependency.`);
process.exit(1);
}
```
- **Self-Dependency Prevention**:
- ✅ DO: Prevent tasks from depending on themselves
- ✅ DO: Handle both direct and indirect self-dependencies
```javascript
// ✅ DO: Prevent self-dependencies
if (String(formattedTaskId) === String(formattedDependencyId)) {
log('error', `Task ${formattedTaskId} cannot depend on itself.`);
process.exit(1);
}
```
## Dependency Modification
- **Adding Dependencies**:
- ✅ DO: Format task and dependency IDs consistently
- ✅ DO: Check for existing dependencies to prevent duplicates
- ✅ DO: Sort dependencies for better readability
```javascript
// ✅ DO: Format IDs consistently when adding dependencies
const formattedTaskId = typeof taskId === 'string' && taskId.includes('.')
? taskId : parseInt(taskId, 10);
const formattedDependencyId = formatTaskId(dependencyId);
```
- **Removing Dependencies**:
- ✅ DO: Check if the dependency exists before removing
- ✅ DO: Handle different ID formats consistently
- ✅ DO: Provide feedback about the removal result
```javascript
// ✅ DO: Properly handle dependency removal
const dependencyIndex = targetTask.dependencies.findIndex(dep => {
// Convert both to strings for comparison
let depStr = String(dep);
// Handle relative subtask references
if (typeof dep === 'number' && dep < 100 && isSubtask) {
const [parentId] = formattedTaskId.split('.');
depStr = `${parentId}.${dep}`;
}
return depStr === normalizedDependencyId;
});
if (dependencyIndex === -1) {
log('info', `Task ${formattedTaskId} does not depend on ${formattedDependencyId}, no changes made.`);
return;
}
// Remove the dependency
targetTask.dependencies.splice(dependencyIndex, 1);
```
## Dependency Cleanup
- **Duplicate Removal**:
- ✅ DO: Use Set objects to identify and remove duplicates
- ✅ DO: Handle both numeric and string ID formats
```javascript
// ✅ DO: Remove duplicate dependencies
const uniqueDeps = new Set();
const uniqueDependencies = task.dependencies.filter(depId => {
// Convert to string for comparison to handle both numeric and string IDs
const depIdStr = String(depId);
if (uniqueDeps.has(depIdStr)) {
log('warn', `Removing duplicate dependency from task ${task.id}: ${depId}`);
return false;
}
uniqueDeps.add(depIdStr);
return true;
});
```
- **Invalid Reference Cleanup**:
- ✅ DO: Check for and remove references to non-existent tasks
- ✅ DO: Check for and remove self-references
- ✅ DO: Track and report changes made during cleanup
```javascript
// ✅ DO: Filter invalid task dependencies
task.dependencies = task.dependencies.filter(depId => {
const numericId = typeof depId === 'string' ? parseInt(depId, 10) : depId;
if (!validTaskIds.has(numericId)) {
log('warn', `Removing invalid task dependency from task ${task.id}: ${depId} (task does not exist)`);
return false;
}
return true;
});
```
## Dependency Visualization
- **Status Indicators**:
- ✅ DO: Use visual indicators to show dependency status (✅/⏱️)
- ✅ DO: Format dependency lists consistently
```javascript
// ✅ DO: Format dependencies with status indicators
function formatDependenciesWithStatus(dependencies, allTasks) {
if (!dependencies || dependencies.length === 0) {
return 'None';
}
return dependencies.map(depId => {
const depTask = findTaskById(allTasks, depId);
if (!depTask) return `${depId} (Not found)`;
const isDone = depTask.status === 'done' || depTask.status === 'completed';
const statusIcon = isDone ? '✅' : '⏱️';
return `${statusIcon} ${depId} (${depTask.status})`;
}).join(', ');
}
```
## Cycle Detection
- **Graph Traversal**:
- ✅ DO: Use depth-first search (DFS) for cycle detection
- ✅ DO: Track visited nodes and recursion stack
- ✅ DO: Support both task and subtask dependencies
```javascript
// ✅ DO: Use proper cycle detection algorithms
function findCycles(subtaskId, dependencyMap, visited = new Set(), recursionStack = new Set()) {
// Mark the current node as visited and part of recursion stack
visited.add(subtaskId);
recursionStack.add(subtaskId);
const cyclesToBreak = [];
const dependencies = dependencyMap.get(subtaskId) || [];
for (const depId of dependencies) {
if (!visited.has(depId)) {
const cycles = findCycles(depId, dependencyMap, visited, recursionStack);
cyclesToBreak.push(...cycles);
}
else if (recursionStack.has(depId)) {
// Found a cycle, add the edge to break
cyclesToBreak.push(depId);
}
}
// Remove the node from recursion stack before returning
recursionStack.delete(subtaskId);
return cyclesToBreak;
}
```
Refer to [`dependency-manager.js`](mdc:scripts/modules/dependency-manager.js) for implementation examples and [`new_features.mdc`](mdc:.cursor/rules/new_features.mdc) for integration guidelines.