Files
claude-task-master/.cursor/rules/utilities.mdc
2025-03-30 02:38:51 -04:00

382 lines
14 KiB
Plaintext
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
description: Guidelines for implementing utility functions
globs: scripts/modules/utils.js, mcp-server/src/**/*
alwaysApply: false
---
# Utility Function Guidelines
## General Principles
- **Function Scope**:
- ✅ DO: Create utility functions that serve multiple modules
- ✅ DO: Keep functions single-purpose and focused
- ❌ DON'T: Include business logic in utility functions
- ❌ DON'T: Create utilities with side effects
```javascript
// ✅ DO: Create focused, reusable utilities
/**
* Truncates text to a specified length
* @param {string} text - The text to truncate
* @param {number} maxLength - The maximum length
* @returns {string} The truncated text
*/
function truncate(text, maxLength) {
if (!text || text.length <= maxLength) {
return text;
}
return text.slice(0, maxLength - 3) + '...';
}
```
```javascript
// ❌ DON'T: Add side effects to utilities
function truncate(text, maxLength) {
if (!text || text.length <= maxLength) {
return text;
}
// Side effect - modifying global state or logging
console.log(`Truncating text from ${text.length} to ${maxLength} chars`);
return text.slice(0, maxLength - 3) + '...';
}
```
## Documentation Standards
- **JSDoc Format**:
- ✅ DO: Document all parameters and return values
- ✅ DO: Include descriptions for complex logic
- ✅ DO: Add examples for non-obvious usage
- ❌ DON'T: Skip documentation for "simple" functions
```javascript
// ✅ DO: Provide complete JSDoc documentation
/**
* Reads and parses a JSON file
* @param {string} filepath - Path to the JSON file
* @returns {Object|null} Parsed JSON data or null if error occurs
*/
function readJSON(filepath) {
try {
const rawData = fs.readFileSync(filepath, 'utf8');
return JSON.parse(rawData);
} catch (error) {
log('error', `Error reading JSON file ${filepath}:`, error.message);
if (CONFIG.debug) {
console.error(error);
}
return null;
}
}
```
## Configuration Management
- **Environment Variables**:
- ✅ DO: Provide default values for all configuration
- ✅ DO: Use environment variables for customization
- ✅ DO: Document available configuration options
- ❌ DON'T: Hardcode values that should be configurable
```javascript
// ✅ DO: Set up configuration with defaults and environment overrides
const CONFIG = {
model: process.env.MODEL || 'claude-3-7-sonnet-20250219',
maxTokens: parseInt(process.env.MAX_TOKENS || '4000'),
temperature: parseFloat(process.env.TEMPERATURE || '0.7'),
debug: process.env.DEBUG === "true",
logLevel: process.env.LOG_LEVEL || "info",
defaultSubtasks: parseInt(process.env.DEFAULT_SUBTASKS || "3"),
defaultPriority: process.env.DEFAULT_PRIORITY || "medium",
projectName: process.env.PROJECT_NAME || "Task Master",
projectVersion: "1.5.0" // Version should be hardcoded
};
```
## Logging Utilities
- **Log Levels**:
- ✅ DO: Support multiple log levels (debug, info, warn, error)
- ✅ DO: Use appropriate icons for different log levels
- ✅ DO: Respect the configured log level
- ❌ DON'T: Add direct console.log calls outside the logging utility
```javascript
// ✅ DO: Implement a proper logging utility
const LOG_LEVELS = {
debug: 0,
info: 1,
warn: 2,
error: 3
};
function log(level, ...args) {
const icons = {
debug: chalk.gray('🔍'),
info: chalk.blue(''),
warn: chalk.yellow('⚠️'),
error: chalk.red('❌'),
success: chalk.green('✅')
};
if (LOG_LEVELS[level] >= LOG_LEVELS[CONFIG.logLevel]) {
const icon = icons[level] || '';
console.log(`${icon} ${args.join(' ')}`);
}
}
```
## File Operations
- **Error Handling**:
- ✅ DO: Use try/catch blocks for all file operations
- ✅ DO: Return null or a default value on failure
- ✅ DO: Log detailed error information
- ❌ DON'T: Allow exceptions to propagate unhandled
```javascript
// ✅ DO: Handle file operation errors properly
function writeJSON(filepath, data) {
try {
fs.writeFileSync(filepath, JSON.stringify(data, null, 2));
} catch (error) {
log('error', `Error writing JSON file ${filepath}:`, error.message);
if (CONFIG.debug) {
console.error(error);
}
}
}
```
## Task-Specific Utilities
- **Task ID Formatting**:
- ✅ DO: Create utilities for consistent ID handling
- ✅ DO: Support different ID formats (numeric, string, dot notation)
- ❌ DON'T: Duplicate formatting logic across modules
```javascript
// ✅ DO: Create utilities for common operations
/**
* Formats a task ID as a string
* @param {string|number} id - The task ID to format
* @returns {string} The formatted task ID
*/
function formatTaskId(id) {
if (typeof id === 'string' && id.includes('.')) {
return id; // Already formatted as a string with a dot (e.g., "1.2")
}
if (typeof id === 'number') {
return id.toString();
}
return id;
}
```
- **Task Search**:
- ✅ DO: Implement reusable task finding utilities
- ✅ DO: Support both task and subtask lookups
- ✅ DO: Add context to subtask results
```javascript
// ✅ DO: Create comprehensive search utilities
/**
* Finds a task by ID in the tasks array
* @param {Array} tasks - The tasks array
* @param {string|number} taskId - The task ID to find
* @returns {Object|null} The task object or null if not found
*/
function findTaskById(tasks, taskId) {
if (!taskId || !tasks || !Array.isArray(tasks)) {
return null;
}
// Check if it's a subtask ID (e.g., "1.2")
if (typeof taskId === 'string' && taskId.includes('.')) {
const [parentId, subtaskId] = taskId.split('.').map(id => parseInt(id, 10));
const parentTask = tasks.find(t => t.id === parentId);
if (!parentTask || !parentTask.subtasks) {
return null;
}
const subtask = parentTask.subtasks.find(st => st.id === subtaskId);
if (subtask) {
// Add reference to parent task for context
subtask.parentTask = {
id: parentTask.id,
title: parentTask.title,
status: parentTask.status
};
subtask.isSubtask = true;
}
return subtask || null;
}
const id = parseInt(taskId, 10);
return tasks.find(t => t.id === id) || null;
}
```
## Cycle Detection
- **Graph Algorithms**:
- ✅ DO: Implement cycle detection using graph traversal
- ✅ DO: Track visited nodes and recursion stack
- ✅ DO: Return specific information about cycles
```javascript
// ✅ DO: Implement proper cycle detection
/**
* Find cycles in a dependency graph using DFS
* @param {string} subtaskId - Current subtask ID
* @param {Map} dependencyMap - Map of subtask IDs to their dependencies
* @param {Set} visited - Set of visited nodes
* @param {Set} recursionStack - Set of nodes in current recursion stack
* @returns {Array} - List of dependency edges that need to be removed to break cycles
*/
function findCycles(subtaskId, dependencyMap, visited = new Set(), recursionStack = new Set(), path = []) {
// Mark the current node as visited and part of recursion stack
visited.add(subtaskId);
recursionStack.add(subtaskId);
path.push(subtaskId);
const cyclesToBreak = [];
// Get all dependencies of the current subtask
const dependencies = dependencyMap.get(subtaskId) || [];
// For each dependency
for (const depId of dependencies) {
// If not visited, recursively check for cycles
if (!visited.has(depId)) {
const cycles = findCycles(depId, dependencyMap, visited, recursionStack, [...path]);
cyclesToBreak.push(...cycles);
}
// If the dependency is in the recursion stack, we found a cycle
else if (recursionStack.has(depId)) {
// The last edge in the cycle is what we want to remove
cyclesToBreak.push(depId);
}
}
// Remove the node from recursion stack before returning
recursionStack.delete(subtaskId);
return cyclesToBreak;
}
```
## MCP Server Utilities (`mcp-server/src/tools/utils.js`)
- **Purpose**: These utilities specifically support the MCP server tools, handling communication patterns and data formatting for MCP clients. Refer to [`mcp.mdc`](mdc:.cursor/rules/mcp.mdc) for usage patterns.
-(See also: [`tests.mdc`](mdc:.cursor/rules/tests.mdc) for testing these utilities)
- **`getProjectRoot(projectRootRaw, log)`**:
- Normalizes a potentially relative project root path into an absolute path.
- Defaults to `process.cwd()` if `projectRootRaw` is not provided.
- Primarily used *internally* by `executeMCPToolAction` and `executeTaskMasterCommand`. Tools usually don't need to call this directly.
- **`executeMCPToolAction({ actionFn, args, log, actionName, processResult })`**:
- ✅ **DO**: Use this as the main wrapper inside an MCP tool's `execute` method when calling a direct function wrapper.
- Handles standard workflow: logs action start, normalizes `projectRoot`, calls the `actionFn` (e.g., `listTasksDirect`), processes the result (using `handleApiResult`), logs success/error, and returns a formatted MCP response (`createContentResponse`/`createErrorResponse`).
- Simplifies tool implementation significantly by handling boilerplate.
- Accepts an optional `processResult` function to customize data filtering/transformation before sending the response (defaults to `processMCPResponseData`).
- **`handleApiResult(result, log, errorPrefix, processFunction)`**:
- Takes the standard `{ success, data/error }` object returned by direct function wrappers (like `listTasksDirect`).
- Checks the `success` flag.
- If successful, processes the `data` using `processFunction` (defaults to `processMCPResponseData`).
- Returns a formatted MCP response object using `createContentResponse` or `createErrorResponse`.
- Typically called *internally* by `executeMCPToolAction`.
- **`executeTaskMasterCommand(command, log, args, projectRootRaw)`**:
- Executes a Task Master command using `child_process.spawnSync`.
- Tries the global `task-master` command first, then falls back to `node scripts/dev.js`.
- Handles project root normalization internally.
- Returns `{ success, stdout, stderr }` or `{ success: false, error }`.
- ❌ **DON'T**: Use this as the primary method for MCP tools. Prefer `executeMCPToolAction` with direct function calls. Use only as a fallback for commands not yet refactored or those requiring CLI execution.
- **`processMCPResponseData(taskOrData, fieldsToRemove = ['details', 'testStrategy'])`**:
- Filters task data before sending it to the MCP client.
- By default, removes the `details` and `testStrategy` fields from task objects and their subtasks to reduce payload size.
- Can handle single task objects or data structures containing a `tasks` array (like from `listTasks`).
- This is the default processor used by `executeMCPToolAction`.
```javascript
// Example usage (typically done inside executeMCPToolAction):
const rawResult = { success: true, data: { tasks: [ { id: 1, title: '...', details: '...', subtasks: [...] } ] } };
const filteredData = processMCPResponseData(rawResult.data);
// filteredData.tasks[0] will NOT have the 'details' field.
```
- **`createContentResponse(content)`**:
- ✅ **DO**: Use this (usually via `handleApiResult` or `executeMCPToolAction`) to format successful MCP responses.
- Wraps the `content` (stringifies objects to JSON) in the standard FastMCP `{ content: [{ type: "text", text: ... }] }` structure.
- **`createErrorResponse(errorMessage)`**:
- ✅ **DO**: Use this (usually via `handleApiResult` or `executeMCPToolAction`) to format error responses for MCP.
- Wraps the `errorMessage` in the standard FastMCP error structure, including `isError: true`.
- **`getCachedOrExecute({ cacheKey, actionFn, log })`**:
- ✅ **DO**: Use this utility *inside direct function wrappers* (like `listTasksDirect` in `task-master-core.js`) to implement caching for MCP operations.
- Checks the `ContextManager` cache using `cacheKey`.
- If a hit occurs, returns the cached result directly.
- If a miss occurs, it executes the provided `actionFn` (which should be an async function returning `{ success, data/error }`).
- If `actionFn` succeeds, its result is stored in the cache under `cacheKey`.
- Returns the result (either cached or fresh) wrapped in the standard structure `{ success, data/error, fromCache: boolean }`.
- **`executeMCPToolAction({ actionFn, args, log, actionName, processResult })`**:
- Update: While this function *can* technically coordinate caching if provided a `cacheKeyGenerator`, the current preferred pattern involves implementing caching *within* the `actionFn` (the direct wrapper) using `getCachedOrExecute`. `executeMCPToolAction` primarily orchestrates the call to `actionFn` and handles processing its result (including the `fromCache` flag) via `handleApiResult`.
- **`handleApiResult(result, log, errorPrefix, processFunction)`**:
- Update: Now expects the `result` object to potentially contain a `fromCache` boolean flag. If present, this flag is included in the final response payload generated by `createContentResponse` (e.g., `{ fromCache: true, data: ... }`).
## Export Organization
- **Grouping Related Functions**:
- ✅ DO: Keep utilities relevant to their location (e.g., core utils in `scripts/modules/utils.js`, MCP utils in `mcp-server/src/tools/utils.js`).
- ✅ DO: Export all utility functions in a single statement per file.
- ✅ DO: Group related exports together.
- ✅ DO: Export configuration constants.
- ❌ DON'T: Use default exports.
- ❌ DON'T: Create circular dependencies between utility files or between utilities and the modules that use them (See [`architecture.mdc`](mdc:.cursor/rules/architecture.mdc)).
```javascript
// ✅ DO: Organize exports logically
export {
// Configuration
CONFIG,
LOG_LEVELS,
// Logging
log,
// File operations
readJSON,
writeJSON,
// String manipulation
sanitizePrompt,
truncate,
// Task utilities
readComplexityReport,
findTaskInComplexityReport,
taskExists,
formatTaskId,
findTaskById,
// Graph algorithms
findCycles,
};
```
Refer to [`utils.js`](mdc:scripts/modules/utils.js) for implementation examples and [`new_features.mdc`](mdc:.cursor/rules/new_features.mdc) for integration guidelines. Use [`commands.mdc`](mdc:.cursor/rules/commands.mdc) for CLI integration details.