Files
claude-task-master/.cursor/rules/new_features.mdc

371 lines
15 KiB
Plaintext

---
description: Guidelines for integrating new features into the Task Master CLI
globs: scripts/modules/*.js
alwaysApply: false
---
# Task Master Feature Integration Guidelines
## Feature Placement Decision Process
- **Identify Feature Type** (See [`architecture.mdc`](mdc:.cursor/rules/architecture.mdc) for module details):
- **Data Manipulation**: Features that create, read, update, or delete tasks belong in [`task-manager.js`](mdc:scripts/modules/task-manager.js). Follow guidelines in [`tasks.mdc`](mdc:.cursor/rules/tasks.mdc).
- **Dependency Management**: Features that handle task relationships belong in [`dependency-manager.js`](mdc:scripts/modules/dependency-manager.js). Follow guidelines in [`dependencies.mdc`](mdc:.cursor/rules/dependencies.mdc).
- **User Interface**: Features that display information to users belong in [`ui.js`](mdc:scripts/modules/ui.js). Follow guidelines in [`ui.mdc`](mdc:.cursor/rules/ui.mdc).
- **AI Integration**: Features that use AI models belong in [`ai-services.js`](mdc:scripts/modules/ai-services.js).
- **Cross-Cutting**: Features that don't fit one category may need components in multiple modules
- **Command-Line Interface** (See [`commands.mdc`](mdc:.cursor/rules/commands.mdc)):
- All new user-facing commands should be added to [`commands.js`](mdc:scripts/modules/commands.js)
- Use consistent patterns for option naming and help text
- Follow the Commander.js model for subcommand structure
## Implementation Pattern
The standard pattern for adding a feature follows this workflow:
1. **Core Logic**: Implement the business logic in the appropriate module (e.g., [`task-manager.js`](mdc:scripts/modules/task-manager.js)).
2. **UI Components**: Add any display functions to [`ui.js`](mdc:scripts/modules/ui.js) following [`ui.mdc`](mdc:.cursor/rules/ui.mdc).
3. **Command Integration**: Add the CLI command to [`commands.js`](mdc:scripts/modules/commands.js) following [`commands.mdc`](mdc:.cursor/rules/commands.mdc).
4. **Testing**: Write tests for all components of the feature (following [`tests.mdc`](mdc:.cursor/rules/tests.mdc))
5. **Configuration**: Update any configuration in [`utils.js`](mdc:scripts/modules/utils.js) if needed, following [`utilities.mdc`](mdc:.cursor/rules/utilities.mdc).
6. **Documentation**: Update help text and documentation in [dev_workflow.mdc](mdc:scripts/modules/dev_workflow.mdc)
```javascript
// 1. CORE LOGIC: Add function to appropriate module (example in task-manager.js)
/**
* Archives completed tasks to archive.json
* @param {string} tasksPath - Path to the tasks.json file
* @param {string} archivePath - Path to the archive.json file
* @returns {number} Number of tasks archived
*/
async function archiveTasks(tasksPath, archivePath = 'tasks/archive.json') {
// Implementation...
return archivedCount;
}
// Export from the module
export {
// ... existing exports ...
archiveTasks,
};
```
```javascript
// 2. UI COMPONENTS: Add display function to ui.js
/**
* Display archive operation results
* @param {string} archivePath - Path to the archive file
* @param {number} count - Number of tasks archived
*/
function displayArchiveResults(archivePath, count) {
console.log(boxen(
chalk.green(`Successfully archived ${count} tasks to ${archivePath}`),
{ padding: 1, borderColor: 'green', borderStyle: 'round' }
));
}
// Export from the module
export {
// ... existing exports ...
displayArchiveResults,
};
```
```javascript
// 3. COMMAND INTEGRATION: Add to commands.js
import { archiveTasks } from './task-manager.js';
import { displayArchiveResults } from './ui.js';
// In registerCommands function
programInstance
.command('archive')
.description('Archive completed tasks to separate file')
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
.option('-o, --output <file>', 'Archive output file', 'tasks/archive.json')
.action(async (options) => {
const tasksPath = options.file;
const archivePath = options.output;
console.log(chalk.blue(`Archiving completed tasks from ${tasksPath} to ${archivePath}...`));
const archivedCount = await archiveTasks(tasksPath, archivePath);
displayArchiveResults(archivePath, archivedCount);
});
```
## Cross-Module Features
For features requiring components in multiple modules:
- ✅ **DO**: Create a clear unidirectional flow of dependencies
```javascript
// In task-manager.js
function analyzeTasksDifficulty(tasks) {
// Implementation...
return difficultyScores;
}
// In ui.js - depends on task-manager.js
import { analyzeTasksDifficulty } from './task-manager.js';
function displayDifficultyReport(tasks) {
const scores = analyzeTasksDifficulty(tasks);
// Render the scores...
}
```
- ❌ **DON'T**: Create circular dependencies between modules
```javascript
// In task-manager.js - depends on ui.js
import { displayDifficultyReport } from './ui.js';
function analyzeTasks() {
// Implementation...
displayDifficultyReport(tasks); // WRONG! Don't call UI functions from task-manager
}
// In ui.js - depends on task-manager.js
import { analyzeTasks } from './task-manager.js';
```
## Command-Line Interface Standards
- **Naming Conventions**:
- Use kebab-case for command names (`analyze-complexity`, not `analyzeComplexity`)
- Use kebab-case for option names (`--output-format`, not `--outputFormat`)
- Use the same option names across commands when they represent the same concept
- **Command Structure**:
```javascript
programInstance
.command('command-name')
.description('Clear, concise description of what the command does')
.option('-s, --short-option <value>', 'Option description', 'default value')
.option('--long-option <value>', 'Option description')
.action(async (options) => {
// Command implementation
});
```
## Utility Function Guidelines
When adding utilities to [`utils.js`](mdc:scripts/modules/utils.js):
- Only add functions that could be used by multiple modules
- Keep utilities single-purpose and purely functional
- Document parameters and return values
```javascript
/**
* Formats a duration in milliseconds to a human-readable string
* @param {number} ms - Duration in milliseconds
* @returns {string} Formatted duration string (e.g., "2h 30m 15s")
*/
function formatDuration(ms) {
// Implementation...
return formatted;
}
```
## Writing Testable Code
When implementing new features, follow these guidelines to ensure your code is testable:
- **Dependency Injection**
- Design functions to accept dependencies as parameters
- Avoid hard-coded dependencies that are difficult to mock
```javascript
// ✅ DO: Accept dependencies as parameters
function processTask(task, fileSystem, logger) {
fileSystem.writeFile('task.json', JSON.stringify(task));
logger.info('Task processed');
}
// ❌ DON'T: Use hard-coded dependencies
function processTask(task) {
fs.writeFile('task.json', JSON.stringify(task));
console.log('Task processed');
}
```
- **Separate Logic from Side Effects**
- Keep pure logic separate from I/O operations or UI rendering
- This allows testing the logic without mocking complex dependencies
```javascript
// ✅ DO: Separate logic from side effects
function calculateTaskPriority(task, dependencies) {
// Pure logic that returns a value
return computedPriority;
}
function displayTaskPriority(task, dependencies) {
const priority = calculateTaskPriority(task, dependencies);
console.log(`Task priority: ${priority}`);
}
```
- **Callback Functions and Testing**
- When using callbacks (like in Commander.js commands), define them separately
- This allows testing the callback logic independently
```javascript
// ✅ DO: Define callbacks separately for testing
function getVersionString() {
// Logic to determine version
return version;
}
// In setupCLI
programInstance.version(getVersionString);
// In tests
test('getVersionString returns correct version', () => {
expect(getVersionString()).toBe('1.5.0');
});
```
- **UI Output Testing**
- For UI components, focus on testing conditional logic rather than exact output
- Use string pattern matching (like `expect(result).toContain('text')`)
- Pay attention to emojis and formatting which can make exact string matching difficult
```javascript
// ✅ DO: Test the essence of the output, not exact formatting
test('statusFormatter shows done status correctly', () => {
const result = formatStatus('done');
expect(result).toContain('done');
expect(result).toContain('✅');
});
```
## Testing Requirements
Every new feature **must** include comprehensive tests following the guidelines in [`tests.mdc`](mdc:.cursor/rules/tests.mdc). Testing should include:
1. **Unit Tests**: Test individual functions and components in isolation
```javascript
// Example unit test for a new utility function
describe('newFeatureUtil', () => {
test('should perform expected operation with valid input', () => {
expect(newFeatureUtil('valid input')).toBe('expected result');
});
test('should handle edge cases appropriately', () => {
expect(newFeatureUtil('')).toBeNull();
});
});
```
2. **Integration Tests**: Verify the feature works correctly with other components
```javascript
// Example integration test for a new command
describe('newCommand integration', () => {
test('should call the correct service functions with parsed arguments', () => {
const mockService = jest.fn().mockResolvedValue('success');
// Set up test with mocked dependencies
// Call the command handler
// Verify service was called with expected arguments
});
});
```
3. **Edge Cases**: Test boundary conditions and error handling
- Invalid inputs
- Missing dependencies
- File system errors
- API failures
4. **Test Coverage**: Aim for at least 80% coverage for all new code
5. **Jest Mocking Best Practices**
- Follow the mock-first-then-import pattern as described in [`tests.mdc`](mdc:.cursor/rules/tests.mdc)
- Use jest.spyOn() to create spy functions for testing
- Clear mocks between tests to prevent interference
- See the Jest Module Mocking Best Practices section in [`tests.mdc`](mdc:.cursor/rules/tests.mdc) for details
When submitting a new feature, always run the full test suite to ensure nothing was broken:
```bash
npm test
```
## Documentation Requirements
For each new feature:
1. Add help text to the command definition
2. Update [`dev_workflow.mdc`](mdc:scripts/modules/dev_workflow.mdc) with command reference
3. Consider updating [`architecture.mdc`](mdc:.cursor/rules/architecture.mdc) if the feature significantly changes module responsibilities.
Follow the existing command reference format:
```markdown
- **Command Reference: your-command**
- CLI Syntax: `task-master your-command [options]`
- Description: Brief explanation of what the command does
- Parameters:
- `--option1=<value>`: Description of option1 (default: 'default')
- `--option2=<value>`: Description of option2 (required)
- Example: `task-master your-command --option1=value --option2=value2`
- Notes: Additional details, limitations, or special considerations
```
For more information on module structure, see [`MODULE_PLAN.md`](mdc:scripts/modules/MODULE_PLAN.md) and follow [`self_improve.mdc`](mdc:scripts/modules/self_improve.mdc) for best practices on updating documentation.
## Adding MCP Server Support for Commands
Integrating Task Master commands with the MCP server (for use by tools like Cursor) follows a specific pattern distinct from the CLI command implementation, prioritizing performance and reliability.
- **Goal**: Leverage direct function calls to core logic, avoiding CLI overhead.
- **Reference**: See [`mcp.mdc`](mdc:.cursor/rules/mcp.mdc) for full details.
**MCP Integration Workflow**:
1. **Core Logic**: Ensure the command's core logic exists and is exported from the appropriate module (e.g., [`task-manager.js`](mdc:scripts/modules/task-manager.js)).
2. **Direct Function Wrapper (`task-master-core.js`)**:
- Create an `async function yourCommandDirect(args, log)` in [`task-master-core.js`](mdc:mcp-server/src/core/task-master-core.js).
- This function imports and calls the core logic function.
- It handles argument parsing, path resolution (e.g., using `findTasksJsonPath`), and validation specific to the direct call.
- **Implement Caching (if applicable)**:
- **Use Case**: Apply caching primarily for read-only operations that benefit from repeated calls (e.g., `listTasks`, `showTask`, `nextTask`). Avoid caching for operations that modify state (`setTaskStatus`, `addTask`, `parsePRD`, `updateTask`, `addDependency`, etc.).
- **Implementation**: Inside the `yourCommandDirect` function, use the `getCachedOrExecute` utility (imported from `../tools/utils.js`).
- Generate a unique `cacheKey` based on relevant arguments (e.g., file path, filters).
- Define an `async` function `coreActionFn` that wraps the actual call to the core logic and returns `{ success: true/false, data/error }`.
- Call `await getCachedOrExecute({ cacheKey, actionFn: coreActionFn, log });`.
- The `yourCommandDirect` function must return a standard object: `{ success: true/false, data/error, fromCache: boolean }`. For non-cached operations, `fromCache` should be `false`.
- Export the function and add it to the `directFunctions` map in `task-master-core.js`.
3. **MCP Tool File (`mcp-server/src/tools/`)**:
- Create a new file (e.g., `yourCommand.js`).
- Import `zod` (for schema), `handleApiResult`, `createErrorResponse` from `./utils.js`, and your `yourCommandDirect` function from `../core/task-master-core.js`.
- Implement `registerYourCommandTool(server)` which calls `server.addTool`.
- Define the tool's `name`, `description`, and `parameters` using `zod`.
- Define the `async execute(args, log)` method. **This is the standard pattern**:
```javascript
// In mcp-server/src/tools/yourCommand.js
import { z } from "zod";
import { handleApiResult, createErrorResponse } from "./utils.js";
import { yourCommandDirect } from "../core/task-master-core.js";
export function registerYourCommandTool(server) {
server.addTool({
name: "yourCommand",
description: "Description of your command.",
parameters: z.object({ /* zod schema, include projectRoot, file if needed */ }),
async execute(args, log) {
try {
log.info(`Executing Your Command with args: ${JSON.stringify(args)}`);
// 1. Call the direct function wrapper
const result = await yourCommandDirect(args, log);
// 2. Pass the result to handleApiResult for formatting
return handleApiResult(result, log, 'Error during Your Command');
} catch (error) {
// Catch unexpected errors from the direct call itself
log.error(`Unexpected error in tool execute: ${error.message}`);
return createErrorResponse(`Tool execution failed: ${error.message}`);
}
}
});
}
```
4. **Register in Tool Index**: Import and call `registerYourCommandTool` in [`mcp-server/src/tools/index.js`](mdc:mcp-server/src/tools/index.js).
5. **Update `mcp.json`**: Add the tool definition to `.cursor/mcp.json`.