--- 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. - Can be used within `*Direct` functions if needed, although often the `projectRoot` argument is passed through. - **`handleApiResult(result, log, errorPrefix, processFunction)`**: - ✅ **DO**: Call this from the MCP tool's `execute` method after receiving the result from the `*Direct` function wrapper. - Takes the standard `{ success, data/error, fromCache }` object returned by direct function wrappers. - Checks the `success` flag. - If successful, processes the `result.data` using the provided `processFunction` (defaults to `processMCPResponseData` for filtering). - Includes the `result.fromCache` flag in the final payload. - Returns a formatted MCP response object using `createContentResponse` or `createErrorResponse`. - **`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 direct function calls via `*Direct` wrappers. Use only as a fallback. - **`processMCPResponseData(taskOrData, fieldsToRemove = ['details', 'testStrategy'])`**: - Filters task data before sending it to the MCP client. Called by `handleApiResult` by default. - 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 tasks. - **`createContentResponse(content)`**: - Used by `handleApiResult` to format successful MCP responses. - Wraps the `content` (which includes the `fromCache` flag and processed `data`) in the standard FastMCP `{ content: [{ type: "text", text: ... }] }` structure, stringifying the payload object. - **`createErrorResponse(errorMessage)`**: - Used by `handleApiResult` or directly in the tool's `execute` catch block to format error responses for MCP. - Wraps the `errorMessage` in the standard FastMCP error structure. - **`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. - **Use Case**: Primarily for read-only operations (e.g., `list`, `show`, `next`). Avoid for operations modifying data. - Checks the `ContextManager` cache using `cacheKey`. - If HIT: returns the cached result directly (which should be `{ success, data/error }`), adding `fromCache: true`. - If MISS: executes the provided `actionFn` (an async function returning `{ success, data/error }`). - If `actionFn` succeeds, its result is stored in the cache. - Returns the result (cached or fresh) wrapped in the standard structure `{ success, data/error, fromCache: boolean }`. ## 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. ## MCP Server Utilities Structure - **Core Utilities** (`mcp-server/src/core/utils/path-utils.js`): - Contains path-related utilities like `findTasksJsonPath` that are used by direct function implementations. - These are imported by direct function files in the `direct-functions/` directory. - **MCP Tool Utilities** (`mcp-server/src/tools/utils.js`): - Contains utilities related to MCP response handling and caching.