Refactor: Modularize Task Master CLI into Modules Directory

Simplified the Task Master CLI by organizing code into modules within the  directory.

**Why:**

- **Better Organization:** Code is now grouped by function (AI, commands, dependencies, tasks, UI, utilities).
- **Easier to Maintain:**  Smaller modules are simpler to update and fix.
- **Scalable:**  New features can be added more easily in a structured way.

**What Changed:**

- Moved code from single   _____         _      __  __           _
 |_   _|_ _ ___| | __ |  \/  | __ _ ___| |_ ___ _ __
   | |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
   | | (_| \__ \   <  | |  | | (_| \__ \ ||  __/ |
   |_|\__,_|___/_|\_\ |_|  |_|\__,_|___/\__\___|_|

by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│                                            │
│   Version: 0.9.16   Project: Task Master   │
│                                            │
╰────────────────────────────────────────────╯

╭─────────────────────╮
│                     │
│   Task Master CLI   │
│                     │
╰─────────────────────╯

╭───────────────────╮
│  Task Generation  │
╰───────────────────╯
    parse-prd                 --input=<file.txt> [--tasks=10]          Generate tasks from a PRD document
    generate                                                           Create individual task files from tasks…

╭───────────────────╮
│  Task Management  │
╰───────────────────╯
    list                      [--status=<status>] [--with-subtas…      List all tasks with their status
    set-status                --id=<id> --status=<status>              Update task status (done, pending, etc.)
    update                    --from=<id> --prompt="<context>"         Update tasks based on new requirements
    add-task                  --prompt="<text>" [--dependencies=…      Add a new task using AI
    add-dependency            --id=<id> --depends-on=<id>              Add a dependency to a task
    remove-dependency         --id=<id> --depends-on=<id>              Remove a dependency from a task

╭──────────────────────────╮
│  Task Analysis & Detail  │
╰──────────────────────────╯
    analyze-complexity        [--research] [--threshold=5]             Analyze tasks and generate expansion re…
    complexity-report         [--file=<path>]                          Display the complexity analysis report
    expand                    --id=<id> [--num=5] [--research] […      Break down tasks into detailed subtasks
    expand --all              [--force] [--research]                   Expand all pending tasks with subtasks
    clear-subtasks            --id=<id>                                Remove subtasks from specified tasks

╭─────────────────────────────╮
│  Task Navigation & Viewing  │
╰─────────────────────────────╯
    next                                                               Show the next task to work on based on …
    show                      <id>                                     Display detailed information about a sp…

╭─────────────────────────╮
│  Dependency Management  │
╰─────────────────────────╯
    validate-dependenci…                                               Identify invalid dependencies without f…
    fix-dependencies                                                   Fix invalid dependencies automatically

╭─────────────────────────╮
│  Environment Variables  │
╰─────────────────────────╯
    ANTHROPIC_API_KEY              Your Anthropic API key                             Required
    MODEL                          Claude model to use                                Default: claude-3-7-sonn…
    MAX_TOKENS                     Maximum tokens for responses                       Default: 4000
    TEMPERATURE                    Temperature for model responses                    Default: 0.7
    PERPLEXITY_API_KEY             Perplexity API key for research                    Optional
    PERPLEXITY_MODEL               Perplexity model to use                            Default: sonar-small-onl…
    DEBUG                          Enable debug logging                               Default: false
    LOG_LEVEL                      Console output level (debug,info,warn,error)       Default: info
    DEFAULT_SUBTASKS               Default number of subtasks to generate             Default: 3
    DEFAULT_PRIORITY               Default task priority                              Default: medium
    PROJECT_NAME                   Project name displayed in UI                       Default: Task Master       file into these new modules:
    - : AI interactions (Claude, Perplexity)
    - :  CLI command definitions (Commander.js)
    - : Task dependency handling
    - : Core task operations (create, list, update, etc.)
    - : User interface elements (display, formatting)
    - : Utility functions and configuration
    - :  Exports all modules
- Replaced direct use of   _____         _      __  __           _
 |_   _|_ _ ___| | __ |  \/  | __ _ ___| |_ ___ _ __
   | |/ _` / __| |/ / | |\/| |/ _` / __| __/ _ \ '__|
   | | (_| \__ \   <  | |  | | (_| \__ \ ||  __/ |
   |_|\__,_|___/_|\_\ |_|  |_|\__,_|___/\__\___|_|

by https://x.com/eyaltoledano
╭────────────────────────────────────────────╮
│                                            │
│   Version: 0.9.16   Project: Task Master   │
│                                            │
╰────────────────────────────────────────────╯

╭─────────────────────╮
│                     │
│   Task Master CLI   │
│                     │
╰─────────────────────╯

╭───────────────────╮
│  Task Generation  │
╰───────────────────╯
    parse-prd                 --input=<file.txt> [--tasks=10]          Generate tasks from a PRD document
    generate                                                           Create individual task files from tasks…

╭───────────────────╮
│  Task Management  │
╰───────────────────╯
    list                      [--status=<status>] [--with-subtas…      List all tasks with their status
    set-status                --id=<id> --status=<status>              Update task status (done, pending, etc.)
    update                    --from=<id> --prompt="<context>"         Update tasks based on new requirements
    add-task                  --prompt="<text>" [--dependencies=…      Add a new task using AI
    add-dependency            --id=<id> --depends-on=<id>              Add a dependency to a task
    remove-dependency         --id=<id> --depends-on=<id>              Remove a dependency from a task

╭──────────────────────────╮
│  Task Analysis & Detail  │
╰──────────────────────────╯
    analyze-complexity        [--research] [--threshold=5]             Analyze tasks and generate expansion re…
    complexity-report         [--file=<path>]                          Display the complexity analysis report
    expand                    --id=<id> [--num=5] [--research] […      Break down tasks into detailed subtasks
    expand --all              [--force] [--research]                   Expand all pending tasks with subtasks
    clear-subtasks            --id=<id>                                Remove subtasks from specified tasks

╭─────────────────────────────╮
│  Task Navigation & Viewing  │
╰─────────────────────────────╯
    next                                                               Show the next task to work on based on …
    show                      <id>                                     Display detailed information about a sp…

╭─────────────────────────╮
│  Dependency Management  │
╰─────────────────────────╯
    validate-dependenci…                                               Identify invalid dependencies without f…
    fix-dependencies                                                   Fix invalid dependencies automatically

╭─────────────────────────╮
│  Environment Variables  │
╰─────────────────────────╯
    ANTHROPIC_API_KEY              Your Anthropic API key                             Required
    MODEL                          Claude model to use                                Default: claude-3-7-sonn…
    MAX_TOKENS                     Maximum tokens for responses                       Default: 4000
    TEMPERATURE                    Temperature for model responses                    Default: 0.7
    PERPLEXITY_API_KEY             Perplexity API key for research                    Optional
    PERPLEXITY_MODEL               Perplexity model to use                            Default: sonar-small-onl…
    DEBUG                          Enable debug logging                               Default: false
    LOG_LEVEL                      Console output level (debug,info,warn,error)       Default: info
    DEFAULT_SUBTASKS               Default number of subtasks to generate             Default: 3
    DEFAULT_PRIORITY               Default task priority                              Default: medium
    PROJECT_NAME                   Project name displayed in UI                       Default: Task Master       with the global  command (see ).
- Updated documentation () to reflect the new  command.

**Benefits:**

Code is now cleaner, easier to work with, and ready for future growth.

Use the  command (or ) to run the CLI.  See  for command details.
This commit is contained in:
Eyal Toledano
2025-03-23 23:19:37 -04:00
parent f003fe8b52
commit 633a5b963e
41 changed files with 7942 additions and 6725 deletions

314
.cursor/rules/utilities.mdc Normal file
View File

@@ -0,0 +1,314 @@
---
description: Guidelines for implementing utility functions
globs: scripts/modules/utils.js
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;
}
```
## Export Organization
- **Grouping Related Functions**:
- ✅ DO: Export all utility functions in a single statement
- ✅ DO: Group related exports together
- ✅ DO: Export configuration constants
- ❌ DON'T: Use default exports
```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.