Files
claude-task-master/.cursor/rules/utilities.mdc
Eyal Toledano 74dcf3b5f4 refactor(mcp): Modularize direct functions in MCP server
Split monolithic task-master-core.js into separate function files within
the mcp-server/src/core/direct-functions/ directory. This change:

- Creates individual files for each direct function implementation
- Moves findTasksJsonPath to a dedicated utils/path-utils.js file
- Converts task-master-core.js to be a simple import/export hub
- Improves maintainability and organization of the codebase
- Reduces potential merge conflicts when multiple developers contribute
- Follows standard module separation patterns

Each function is now in its own self-contained file with clear imports and
focused responsibility, while maintaining the same API endpoints.
2025-03-31 01:52:34 -04:00

373 lines
13 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.
- 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.