diff --git a/README-task-master.md b/README-task-master.md index 4a90695e..818e696c 100644 --- a/README-task-master.md +++ b/README-task-master.md @@ -36,8 +36,6 @@ The script can be configured through environment variables in a `.env` file at t npm install task-master-ai ``` -## Usage - ### Initialize a new project ```bash @@ -69,6 +67,22 @@ cd claude-task-master node scripts/init.js ``` +## Task Structure + +Tasks in tasks.json have the following structure: + +- `id`: Unique identifier for the task (Example: `1`) +- `title`: Brief, descriptive title of the task (Example: `"Initialize Repo"`) +- `description`: Concise description of what the task involves (Example: `"Create a new repository, set up initial structure."`) +- `status`: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`) +- `dependencies`: IDs of tasks that must be completed before this task (Example: `[1, 2]`) + - Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) + - This helps quickly identify which prerequisite tasks are blocking work +- `priority`: Importance level of the task (Example: `"high"`, `"medium"`, `"low"`) +- `details`: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`) +- `testStrategy`: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`) +- `subtasks`: List of smaller, more specific tasks that make up the main task (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`) + ## Integrating with Cursor AI Claude Task Master is designed to work seamlessly with [Cursor AI](https://www.cursor.so/), providing a structured workflow for AI-driven development. @@ -127,7 +141,7 @@ What tasks are available to work on next? The agent will: - Run `node scripts/dev.js list` to see all tasks -- Run `node scripts/dev.js list --with-subtasks` to see tasks with their subtasks +- Run `node scripts/dev.js next` to determine the next task to work on - Analyze dependencies to determine which tasks are ready to be worked on - Prioritize tasks based on priority level and ID order - Suggest the next task(s) to implement @@ -227,77 +241,95 @@ The agent will execute: node scripts/dev.js expand --id=5 --research ``` -You can also apply research-backed generation to all tasks: -``` -Please break down all pending tasks using research-backed generation. -``` +## Command Reference -The agent will execute: -```bash -node scripts/dev.js expand --all --research -``` - -## Manual Command Reference - -While the Cursor agent will handle most commands for you, you can also run them manually: +Here's a comprehensive reference of all available commands: ### Parse PRD ```bash +# Parse a PRD file and generate tasks npm run parse-prd -- --input= + +# Limit the number of tasks generated +npm run dev -- parse-prd --input= --tasks=10 ``` ### List Tasks ```bash +# List all tasks npm run list -``` # List tasks with a specific status -```bash npm run dev -- list --status= -``` # List tasks with subtasks -```bash npm run dev -- list --with-subtasks -``` # List tasks with a specific status and include subtasks -```bash npm run dev -- list --status= --with-subtasks ``` +### Show Next Task +```bash +# Show the next task to work on based on dependencies and status +npm run dev -- next +``` + +### Show Specific Task +```bash +# Show details of a specific task +npm run dev -- show +# or +npm run dev -- show --id= + +# View a specific subtask (e.g., subtask 2 of task 1) +npm run dev -- show 1.2 +``` + ### Update Tasks ```bash +# Update tasks from a specific ID and provide context npm run dev -- update --from= --prompt="" ``` ### Generate Task Files ```bash +# Generate individual task files from tasks.json npm run generate ``` ### Set Task Status ```bash +# Set status of a single task npm run dev -- set-status --id= --status= + +# Set status for multiple tasks +npm run dev -- set-status --id=1,2,3 --status= + +# Set status for subtasks +npm run dev -- set-status --id=1.1,1.2 --status= ``` When marking a task as "done", all of its subtasks will automatically be marked as "done" as well. ### Expand Tasks ```bash -npm run dev -- expand --id= --subtasks= --prompt="" -``` -or -```bash -npm run dev -- expand --all -``` +# Expand a specific task with subtasks +npm run dev -- expand --id= --subtasks= -For research-backed subtask generation: -```bash +# Expand with additional context +npm run dev -- expand --id= --prompt="" + +# Expand all pending tasks +npm run dev -- expand --all + +# Force regeneration of subtasks for tasks that already have them +npm run dev -- expand --all --force + +# Research-backed subtask generation for a specific task npm run dev -- expand --id= --research -``` -or -```bash + +# Research-backed generation for all tasks npm run dev -- expand --all --research ``` @@ -313,6 +345,27 @@ npm run dev -- clear-subtasks --id=1,2,3 npm run dev -- clear-subtasks --all ``` +### Analyze Task Complexity +```bash +# Analyze complexity of all tasks +npm run dev -- analyze-complexity + +# Save report to a custom location +npm run dev -- analyze-complexity --output=my-report.json + +# Use a specific LLM model +npm run dev -- analyze-complexity --model=claude-3-opus-20240229 + +# Set a custom complexity threshold (1-10) +npm run dev -- analyze-complexity --threshold=6 + +# Use an alternative tasks file +npm run dev -- analyze-complexity --file=custom-tasks.json + +# Use Perplexity AI for research-backed complexity analysis +npm run dev -- analyze-complexity --research +``` + ### Managing Task Dependencies ```bash # Add a dependency to a task @@ -328,113 +381,17 @@ npm run dev -- validate-dependencies npm run dev -- fix-dependencies ``` -## Task Structure - -Tasks in tasks.json have the following structure: - -- `id`: Unique identifier for the task -- `title`: Brief, descriptive title of the task -- `description`: Concise description of what the task involves -- `status`: Current state of the task (pending, done, deferred) -- `dependencies`: IDs of tasks that must be completed before this task -- `priority`: Importance level of the task (high, medium, low) -- `details`: In-depth instructions for implementing the task -- `testStrategy`: Approach for verifying the task has been completed correctly -- `subtasks`: List of smaller, more specific tasks that make up the main task - -Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) throughout the system, making it easy to see which prerequisite tasks are done and which still need work. - -## Best Practices for AI-Driven Development - -1. **Start with a detailed PRD**: The more detailed your PRD, the better the generated tasks will be. - -2. **Review generated tasks**: After parsing the PRD, review the tasks to ensure they make sense and have appropriate dependencies. - -3. **Follow the dependency chain**: Always respect task dependencies - the Cursor agent will help with this. - -4. **Update as you go**: If your implementation diverges from the plan, use the update command to keep future tasks aligned with your current approach. - -5. **Break down complex tasks**: Use the expand command to break down complex tasks into manageable subtasks. - -6. **Regenerate task files**: After any updates to tasks.json, regenerate the task files to keep them in sync. - -7. **Communicate context to the agent**: When asking the Cursor agent to help with a task, provide context about what you're trying to achieve. - -## Example Cursor AI Interactions - -### Starting a new project -``` -I've just initialized a new project with Claude Task Master. I have a PRD at scripts/prd.txt. -Can you help me parse it and set up the initial tasks? -``` - -### Working on tasks -``` -What's the next task I should work on? Please consider dependencies and priorities. -``` - -### Implementing a specific task -``` -I'd like to implement task 4. Can you help me understand what needs to be done and how to approach it? -``` - -### Managing subtasks -``` -I need to regenerate the subtasks for task 3 with a different approach. Can you help me clear and regenerate them? -``` - -### Handling changes -``` -We've decided to use MongoDB instead of PostgreSQL. Can you update all future tasks to reflect this change? -``` - -### Completing work -``` -I've finished implementing the authentication system described in task 2. All tests are passing. -Please mark it as complete and tell me what I should work on next. -``` - -## Documentation - -For more detailed documentation on the scripts and command-line options, see the [scripts/README.md](scripts/README.md) file in your initialized project. - -## License - -MIT +## Feature Details ### Analyzing Task Complexity -To analyze the complexity of tasks and automatically generate expansion recommendations: - -```bash -npm run dev -- analyze-complexity -``` - -This command: -- Analyzes each task using AI to assess its complexity +The `analyze-complexity` command: +- Analyzes each task using AI to assess its complexity on a scale of 1-10 - Recommends optimal number of subtasks based on configured DEFAULT_SUBTASKS - Generates tailored prompts for expanding each task - Creates a comprehensive JSON report with ready-to-use commands - Saves the report to scripts/task-complexity-report.json by default -Options: -```bash -# Save report to a custom location -npm run dev -- analyze-complexity --output=my-report.json - -# Use a specific LLM model -npm run dev -- analyze-complexity --model=claude-3-opus-20240229 - -# Set a custom complexity threshold (1-10) -npm run dev -- analyze-complexity --threshold=6 - -# Use an alternative tasks file -npm run dev -- analyze-complexity --file=custom-tasks.json - -# Use Perplexity AI for research-backed complexity analysis -npm run dev -- analyze-complexity --research -``` - The generated report contains: - Complexity analysis for each task (scored 1-10) - Recommended number of subtasks based on complexity @@ -443,15 +400,7 @@ The generated report contains: ### Smart Task Expansion -The `expand` command now automatically checks for and uses the complexity report: - -```bash -# Expand a task, using complexity report recommendations if available -npm run dev -- expand --id=8 - -# Expand all tasks, prioritizing by complexity score if a report exists -npm run dev -- expand --all -``` +The `expand` command automatically checks for and uses the complexity report: When a complexity report exists: - Tasks are automatically expanded using the recommended subtask count and prompts @@ -472,17 +421,9 @@ npm run dev -- expand --id=8 npm run dev -- expand --all ``` -This integration ensures that task expansion is informed by thorough complexity analysis, resulting in better subtask organization and more efficient development. - ### Finding the Next Task -To find the next task to work on based on dependencies and status: - -```bash -npm run dev -- next -``` - -This command: +The `next` command: - Identifies tasks that are pending/in-progress and have all dependencies satisfied - Prioritizes tasks by priority level, dependency count, and task ID - Displays comprehensive information about the selected task: @@ -494,24 +435,9 @@ This command: - Command to mark the task as done - Commands for working with subtasks -Example Cursor AI interaction: -``` -What's the next task I should work on? -``` - ### Viewing Specific Task Details -To view detailed information about a specific task: - -```bash -npm run dev -- show 1 -``` -or -```bash -npm run dev -- show --id=1.2 -``` - -This command: +The `show` command: - Displays comprehensive details about a specific task or subtask - Shows task status, priority, dependencies, and detailed implementation notes - For parent tasks, displays all subtasks and their status @@ -519,46 +445,25 @@ This command: - Provides contextual action suggestions based on the task's state - Works with both regular tasks and subtasks (using the format taskId.subtaskId) -Example Cursor AI interaction: -``` -Show me the details for task 3 -``` -or -``` -Tell me more about subtask 2.1 -``` - -## Task Structure - -Tasks in tasks.json have the following structure: - -- `id`: Unique identifier for the task -- `title`: Brief, descriptive title of the task -- `description`: Concise description of what the task involves -- `status`: Current state of the task (pending, done, deferred) -- `dependencies`: IDs of tasks that must be completed before this task -- `priority`: Importance level of the task (high, medium, low) -- `details`: In-depth instructions for implementing the task -- `testStrategy`: Approach for verifying the task has been completed correctly -- `subtasks`: List of smaller, more specific tasks that make up the main task - -Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) throughout the system, making it easy to see which prerequisite tasks are done and which still need work. - ## Best Practices for AI-Driven Development 1. **Start with a detailed PRD**: The more detailed your PRD, the better the generated tasks will be. 2. **Review generated tasks**: After parsing the PRD, review the tasks to ensure they make sense and have appropriate dependencies. -3. **Follow the dependency chain**: Always respect task dependencies - the Cursor agent will help with this. +3. **Analyze task complexity**: Use the complexity analysis feature to identify which tasks should be broken down further. -4. **Update as you go**: If your implementation diverges from the plan, use the update command to keep future tasks aligned with your current approach. +4. **Follow the dependency chain**: Always respect task dependencies - the Cursor agent will help with this. -5. **Break down complex tasks**: Use the expand command to break down complex tasks into manageable subtasks. +5. **Update as you go**: If your implementation diverges from the plan, use the update command to keep future tasks aligned with your current approach. -6. **Regenerate task files**: After any updates to tasks.json, regenerate the task files to keep them in sync. +6. **Break down complex tasks**: Use the expand command to break down complex tasks into manageable subtasks. -7. **Communicate context to the agent**: When asking the Cursor agent to help with a task, provide context about what you're trying to achieve. +7. **Regenerate task files**: After any updates to tasks.json, regenerate the task files to keep them in sync. + +8. **Communicate context to the agent**: When asking the Cursor agent to help with a task, provide context about what you're trying to achieve. + +9. **Validate dependencies**: Periodically run the validate-dependencies command to check for invalid or circular dependencies. ## Example Cursor AI Interactions @@ -594,56 +499,7 @@ I've finished implementing the authentication system described in task 2. All te Please mark it as complete and tell me what I should work on next. ``` -## Documentation - -For more detailed documentation on the scripts and command-line options, see the [scripts/README.md](scripts/README.md) file in your initialized project. - -## License - -MIT - -### Analyzing Task Complexity - -To analyze the complexity of tasks and automatically generate expansion recommendations: - -```bash -npm run dev -- analyze-complexity +### Analyzing complexity ``` - -This command: -- Analyzes each task using AI to assess its complexity -- Recommends optimal number of subtasks based on configured DEFAULT_SUBTASKS -- Generates tailored prompts for expanding each task -- Creates a comprehensive JSON report with ready-to-use commands -- Saves the report to scripts/task-complexity-report.json by default - -Options: -```bash -# Save report to a custom location -npm run dev -- analyze-complexity --output=my-report.json - -# Use a specific LLM model -npm run dev -- analyze-complexity --model=claude-3-opus-20240229 - -# Set a custom complexity threshold (1-10) -npm run dev -- analyze-complexity --threshold=6 - -# Use an alternative tasks file -npm run dev -- analyze-complexity --file=custom-tasks.json - -# Use Perplexity AI for research-backed complexity analysis -npm run dev -- analyze-complexity --research -``` - -The generated report contains: -- Complexity analysis for each task (scored 1-10) -- Recommended number of subtasks based on complexity -- AI-generated expansion prompts customized for each task -- Ready-to-run expansion commands directly within each task analysis - -### Smart Task Expansion - -The `expand` command now automatically checks for and uses the complexity report: - -```bash -# Expand a task, using complexity report recommendations if available \ No newline at end of file +Can you analyze the complexity of our tasks to help me understand which ones need to be broken down further? +``` \ No newline at end of file diff --git a/README.md b/README.md index 06c004ad..0fe224af 100644 --- a/README.md +++ b/README.md @@ -34,9 +34,508 @@ This will create the necessary file structure for your project, including: - `tasks.json` - Empty tasks file - `tasks/` - Directory for task files -## Documentation +# Documentation -For more detailed documentation, see the README.md file in your initialized project. +## Requirements + +- Node.js 14.0.0 or higher +- Anthropic API key (Claude API) +- Anthropic SDK version 0.39.0 or higher +- OpenAI SDK (for Perplexity API integration, optional) + +## Configuration + +The script can be configured through environment variables in a `.env` file at the root of the project: + +### Required Configuration +- `ANTHROPIC_API_KEY`: Your Anthropic API key for Claude + +### Optional Configuration +- `MODEL`: Specify which Claude model to use (default: "claude-3-7-sonnet-20250219") +- `MAX_TOKENS`: Maximum tokens for model responses (default: 4000) +- `TEMPERATURE`: Temperature for model responses (default: 0.7) +- `PERPLEXITY_API_KEY`: Your Perplexity API key for research-backed subtask generation +- `PERPLEXITY_MODEL`: Specify which Perplexity model to use (default: "sonar-medium-online") +- `DEBUG`: Enable debug logging (default: false) +- `LOG_LEVEL`: Log level - debug, info, warn, error (default: info) +- `DEFAULT_SUBTASKS`: Default number of subtasks when expanding (default: 3) +- `DEFAULT_PRIORITY`: Default priority for generated tasks (default: medium) +- `PROJECT_NAME`: Override default project name in tasks.json +- `PROJECT_VERSION`: Override default version in tasks.json + +## Installation + +```bash +npm install task-master-ai +``` + +### Initialize a new project + +```bash +npx claude-task-init +``` + +This will prompt you for project details and set up a new project with the necessary files and structure. + +### Important Notes + +1. This package uses ES modules. Your package.json should include `"type": "module"`. +2. The Anthropic SDK version should be 0.39.0 or higher. + +## Troubleshooting + +### If `npx claude-task-init` doesn't respond: + +Try running it with Node directly: + +```bash +node node_modules/claude-task-master/scripts/init.js +``` + +Or clone the repository and run: + +```bash +git clone https://github.com/eyaltoledano/claude-task-master.git +cd claude-task-master +node scripts/init.js +``` + +## Task Structure + +Tasks in tasks.json have the following structure: + +- `id`: Unique identifier for the task (Example: `1`) +- `title`: Brief, descriptive title of the task (Example: `"Initialize Repo"`) +- `description`: Concise description of what the task involves (Example: `"Create a new repository, set up initial structure."`) +- `status`: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`) +- `dependencies`: IDs of tasks that must be completed before this task (Example: `[1, 2]`) + - Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) + - This helps quickly identify which prerequisite tasks are blocking work +- `priority`: Importance level of the task (Example: `"high"`, `"medium"`, `"low"`) +- `details`: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`) +- `testStrategy`: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`) +- `subtasks`: List of smaller, more specific tasks that make up the main task (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`) + +## Integrating with Cursor AI + +Claude Task Master is designed to work seamlessly with [Cursor AI](https://www.cursor.so/), providing a structured workflow for AI-driven development. + +### Setup with Cursor + +1. After initializing your project, open it in Cursor +2. The `.cursor/rules/dev_workflow.mdc` file is automatically loaded by Cursor, providing the AI with knowledge about the task management system +3. Place your PRD document in the `scripts/` directory (e.g., `scripts/prd.txt`) +4. Open Cursor's AI chat and switch to Agent mode + +### Initial Task Generation + +In Cursor's AI chat, instruct the agent to generate tasks from your PRD: + +``` +Please use the dev.js script to parse my PRD and generate tasks. The PRD is located at scripts/prd.txt. +``` + +The agent will execute: +```bash +node scripts/dev.js parse-prd --input=scripts/prd.txt +``` + +This will: +- Parse your PRD document +- Generate a structured `tasks.json` file with tasks, dependencies, priorities, and test strategies +- The agent will understand this process due to the Cursor rules + +### Generate Individual Task Files + +Next, ask the agent to generate individual task files: + +``` +Please generate individual task files from tasks.json +``` + +The agent will execute: +```bash +node scripts/dev.js generate +``` + +This creates individual task files in the `tasks/` directory (e.g., `task_001.txt`, `task_002.txt`), making it easier to reference specific tasks. + +## AI-Driven Development Workflow + +The Cursor agent is pre-configured (via the rules file) to follow this workflow: + +### 1. Task Discovery and Selection + +Ask the agent to list available tasks: + +``` +What tasks are available to work on next? +``` + +The agent will: +- Run `node scripts/dev.js list` to see all tasks +- Run `node scripts/dev.js next` to determine the next task to work on +- Analyze dependencies to determine which tasks are ready to be worked on +- Prioritize tasks based on priority level and ID order +- Suggest the next task(s) to implement + +### 2. Task Implementation + +When implementing a task, the agent will: +- Reference the task's details section for implementation specifics +- Consider dependencies on previous tasks +- Follow the project's coding standards +- Create appropriate tests based on the task's testStrategy + +You can ask: +``` +Let's implement task 3. What does it involve? +``` + +### 3. Task Verification + +Before marking a task as complete, verify it according to: +- The task's specified testStrategy +- Any automated tests in the codebase +- Manual verification if required + +### 4. Task Completion + +When a task is completed, tell the agent: + +``` +Task 3 is now complete. Please update its status. +``` + +The agent will execute: +```bash +node scripts/dev.js set-status --id=3 --status=done +``` + +### 5. Handling Implementation Drift + +If during implementation, you discover that: +- The current approach differs significantly from what was planned +- Future tasks need to be modified due to current implementation choices +- New dependencies or requirements have emerged + +Tell the agent: +``` +We've changed our approach. We're now using Express instead of Fastify. Please update all future tasks to reflect this change. +``` + +The agent will execute: +```bash +node scripts/dev.js update --from=4 --prompt="Now we are using Express instead of Fastify." +``` + +This will rewrite or re-scope subsequent tasks in tasks.json while preserving completed work. + +### 6. Breaking Down Complex Tasks + +For complex tasks that need more granularity: + +``` +Task 5 seems complex. Can you break it down into subtasks? +``` + +The agent will execute: +```bash +node scripts/dev.js expand --id=5 --subtasks=3 +``` + +You can provide additional context: +``` +Please break down task 5 with a focus on security considerations. +``` + +The agent will execute: +```bash +node scripts/dev.js expand --id=5 --prompt="Focus on security aspects" +``` + +You can also expand all pending tasks: +``` +Please break down all pending tasks into subtasks. +``` + +The agent will execute: +```bash +node scripts/dev.js expand --all +``` + +For research-backed subtask generation using Perplexity AI: +``` +Please break down task 5 using research-backed generation. +``` + +The agent will execute: +```bash +node scripts/dev.js expand --id=5 --research +``` + +## Command Reference + +Here's a comprehensive reference of all available commands: + +### Parse PRD +```bash +# Parse a PRD file and generate tasks +npm run parse-prd -- --input= + +# Limit the number of tasks generated +npm run dev -- parse-prd --input= --tasks=10 +``` + +### List Tasks +```bash +# List all tasks +npm run list + +# List tasks with a specific status +npm run dev -- list --status= + +# List tasks with subtasks +npm run dev -- list --with-subtasks + +# List tasks with a specific status and include subtasks +npm run dev -- list --status= --with-subtasks +``` + +### Show Next Task +```bash +# Show the next task to work on based on dependencies and status +npm run dev -- next +``` + +### Show Specific Task +```bash +# Show details of a specific task +npm run dev -- show +# or +npm run dev -- show --id= + +# View a specific subtask (e.g., subtask 2 of task 1) +npm run dev -- show 1.2 +``` + +### Update Tasks +```bash +# Update tasks from a specific ID and provide context +npm run dev -- update --from= --prompt="" +``` + +### Generate Task Files +```bash +# Generate individual task files from tasks.json +npm run generate +``` + +### Set Task Status +```bash +# Set status of a single task +npm run dev -- set-status --id= --status= + +# Set status for multiple tasks +npm run dev -- set-status --id=1,2,3 --status= + +# Set status for subtasks +npm run dev -- set-status --id=1.1,1.2 --status= +``` + +When marking a task as "done", all of its subtasks will automatically be marked as "done" as well. + +### Expand Tasks +```bash +# Expand a specific task with subtasks +npm run dev -- expand --id= --subtasks= + +# Expand with additional context +npm run dev -- expand --id= --prompt="" + +# Expand all pending tasks +npm run dev -- expand --all + +# Force regeneration of subtasks for tasks that already have them +npm run dev -- expand --all --force + +# Research-backed subtask generation for a specific task +npm run dev -- expand --id= --research + +# Research-backed generation for all tasks +npm run dev -- expand --all --research +``` + +### Clear Subtasks +```bash +# Clear subtasks from a specific task +npm run dev -- clear-subtasks --id= + +# Clear subtasks from multiple tasks +npm run dev -- clear-subtasks --id=1,2,3 + +# Clear subtasks from all tasks +npm run dev -- clear-subtasks --all +``` + +### Analyze Task Complexity +```bash +# Analyze complexity of all tasks +npm run dev -- analyze-complexity + +# Save report to a custom location +npm run dev -- analyze-complexity --output=my-report.json + +# Use a specific LLM model +npm run dev -- analyze-complexity --model=claude-3-opus-20240229 + +# Set a custom complexity threshold (1-10) +npm run dev -- analyze-complexity --threshold=6 + +# Use an alternative tasks file +npm run dev -- analyze-complexity --file=custom-tasks.json + +# Use Perplexity AI for research-backed complexity analysis +npm run dev -- analyze-complexity --research +``` + +### Managing Task Dependencies +```bash +# Add a dependency to a task +npm run dev -- add-dependency --id= --depends-on= + +# Remove a dependency from a task +npm run dev -- remove-dependency --id= --depends-on= + +# Validate dependencies without fixing them +npm run dev -- validate-dependencies + +# Find and fix invalid dependencies automatically +npm run dev -- fix-dependencies +``` + +## Feature Details + +### Analyzing Task Complexity + +The `analyze-complexity` command: +- Analyzes each task using AI to assess its complexity on a scale of 1-10 +- Recommends optimal number of subtasks based on configured DEFAULT_SUBTASKS +- Generates tailored prompts for expanding each task +- Creates a comprehensive JSON report with ready-to-use commands +- Saves the report to scripts/task-complexity-report.json by default + +The generated report contains: +- Complexity analysis for each task (scored 1-10) +- Recommended number of subtasks based on complexity +- AI-generated expansion prompts customized for each task +- Ready-to-run expansion commands directly within each task analysis + +### Smart Task Expansion + +The `expand` command automatically checks for and uses the complexity report: + +When a complexity report exists: +- Tasks are automatically expanded using the recommended subtask count and prompts +- When expanding all tasks, they're processed in order of complexity (highest first) +- Research-backed generation is preserved from the complexity analysis +- You can still override recommendations with explicit command-line options + +Example workflow: +```bash +# Generate the complexity analysis report with research capabilities +npm run dev -- analyze-complexity --research + +# Review the report in scripts/task-complexity-report.json + +# Expand tasks using the optimized recommendations +npm run dev -- expand --id=8 +# or expand all tasks +npm run dev -- expand --all +``` + +### Finding the Next Task + +The `next` command: +- Identifies tasks that are pending/in-progress and have all dependencies satisfied +- Prioritizes tasks by priority level, dependency count, and task ID +- Displays comprehensive information about the selected task: + - Basic task details (ID, title, priority, dependencies) + - Implementation details + - Subtasks (if they exist) +- Provides contextual suggested actions: + - Command to mark the task as in-progress + - Command to mark the task as done + - Commands for working with subtasks + +### Viewing Specific Task Details + +The `show` command: +- Displays comprehensive details about a specific task or subtask +- Shows task status, priority, dependencies, and detailed implementation notes +- For parent tasks, displays all subtasks and their status +- For subtasks, shows parent task relationship +- Provides contextual action suggestions based on the task's state +- Works with both regular tasks and subtasks (using the format taskId.subtaskId) + +## Best Practices for AI-Driven Development + +1. **Start with a detailed PRD**: The more detailed your PRD, the better the generated tasks will be. + +2. **Review generated tasks**: After parsing the PRD, review the tasks to ensure they make sense and have appropriate dependencies. + +3. **Analyze task complexity**: Use the complexity analysis feature to identify which tasks should be broken down further. + +4. **Follow the dependency chain**: Always respect task dependencies - the Cursor agent will help with this. + +5. **Update as you go**: If your implementation diverges from the plan, use the update command to keep future tasks aligned with your current approach. + +6. **Break down complex tasks**: Use the expand command to break down complex tasks into manageable subtasks. + +7. **Regenerate task files**: After any updates to tasks.json, regenerate the task files to keep them in sync. + +8. **Communicate context to the agent**: When asking the Cursor agent to help with a task, provide context about what you're trying to achieve. + +9. **Validate dependencies**: Periodically run the validate-dependencies command to check for invalid or circular dependencies. + +## Example Cursor AI Interactions + +### Starting a new project +``` +I've just initialized a new project with Claude Task Master. I have a PRD at scripts/prd.txt. +Can you help me parse it and set up the initial tasks? +``` + +### Working on tasks +``` +What's the next task I should work on? Please consider dependencies and priorities. +``` + +### Implementing a specific task +``` +I'd like to implement task 4. Can you help me understand what needs to be done and how to approach it? +``` + +### Managing subtasks +``` +I need to regenerate the subtasks for task 3 with a different approach. Can you help me clear and regenerate them? +``` + +### Handling changes +``` +We've decided to use MongoDB instead of PostgreSQL. Can you update all future tasks to reflect this change? +``` + +### Completing work +``` +I've finished implementing the authentication system described in task 2. All tests are passing. +Please mark it as complete and tell me what I should work on next. +``` + +### Analyzing complexity +``` +Can you analyze the complexity of our tasks to help me understand which ones need to be broken down further? +``` ## License diff --git a/package.json b/package.json index 964ab356..b0745679 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "task-master-ai", - "version": "0.9.9", + "version": "0.9.11", "description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.", "main": "index.js", "type": "module", diff --git a/templates/README-task-master.md b/templates/README-task-master.md deleted file mode 100644 index 5e1bd872..00000000 --- a/templates/README-task-master.md +++ /dev/null @@ -1,643 +0,0 @@ -# Task Master -### by [@eyaltoledano](https://x.com/eyaltoledano) - -A task management system for AI-driven development with Claude, designed to work seamlessly with Cursor AI. - -## Requirements - -- Node.js 14.0.0 or higher -- Anthropic API key (Claude API) -- Anthropic SDK version 0.39.0 or higher -- OpenAI SDK (for Perplexity API integration, optional) - -## Configuration - -The script can be configured through environment variables in a `.env` file at the root of the project: - -### Required Configuration -- `ANTHROPIC_API_KEY`: Your Anthropic API key for Claude - -### Optional Configuration -- `MODEL`: Specify which Claude model to use (default: "claude-3-7-sonnet-20250219") -- `MAX_TOKENS`: Maximum tokens for model responses (default: 4000) -- `TEMPERATURE`: Temperature for model responses (default: 0.7) -- `PERPLEXITY_API_KEY`: Your Perplexity API key for research-backed subtask generation -- `PERPLEXITY_MODEL`: Specify which Perplexity model to use (default: "sonar-medium-online") -- `DEBUG`: Enable debug logging (default: false) -- `LOG_LEVEL`: Log level - debug, info, warn, error (default: info) -- `DEFAULT_SUBTASKS`: Default number of subtasks when expanding (default: 3) -- `DEFAULT_PRIORITY`: Default priority for generated tasks (default: medium) -- `PROJECT_NAME`: Override default project name in tasks.json -- `PROJECT_VERSION`: Override default version in tasks.json - -## Installation - -```bash -npm install task-master-ai -``` - -## Usage - -### Initialize a new project - -```bash -npx claude-task-init -``` - -This will prompt you for project details and set up a new project with the necessary files and structure. - -### Important Notes - -1. This package uses ES modules. Your package.json should include `"type": "module"`. -2. The Anthropic SDK version should be 0.39.0 or higher. - -## Troubleshooting - -### If `npx claude-task-init` doesn't respond: - -Try running it with Node directly: - -```bash -node node_modules/claude-task-master/scripts/init.js -``` - -Or clone the repository and run: - -```bash -git clone https://github.com/eyaltoledano/claude-task-master.git -cd claude-task-master -node scripts/init.js -``` - -## Integrating with Cursor AI - -Claude Task Master is designed to work seamlessly with [Cursor AI](https://www.cursor.so/), providing a structured workflow for AI-driven development. - -### Setup with Cursor - -1. After initializing your project, open it in Cursor -2. The `.cursor/rules/dev_workflow.mdc` file is automatically loaded by Cursor, providing the AI with knowledge about the task management system -3. Place your PRD document in the `scripts/` directory (e.g., `scripts/prd.txt`) -4. Open Cursor's AI chat and switch to Agent mode - -### Initial Task Generation - -In Cursor's AI chat, instruct the agent to generate tasks from your PRD: - -``` -Please use the dev.js script to parse my PRD and generate tasks. The PRD is located at scripts/prd.txt. -``` - -The agent will execute: -```bash -node scripts/dev.js parse-prd --input=scripts/prd.txt -``` - -This will: -- Parse your PRD document -- Generate a structured `tasks.json` file with tasks, dependencies, priorities, and test strategies -- The agent will understand this process due to the Cursor rules - -### Generate Individual Task Files - -Next, ask the agent to generate individual task files: - -``` -Please generate individual task files from tasks.json -``` - -The agent will execute: -```bash -node scripts/dev.js generate -``` - -This creates individual task files in the `tasks/` directory (e.g., `task_001.txt`, `task_002.txt`), making it easier to reference specific tasks. - -## AI-Driven Development Workflow - -The Cursor agent is pre-configured (via the rules file) to follow this workflow: - -### 1. Task Discovery and Selection - -Ask the agent to list available tasks: - -``` -What tasks are available to work on next? -``` - -The agent will: -- Run `node scripts/dev.js list` to see all tasks -- Run `node scripts/dev.js list --with-subtasks` to see tasks with their subtasks -- Analyze dependencies to determine which tasks are ready to be worked on -- Prioritize tasks based on priority level and ID order -- Suggest the next task(s) to implement - -### 2. Task Implementation - -When implementing a task, the agent will: -- Reference the task's details section for implementation specifics -- Consider dependencies on previous tasks -- Follow the project's coding standards -- Create appropriate tests based on the task's testStrategy - -You can ask: -``` -Let's implement task 3. What does it involve? -``` - -### 3. Task Verification - -Before marking a task as complete, verify it according to: -- The task's specified testStrategy -- Any automated tests in the codebase -- Manual verification if required - -### 4. Task Completion - -When a task is completed, tell the agent: - -``` -Task 3 is now complete. Please update its status. -``` - -The agent will execute: -```bash -node scripts/dev.js set-status --id=3 --status=done -``` - -### 5. Handling Implementation Drift - -If during implementation, you discover that: -- The current approach differs significantly from what was planned -- Future tasks need to be modified due to current implementation choices -- New dependencies or requirements have emerged - -Tell the agent: -``` -We've changed our approach. We're now using Express instead of Fastify. Please update all future tasks to reflect this change. -``` - -The agent will execute: -```bash -node scripts/dev.js update --from=4 --prompt="Now we are using Express instead of Fastify." -``` - -This will rewrite or re-scope subsequent tasks in tasks.json while preserving completed work. - -### 6. Breaking Down Complex Tasks - -For complex tasks that need more granularity: - -``` -Task 5 seems complex. Can you break it down into subtasks? -``` - -The agent will execute: -```bash -node scripts/dev.js expand --id=5 --subtasks=3 -``` - -You can provide additional context: -``` -Please break down task 5 with a focus on security considerations. -``` - -The agent will execute: -```bash -node scripts/dev.js expand --id=5 --prompt="Focus on security aspects" -``` - -You can also expand all pending tasks: -``` -Please break down all pending tasks into subtasks. -``` - -The agent will execute: -```bash -node scripts/dev.js expand --all -``` - -For research-backed subtask generation using Perplexity AI: -``` -Please break down task 5 using research-backed generation. -``` - -The agent will execute: -```bash -node scripts/dev.js expand --id=5 --research -``` - -You can also apply research-backed generation to all tasks: -``` -Please break down all pending tasks using research-backed generation. -``` - -The agent will execute: -```bash -node scripts/dev.js expand --all --research -``` - -## Manual Command Reference - -While the Cursor agent will handle most commands for you, you can also run them manually: - -### Parse PRD -```bash -npm run parse-prd -- --input= -``` - -### List Tasks -```bash -npm run list -``` - -# List tasks with a specific status -```bash -npm run dev -- list --status= -``` - -# List tasks with subtasks -```bash -npm run dev -- list --with-subtasks -``` - -# List tasks with a specific status and include subtasks -```bash -npm run dev -- list --status= --with-subtasks -``` - -### Update Tasks -```bash -npm run dev -- update --from= --prompt="" -``` - -### Generate Task Files -```bash -npm run generate -``` - -### Set Task Status -```bash -npm run dev -- set-status --id= --status= -``` - -When marking a task as "done", all of its subtasks will automatically be marked as "done" as well. - -### Expand Tasks -```bash -npm run dev -- expand --id= --subtasks= --prompt="" -``` -or -```bash -npm run dev -- expand --all -``` - -For research-backed subtask generation: -```bash -npm run dev -- expand --id= --research -``` -or -```bash -npm run dev -- expand --all --research -``` - -### Clear Subtasks -```bash -# Clear subtasks from a specific task -npm run dev -- clear-subtasks --id= - -# Clear subtasks from multiple tasks -npm run dev -- clear-subtasks --id=1,2,3 - -# Clear subtasks from all tasks -npm run dev -- clear-subtasks --all -``` - -### Managing Task Dependencies -```bash -# Add a dependency to a task -npm run dev -- add-dependency --id= --depends-on= - -# Remove a dependency from a task -npm run dev -- remove-dependency --id= --depends-on= -``` - -## Task Structure - -Tasks in tasks.json have the following structure: - -- `id`: Unique identifier for the task -- `title`: Brief, descriptive title of the task -- `description`: Concise description of what the task involves -- `status`: Current state of the task (pending, done, deferred) -- `dependencies`: IDs of tasks that must be completed before this task -- `priority`: Importance level of the task (high, medium, low) -- `details`: In-depth instructions for implementing the task -- `testStrategy`: Approach for verifying the task has been completed correctly -- `subtasks`: List of smaller, more specific tasks that make up the main task - -Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) throughout the system, making it easy to see which prerequisite tasks are done and which still need work. - -## Best Practices for AI-Driven Development - -1. **Start with a detailed PRD**: The more detailed your PRD, the better the generated tasks will be. - -2. **Review generated tasks**: After parsing the PRD, review the tasks to ensure they make sense and have appropriate dependencies. - -3. **Follow the dependency chain**: Always respect task dependencies - the Cursor agent will help with this. - -4. **Update as you go**: If your implementation diverges from the plan, use the update command to keep future tasks aligned with your current approach. - -5. **Break down complex tasks**: Use the expand command to break down complex tasks into manageable subtasks. - -6. **Regenerate task files**: After any updates to tasks.json, regenerate the task files to keep them in sync. - -7. **Communicate context to the agent**: When asking the Cursor agent to help with a task, provide context about what you're trying to achieve. - -## Example Cursor AI Interactions - -### Starting a new project -``` -I've just initialized a new project with Claude Task Master. I have a PRD at scripts/prd.txt. -Can you help me parse it and set up the initial tasks? -``` - -### Working on tasks -``` -What's the next task I should work on? Please consider dependencies and priorities. -``` - -### Implementing a specific task -``` -I'd like to implement task 4. Can you help me understand what needs to be done and how to approach it? -``` - -### Managing subtasks -``` -I need to regenerate the subtasks for task 3 with a different approach. Can you help me clear and regenerate them? -``` - -### Handling changes -``` -We've decided to use MongoDB instead of PostgreSQL. Can you update all future tasks to reflect this change? -``` - -### Completing work -``` -I've finished implementing the authentication system described in task 2. All tests are passing. -Please mark it as complete and tell me what I should work on next. -``` - -## Documentation - -For more detailed documentation on the scripts and command-line options, see the [scripts/README.md](scripts/README.md) file in your initialized project. - -## License - -MIT - -### Analyzing Task Complexity - -To analyze the complexity of tasks and automatically generate expansion recommendations: - -```bash -npm run dev -- analyze-complexity -``` - -This command: -- Analyzes each task using AI to assess its complexity -- Recommends optimal number of subtasks based on configured DEFAULT_SUBTASKS -- Generates tailored prompts for expanding each task -- Creates a comprehensive JSON report with ready-to-use commands -- Saves the report to scripts/task-complexity-report.json by default - -Options: -```bash -# Save report to a custom location -npm run dev -- analyze-complexity --output=my-report.json - -# Use a specific LLM model -npm run dev -- analyze-complexity --model=claude-3-opus-20240229 - -# Set a custom complexity threshold (1-10) -npm run dev -- analyze-complexity --threshold=6 - -# Use an alternative tasks file -npm run dev -- analyze-complexity --file=custom-tasks.json - -# Use Perplexity AI for research-backed complexity analysis -npm run dev -- analyze-complexity --research -``` - -The generated report contains: -- Complexity analysis for each task (scored 1-10) -- Recommended number of subtasks based on complexity -- AI-generated expansion prompts customized for each task -- Ready-to-run expansion commands directly within each task analysis - -### Smart Task Expansion - -The `expand` command now automatically checks for and uses the complexity report: - -```bash -# Expand a task, using complexity report recommendations if available -npm run dev -- expand --id=8 - -# Expand all tasks, prioritizing by complexity score if a report exists -npm run dev -- expand --all -``` - -When a complexity report exists: -- Tasks are automatically expanded using the recommended subtask count and prompts -- When expanding all tasks, they're processed in order of complexity (highest first) -- Research-backed generation is preserved from the complexity analysis -- You can still override recommendations with explicit command-line options - -Example workflow: -```bash -# Generate the complexity analysis report with research capabilities -npm run dev -- analyze-complexity --research - -# Review the report in scripts/task-complexity-report.json - -# Expand tasks using the optimized recommendations -npm run dev -- expand --id=8 -# or expand all tasks -npm run dev -- expand --all -``` - -This integration ensures that task expansion is informed by thorough complexity analysis, resulting in better subtask organization and more efficient development. - -### Finding the Next Task - -To find the next task to work on based on dependencies and status: - -```bash -npm run dev -- next -``` - -This command: -- Identifies tasks that are pending/in-progress and have all dependencies satisfied -- Prioritizes tasks by priority level, dependency count, and task ID -- Displays comprehensive information about the selected task: - - Basic task details (ID, title, priority, dependencies) - - Implementation details - - Subtasks (if they exist) -- Provides contextual suggested actions: - - Command to mark the task as in-progress - - Command to mark the task as done - - Commands for working with subtasks - -Example Cursor AI interaction: -``` -What's the next task I should work on? -``` - -### Viewing Specific Task Details - -To view detailed information about a specific task: - -```bash -npm run dev -- show 1 -``` -or -```bash -npm run dev -- show --id=1.2 -``` - -This command: -- Displays comprehensive details about a specific task or subtask -- Shows task status, priority, dependencies, and detailed implementation notes -- For parent tasks, displays all subtasks and their status -- For subtasks, shows parent task relationship -- Provides contextual action suggestions based on the task's state -- Works with both regular tasks and subtasks (using the format taskId.subtaskId) - -Example Cursor AI interaction: -``` -Show me the details for task 3 -``` -or -``` -Tell me more about subtask 2.1 -``` - -## Task Structure - -Tasks in tasks.json have the following structure: - -- `id`: Unique identifier for the task -- `title`: Brief, descriptive title of the task -- `description`: Concise description of what the task involves -- `status`: Current state of the task (pending, done, deferred) -- `dependencies`: IDs of tasks that must be completed before this task -- `priority`: Importance level of the task (high, medium, low) -- `details`: In-depth instructions for implementing the task -- `testStrategy`: Approach for verifying the task has been completed correctly -- `subtasks`: List of smaller, more specific tasks that make up the main task - -Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) throughout the system, making it easy to see which prerequisite tasks are done and which still need work. - -## Best Practices for AI-Driven Development - -1. **Start with a detailed PRD**: The more detailed your PRD, the better the generated tasks will be. - -2. **Review generated tasks**: After parsing the PRD, review the tasks to ensure they make sense and have appropriate dependencies. - -3. **Follow the dependency chain**: Always respect task dependencies - the Cursor agent will help with this. - -4. **Update as you go**: If your implementation diverges from the plan, use the update command to keep future tasks aligned with your current approach. - -5. **Break down complex tasks**: Use the expand command to break down complex tasks into manageable subtasks. - -6. **Regenerate task files**: After any updates to tasks.json, regenerate the task files to keep them in sync. - -7. **Communicate context to the agent**: When asking the Cursor agent to help with a task, provide context about what you're trying to achieve. - -## Example Cursor AI Interactions - -### Starting a new project -``` -I've just initialized a new project with Claude Task Master. I have a PRD at scripts/prd.txt. -Can you help me parse it and set up the initial tasks? -``` - -### Working on tasks -``` -What's the next task I should work on? Please consider dependencies and priorities. -``` - -### Implementing a specific task -``` -I'd like to implement task 4. Can you help me understand what needs to be done and how to approach it? -``` - -### Managing subtasks -``` -I need to regenerate the subtasks for task 3 with a different approach. Can you help me clear and regenerate them? -``` - -### Handling changes -``` -We've decided to use MongoDB instead of PostgreSQL. Can you update all future tasks to reflect this change? -``` - -### Completing work -``` -I've finished implementing the authentication system described in task 2. All tests are passing. -Please mark it as complete and tell me what I should work on next. -``` - -## Documentation - -For more detailed documentation on the scripts and command-line options, see the [scripts/README.md](scripts/README.md) file in your initialized project. - -## License - -MIT - -### Analyzing Task Complexity - -To analyze the complexity of tasks and automatically generate expansion recommendations: - -```bash -npm run dev -- analyze-complexity -``` - -This command: -- Analyzes each task using AI to assess its complexity -- Recommends optimal number of subtasks based on configured DEFAULT_SUBTASKS -- Generates tailored prompts for expanding each task -- Creates a comprehensive JSON report with ready-to-use commands -- Saves the report to scripts/task-complexity-report.json by default - -Options: -```bash -# Save report to a custom location -npm run dev -- analyze-complexity --output=my-report.json - -# Use a specific LLM model -npm run dev -- analyze-complexity --model=claude-3-opus-20240229 - -# Set a custom complexity threshold (1-10) -npm run dev -- analyze-complexity --threshold=6 - -# Use an alternative tasks file -npm run dev -- analyze-complexity --file=custom-tasks.json - -# Use Perplexity AI for research-backed complexity analysis -npm run dev -- analyze-complexity --research -``` - -The generated report contains: -- Complexity analysis for each task (scored 1-10) -- Recommended number of subtasks based on complexity -- AI-generated expansion prompts customized for each task -- Ready-to-run expansion commands directly within each task analysis - -### Smart Task Expansion - -The `expand` command now automatically checks for and uses the complexity report: - -```bash -# Expand a task, using complexity report recommendations if available \ No newline at end of file diff --git a/templates/cursor_rules.mdc b/templates/cursor_rules.mdc deleted file mode 100644 index 7dfae3de..00000000 --- a/templates/cursor_rules.mdc +++ /dev/null @@ -1,53 +0,0 @@ ---- -description: Guidelines for creating and maintaining Cursor rules to ensure consistency and effectiveness. -globs: .cursor/rules/*.mdc -alwaysApply: true ---- - -- **Required Rule Structure:** - ```markdown - --- - description: Clear, one-line description of what the rule enforces - globs: path/to/files/*.ext, other/path/**/* - alwaysApply: boolean - --- - - - **Main Points in Bold** - - Sub-points with details - - Examples and explanations - ``` - -- **File References:** - - Use `[filename](mdc:path/to/file)` ([filename](mdc:filename)) to reference files - - Example: [prisma.mdc](mdc:.cursor/rules/prisma.mdc) for rule references - - Example: [schema.prisma](mdc:prisma/schema.prisma) for code references - -- **Code Examples:** - - Use language-specific code blocks - ```typescript - // ✅ DO: Show good examples - const goodExample = true; - - // ❌ DON'T: Show anti-patterns - const badExample = false; - ``` - -- **Rule Content Guidelines:** - - Start with high-level overview - - Include specific, actionable requirements - - Show examples of correct implementation - - Reference existing code when possible - - Keep rules DRY by referencing other rules - -- **Rule Maintenance:** - - Update rules when new patterns emerge - - Add examples from actual codebase - - Remove outdated patterns - - Cross-reference related rules - -- **Best Practices:** - - Use bullet points for clarity - - Keep descriptions concise - - Include both DO and DON'T examples - - Reference actual code over theoretical examples - - Use consistent formatting across rules \ No newline at end of file diff --git a/templates/dev.js b/templates/dev.js deleted file mode 100755 index c3ae6463..00000000 --- a/templates/dev.js +++ /dev/null @@ -1,5201 +0,0 @@ -#!/usr/bin/env node - -/** - * dev.js - * - * Subcommands: - * 1) parse-prd --input=some-prd.txt [--tasks=10] - * -> Creates/overwrites tasks.json with a set of tasks (naive or LLM-based). - * -> Optional --tasks parameter limits the number of tasks generated. - * - * 2) update --from=5 --prompt="We changed from Slack to Discord." - * -> Regenerates tasks from ID >= 5 using the provided prompt. - * -> Only updates tasks that aren't marked as 'done'. - * -> The --prompt parameter is required and should explain the changes or new context. - * - * 3) generate - * -> Generates per-task files (e.g., task_001.txt) from tasks.json - * - * 4) set-status --id=4 --status=done - * -> Updates a single task's status to done (or pending, deferred, in-progress, etc.). - * -> Supports comma-separated IDs for updating multiple tasks: --id=1,2,3,1.1,1.2 - * -> If you set the status of a parent task to done, all its subtasks will be set to done. - * 5) list - * -> Lists tasks in a brief console view (ID, title, status). - * - * 6) expand --id=3 [--num=5] [--no-research] [--prompt="Additional context"] - * -> Expands a task with subtasks for more detailed implementation. - * -> Use --all instead of --id to expand all tasks. - * -> Optional --num parameter controls number of subtasks (default: 3). - * -> Uses Perplexity AI for research-backed subtask generation by default. - * -> Use --no-research to disable research-backed generation. - * -> Add --force when using --all to regenerate subtasks for tasks that already have them. - * -> Note: Tasks marked as 'done' or 'completed' are always skipped. - * -> If a complexity report exists for the specified task, its recommended - * subtask count and expansion prompt will be used (unless overridden). - * - * 7) analyze-complexity [options] - * -> Analyzes task complexity and generates expansion recommendations - * -> Generates a report in scripts/task-complexity-report.json by default - * -> Uses configured LLM to assess task complexity and create tailored expansion prompts - * -> Can use Perplexity AI for research-backed analysis with --research flag - * -> Each task includes: - * - Complexity score (1-10) - * - Recommended number of subtasks (based on DEFAULT_SUBTASKS config) - * - Detailed expansion prompt - * - Reasoning for complexity assessment - * - Ready-to-run expansion command - * -> Options: - * --output, -o : Specify output file path (default: 'scripts/task-complexity-report.json') - * --model, -m : Override LLM model to use for analysis - * --threshold, -t : Set minimum complexity score (1-10) for expansion recommendation (default: 5) - * --file, -f : Use alternative tasks.json file instead of default - * --research, -r: Use Perplexity AI for research-backed complexity analysis - * - * 8) clear-subtasks - * -> Clears subtasks from specified tasks - * -> Supports comma-separated IDs for clearing multiple tasks: --id=1,2,3,1.1,1.2 - * -> Use --all to clear subtasks from all tasks - * - * 9) next - * -> Shows the next task to work on based on dependencies and status - * -> Prioritizes tasks whose dependencies are all satisfied - * -> Orders eligible tasks by priority, dependency count, and ID - * -> Displays comprehensive information about the selected task - * -> Shows subtasks if they exist - * -> Provides contextual action commands for the next steps - * - * 10) show [id] or show --id= - * -> Shows details of a specific task by ID - * -> Displays the same comprehensive information as the 'next' command - * -> Handles both regular tasks and subtasks (e.g., 1.2) - * -> For subtasks, shows parent task information and link - * -> Provides contextual action commands tailored to the specific task - * - * 11) add-dependency --id= --depends-on= - * -> Adds a dependency to a task - * -> Checks if the dependency already exists before adding - * -> Prevents circular dependencies - * -> Automatically sorts dependencies for clarity - * - * 12) remove-dependency --id= --depends-on= - * -> Removes a dependency from a task - * -> Checks if the dependency exists before attempting to remove - * - * 13) validate-dependencies - * -> Checks for and identifies invalid dependencies in tasks.json and task files - * -> Reports all non-existent dependencies and self-dependencies - * -> Provides detailed statistics on task dependencies - * -> Does not automatically fix issues, only identifies them - * - * 14) fix-dependencies - * -> Finds and fixes all invalid dependencies in tasks.json and task files - * -> Removes references to non-existent tasks and subtasks - * -> Eliminates self-dependencies - * -> Regenerates task files with corrected dependencies - * -> Provides detailed report of all fixes made - * - * Usage examples: - * node dev.js parse-prd --input=sample-prd.txt - * node dev.js parse-prd --input=sample-prd.txt --tasks=10 - * node dev.js update --from=4 --prompt="Refactor tasks from ID 4 onward" - * node dev.js generate - * node dev.js set-status --id=3 --status=done - * node dev.js list - * node dev.js expand --id=3 --num=5 - * node dev.js expand --id=3 --no-research - * node dev.js expand --all - * node dev.js expand --all --force - * node dev.js analyze-complexity - * node dev.js analyze-complexity --output=custom-report.json - * node dev.js analyze-complexity --threshold=6 --model=claude-3.7-sonnet - * node dev.js analyze-complexity --research - * node dev.js clear-subtasks --id=1,2,3 --all - * node dev.js next - * node dev.js show 1 - * node dev.js show --id=1.2 - * node dev.js add-dependency --id=22 --depends-on=21 - * node dev.js remove-dependency --id=22 --depends-on=21 - * node dev.js validate-dependencies - * node dev.js fix-dependencies - */ - -import fs from 'fs'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import { dirname } from 'path'; -import readline from 'readline'; -import { program } from 'commander'; -import chalk from 'chalk'; -import { Anthropic } from '@anthropic-ai/sdk'; -import OpenAI from 'openai'; -import dotenv from 'dotenv'; -import figlet from 'figlet'; -import boxen from 'boxen'; -import ora from 'ora'; -import Table from 'cli-table3'; -import gradient from 'gradient-string'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = dirname(__filename); - -// Load environment variables -dotenv.config(); - -// Configure Anthropic client -const anthropic = new Anthropic({ - apiKey: process.env.ANTHROPIC_API_KEY, -}); - -// Configure OpenAI client for Perplexity - make it lazy -let perplexity = null; -function getPerplexityClient() { - if (!perplexity) { - if (!process.env.PERPLEXITY_API_KEY) { - throw new Error("PERPLEXITY_API_KEY environment variable is missing. Set it to use research-backed features."); - } - perplexity = new OpenAI({ - apiKey: process.env.PERPLEXITY_API_KEY, - baseURL: 'https://api.perplexity.ai', - }); - } - return perplexity; -} - -// Model configuration -const MODEL = process.env.MODEL || 'claude-3-7-sonnet-20250219'; -const PERPLEXITY_MODEL = process.env.PERPLEXITY_MODEL || 'sonar-small-online'; -const MAX_TOKENS = parseInt(process.env.MAX_TOKENS || '4000'); -const TEMPERATURE = parseFloat(process.env.TEMPERATURE || '0.7'); - -// Set up configuration with environment variables or defaults -const CONFIG = { - model: MODEL, - maxTokens: MAX_TOKENS, - temperature: TEMPERATURE, - 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" // Hardcoded version - ALWAYS use this value, ignore environment variable -}; - -// Set up logging based on log level -const LOG_LEVELS = { - debug: 0, - info: 1, - warn: 2, - error: 3 -}; - -// Create a color gradient for the banner -const coolGradient = gradient(['#00b4d8', '#0077b6', '#03045e']); -const warmGradient = gradient(['#fb8b24', '#e36414', '#9a031e']); - -// Display a fancy banner -function displayBanner() { - console.clear(); - const bannerText = figlet.textSync('Claude Task Master', { - font: 'Standard', - horizontalLayout: 'default', - verticalLayout: 'default' - }); - - console.log(coolGradient(bannerText)); - - // Read version directly from package.json - let version = "1.5.0"; // Default fallback - try { - const packageJsonPath = path.join(__dirname, '..', 'package.json'); - if (fs.existsSync(packageJsonPath)) { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); - version = packageJson.version; - } - } catch (error) { - // Silently fall back to default version - } - - console.log(boxen(chalk.white(`${chalk.bold('Version:')} ${version} ${chalk.bold('Project:')} ${CONFIG.projectName}`), { - padding: 1, - margin: { top: 0, bottom: 1 }, - borderStyle: 'round', - borderColor: 'cyan' - })); -} - -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] || ''; - - if (level === 'error') { - console.error(icon, chalk.red(...args)); - } else if (level === 'warn') { - console.warn(icon, chalk.yellow(...args)); - } else if (level === 'success') { - console.log(icon, chalk.green(...args)); - } else if (level === 'info') { - console.log(icon, chalk.blue(...args)); - } else { - console.log(icon, ...args); - } - } - - // Additional debug logging to file if debug mode is enabled - if (CONFIG.debug && level === 'debug') { - const timestamp = new Date().toISOString(); - const logMessage = `${timestamp} [DEBUG] ${args.join(' ')}\n`; - fs.appendFileSync('dev-debug.log', logMessage); - } -} - -function readJSON(filepath) { - if (!fs.existsSync(filepath)) return null; - try { - const content = fs.readFileSync(filepath, 'utf8'); - const data = JSON.parse(content); - - // Optional validation and cleanup of task dependencies - if (data && data.tasks && Array.isArray(data.tasks)) { - validateTaskDependencies(data.tasks, filepath); - } - - return data; - } catch (error) { - log('error', `Error reading JSON file: ${filepath}`, error); - return null; - } -} - -function writeJSON(filepath, data) { - fs.writeFileSync(filepath, JSON.stringify(data, null, 2), 'utf8'); -} - -// Replace the simple loading indicator with ora spinner -function startLoadingIndicator(message) { - const spinner = ora({ - text: message, - color: 'cyan', - spinner: 'dots' - }).start(); - - return spinner; -} - -function stopLoadingIndicator(spinner) { - if (spinner && spinner.stop) { - spinner.stop(); - } -} - -async function callClaude(prdContent, prdPath, numTasks, retryCount = 0) { - const MAX_RETRIES = 3; - const INITIAL_BACKOFF_MS = 1000; - - log('info', `Starting Claude API call to process PRD from ${prdPath}...`); - log('debug', `PRD content length: ${prdContent.length} characters`); - - // Start loading indicator - const loadingMessage = `Waiting for Claude to generate tasks${retryCount > 0 ? ` (retry ${retryCount}/${MAX_RETRIES})` : ''}...`; - const loadingIndicator = startLoadingIndicator(loadingMessage); - - const TASKS_JSON_TEMPLATE = ` - { - "meta": { - "projectName": "${CONFIG.projectName}", - "version": "${CONFIG.projectVersion}", - "source": "${prdPath}", - "description": "Tasks generated from ${prdPath.split('/').pop()}" - }, - "tasks": [ - { - "id": 1, - "title": "Set up project scaffolding", - "description": "Initialize repository structure with Wrangler configuration for Cloudflare Workers, set up D1 database schema, and configure development environment.", - "status": "pending", - "dependencies": [], - "priority": "high", - "details": "Create the initial project structure including:\n- Wrangler configuration for Cloudflare Workers\n- D1 database schema setup\n- Development environment configuration\n- Basic folder structure for the project", - "testStrategy": "Verify that the project structure is set up correctly and that the development environment can be started without errors." - }, - { - "id": 2, - "title": "Implement GitHub OAuth flow", - "description": "Create authentication system using GitHub OAuth for user sign-up and login, storing authenticated user profiles in D1 database.", - "status": "pending", - "dependencies": [1], - "priority": "${CONFIG.defaultPriority}", - "details": "Implement the GitHub OAuth flow for user authentication:\n- Create OAuth application in GitHub\n- Implement OAuth callback endpoint\n- Store user profiles in D1 database\n- Create session management", - "testStrategy": "Test the complete OAuth flow from login to callback to session creation. Verify user data is correctly stored in the database." - } - ] - }` - - let systemPrompt = "You are a helpful assistant that generates tasks from a PRD using the below json template. You don't worry much about non-task related content, nor do you worry about tasks that don't particularly add value to an mvp. Things like implementing security enhancements, documentation, expansive testing etc are nice to have. The most important is to turn the PRD into a task list that fully materializes the product enough so it can go to market. The JSON template goes as follows -- make sure to only return the json, nothing else: " + TASKS_JSON_TEMPLATE + "ONLY RETURN THE JSON, NOTHING ELSE."; - - // Add instruction about the number of tasks if specified - if (numTasks) { - systemPrompt += ` Generate exactly ${numTasks} tasks.`; - } else { - systemPrompt += " Generate a comprehensive set of tasks that covers all requirements in the PRD."; - } - - log('debug', "System prompt:", systemPrompt); - - try { - // Calculate appropriate max tokens based on PRD size - let maxTokens = CONFIG.maxTokens; - // Rough estimate: 1 token ≈ 4 characters - const estimatedPrdTokens = Math.ceil(prdContent.length / 4); - // Ensure we have enough tokens for the response - if (estimatedPrdTokens > maxTokens / 2) { - // If PRD is large, increase max tokens if possible - const suggestedMaxTokens = Math.min(32000, estimatedPrdTokens * 2); - if (suggestedMaxTokens > maxTokens) { - log('info', `PRD is large (est. ${estimatedPrdTokens} tokens). Increasing max_tokens to ${suggestedMaxTokens}.`); - maxTokens = suggestedMaxTokens; - } - } - - // Always use streaming to avoid "Streaming is strongly recommended" error - log('info', `Using streaming API for PRD processing...`); - return await handleStreamingRequest(prdContent, prdPath, numTasks, maxTokens, systemPrompt, loadingIndicator); - - } catch (error) { - // Stop loading indicator - stopLoadingIndicator(loadingIndicator); - - log('error', "Error calling Claude API:", error); - - // Implement exponential backoff for retries - if (retryCount < MAX_RETRIES) { - const backoffTime = INITIAL_BACKOFF_MS * Math.pow(2, retryCount); - log('info', `Retrying in ${backoffTime/1000} seconds (attempt ${retryCount + 1}/${MAX_RETRIES})...`); - - await new Promise(resolve => setTimeout(resolve, backoffTime)); - - // If we have a numTasks parameter and it's greater than 3, try again with fewer tasks - if (numTasks && numTasks > 3) { - const reducedTasks = Math.max(3, Math.floor(numTasks * 0.7)); // Reduce by 30%, minimum 3 - log('info', `Retrying with reduced task count: ${reducedTasks} (was ${numTasks})`); - return callClaude(prdContent, prdPath, reducedTasks, retryCount + 1); - } else { - // Otherwise, just retry with the same parameters - return callClaude(prdContent, prdPath, numTasks, retryCount + 1); - } - } - - // If we've exhausted all retries, ask the user what to do - console.log("\nClaude API call failed after multiple attempts."); - console.log("Options:"); - console.log("1. Retry with the same parameters"); - console.log("2. Retry with fewer tasks (if applicable)"); - console.log("3. Abort"); - - const readline = require('readline').createInterface({ - input: process.stdin, - output: process.stdout - }); - - return new Promise((resolve, reject) => { - readline.question('Enter your choice (1-3): ', async (choice) => { - readline.close(); - - switch (choice) { - case '1': - console.log("Retrying with the same parameters..."); - resolve(await callClaude(prdContent, prdPath, numTasks, 0)); // Reset retry count - break; - case '2': - if (numTasks && numTasks > 2) { - const reducedTasks = Math.max(2, Math.floor(numTasks * 0.5)); // Reduce by 50%, minimum 2 - console.log(`Retrying with reduced task count: ${reducedTasks} (was ${numTasks})...`); - resolve(await callClaude(prdContent, prdPath, reducedTasks, 0)); // Reset retry count - } else { - console.log("Cannot reduce task count further. Retrying with the same parameters..."); - resolve(await callClaude(prdContent, prdPath, numTasks, 0)); // Reset retry count - } - break; - case '3': - default: - console.log("Aborting..."); - reject(new Error("User aborted after multiple failed attempts")); - break; - } - }); - }); - } -} - -// Helper function to handle streaming requests to Claude API -async function handleStreamingRequest(prdContent, prdPath, numTasks, maxTokens, systemPrompt, loadingIndicator) { - log('info', "Sending streaming request to Claude API..."); - - let fullResponse = ''; - let streamComplete = false; - let streamError = null; - let streamingInterval = null; // Initialize streamingInterval here - - try { - const stream = await anthropic.messages.create({ - max_tokens: maxTokens, - model: CONFIG.model, - temperature: CONFIG.temperature, - messages: [{ role: "user", content: prdContent }], - system: systemPrompt, - stream: true - }); - - // Update loading indicator to show streaming progress - let dotCount = 0; - streamingInterval = setInterval(() => { - readline.cursorTo(process.stdout, 0); - process.stdout.write(`Receiving streaming response from Claude${'.'.repeat(dotCount)}`); - dotCount = (dotCount + 1) % 4; - }, 500); - - // Process the stream - for await (const chunk of stream) { - if (chunk.type === 'content_block_delta' && chunk.delta.text) { - fullResponse += chunk.delta.text; - } - } - - clearInterval(streamingInterval); - streamComplete = true; - - // Stop loading indicator - stopLoadingIndicator(loadingIndicator); - log('info', "Completed streaming response from Claude API!"); - log('debug', `Streaming response length: ${fullResponse.length} characters`); - - return processClaudeResponse(fullResponse, numTasks, 0, prdContent, prdPath); - } catch (error) { - if (streamingInterval) clearInterval(streamingInterval); // Safely clear interval - stopLoadingIndicator(loadingIndicator); - log('error', "Error during streaming response:", error); - throw error; - } -} - -// Helper function to process Claude's response text -function processClaudeResponse(textContent, numTasks, retryCount, prdContent, prdPath) { - try { - // Check if the response is wrapped in a Markdown code block and extract the JSON - log('info', "Parsing response as JSON..."); - let jsonText = textContent; - const codeBlockMatch = textContent.match(/```(?:json)?\s*([\s\S]*?)\s*```/); - if (codeBlockMatch) { - log('debug', "Detected JSON wrapped in Markdown code block, extracting..."); - jsonText = codeBlockMatch[1]; - } - - // Try to parse the response as JSON - const parsedJson = JSON.parse(jsonText); - - // Check if the response seems incomplete (e.g., missing closing brackets) - if (!parsedJson.tasks || parsedJson.tasks.length === 0) { - log('warn', "Parsed JSON has no tasks. Response may be incomplete."); - - // If we have a numTasks parameter and it's greater than 5, try again with fewer tasks - if (numTasks && numTasks > 5 && retryCount < MAX_RETRIES) { - const reducedTasks = Math.max(5, Math.floor(numTasks * 0.7)); // Reduce by 30%, minimum 5 - log('info', `Retrying with reduced task count: ${reducedTasks} (was ${numTasks})`); - return callClaude(prdContent, prdPath, reducedTasks, retryCount + 1); - } - } - - log('info', `Successfully parsed JSON with ${parsedJson.tasks?.length || 0} tasks`); - return parsedJson; - } catch (error) { - log('error', "Failed to parse Claude's response as JSON:", error); - log('debug', "Raw response:", textContent); - - // Check if we should retry with different parameters - if (retryCount < MAX_RETRIES) { - // If we have a numTasks parameter, try again with fewer tasks - if (numTasks && numTasks > 3) { - const reducedTasks = Math.max(3, Math.floor(numTasks * 0.6)); // Reduce by 40%, minimum 3 - log('info', `Retrying with reduced task count: ${reducedTasks} (was ${numTasks})`); - return callClaude(prdContent, prdPath, reducedTasks, retryCount + 1); - } else { - // Otherwise, just retry with the same parameters - log('info', `Retrying Claude API call (attempt ${retryCount + 1}/${MAX_RETRIES})...`); - return callClaude(prdContent, prdPath, numTasks, retryCount + 1); - } - } - - throw new Error("Failed to parse Claude's response as JSON after multiple attempts. See console for details."); - } -} - -// -// 1) parse-prd -// -async function parsePRD(prdPath, tasksPath, numTasks) { - displayBanner(); - - if (!fs.existsSync(prdPath)) { - log('error', `PRD file not found: ${prdPath}`); - process.exit(1); - } - - const headerBox = boxen( - chalk.white.bold(`Parsing PRD Document: ${chalk.cyan(path.basename(prdPath))}`), - { padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } } - ); - console.log(headerBox); - - log('info', `Reading PRD file from: ${prdPath}`); - const prdContent = fs.readFileSync(prdPath, 'utf8'); - log('info', `PRD file read successfully. Content length: ${prdContent.length} characters`); - - // call claude to generate the tasks.json - log('info', "Calling Claude to generate tasks from PRD..."); - - try { - const loadingSpinner = startLoadingIndicator('Generating tasks with Claude AI'); - const claudeResponse = await callClaude(prdContent, prdPath, numTasks); - let tasks = claudeResponse.tasks || []; - stopLoadingIndicator(loadingSpinner); - - log('success', `Claude generated ${tasks.length} tasks from the PRD`); - - // Limit the number of tasks if specified - if (numTasks && numTasks > 0 && numTasks < tasks.length) { - log('info', `Limiting to the first ${numTasks} tasks as specified`); - tasks = tasks.slice(0, numTasks); - } - - log('info', "Creating tasks.json data structure..."); - const data = { - meta: { - projectName: CONFIG.projectName, - version: CONFIG.projectVersion, - source: prdPath, - description: "Tasks generated from PRD", - totalTasksGenerated: claudeResponse.tasks?.length || 0, - tasksIncluded: tasks.length - }, - tasks - }; - - // Validate and fix dependencies in the generated tasks - log('info', "Validating dependencies in generated tasks..."); - const dependencyChanges = validateAndFixDependencies(data, null); - if (dependencyChanges) { - log('info', "Fixed some invalid dependencies in the generated tasks"); - } else { - log('info', "All dependencies in generated tasks are valid"); - } - - log('info', `Writing ${tasks.length} tasks to ${tasksPath}...`); - writeJSON(tasksPath, data); - - // Show success message in a box - const successBox = boxen( - chalk.green(`Successfully parsed PRD from: ${chalk.cyan(prdPath)}\n`) + - chalk.green(`Generated ${chalk.bold(tasks.length)} tasks in: ${chalk.cyan(tasksPath)}`), - { padding: 1, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } } - ); - console.log(successBox); - - // Show the first few tasks in a table - const previewTasks = tasks.slice(0, Math.min(5, tasks.length)); - const taskTable = new Table({ - head: [chalk.cyan.bold('ID'), chalk.cyan.bold('Title'), chalk.cyan.bold('Priority')], - colWidths: [6, 60, 12], - style: { head: [], border: [] } - }); - - previewTasks.forEach(task => { - taskTable.push([ - task.id.toString(), - task.title, - chalk.yellow(task.priority || 'medium') - ]); - }); - - if (tasks.length > 5) { - taskTable.push([ - '...', - chalk.dim(`${tasks.length - 5} more tasks`), - '' - ]); - } - - console.log(boxen( - chalk.white.bold('Task Preview:'), - { padding: { left: 2, right: 2, top: 0, bottom: 0 }, margin: { top: 1, bottom: 0 }, borderColor: 'blue', borderStyle: 'round' } - )); - console.log(taskTable.toString()); - - // Next steps suggestion - console.log(boxen( - chalk.white.bold('Next Steps:') + '\n\n' + - `${chalk.cyan('1.')} Run ${chalk.yellow('node scripts/dev.js generate')} to create individual task files\n` + - `${chalk.cyan('2.')} Run ${chalk.yellow('node scripts/dev.js list')} to see all tasks\n` + - `${chalk.cyan('3.')} Run ${chalk.yellow('node scripts/dev.js analyze-complexity')} to plan task breakdown`, - { padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1 } } - )); - - } catch (error) { - log('error', "Failed to generate tasks:", error.message); - process.exit(1); - } -} - -// -// 2) update -// -async function updateTasks(tasksPath, fromId, prompt) { - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', "Invalid or missing tasks.json."); - process.exit(1); - } - - log('info', `Updating tasks from ID >= ${fromId} with prompt: ${prompt}`); - - const tasksToUpdate = data.tasks.filter(task => task.id >= fromId && task.status !== "done"); - - const systemPrompt = "You are a helpful assistant that updates tasks based on provided insights. Return only the updated tasks as a JSON array."; - const userPrompt = `Update these tasks based on the following insight: ${prompt}\nTasks: ${JSON.stringify(tasksToUpdate, null, 2)}`; - - // Start loading indicator - const loadingIndicator = startLoadingIndicator("Waiting for Claude to update tasks..."); - - let fullResponse = ''; - let streamingInterval = null; - - try { - const stream = await anthropic.messages.create({ - max_tokens: CONFIG.maxTokens, - model: CONFIG.model, - temperature: CONFIG.temperature, - messages: [{ role: "user", content: userPrompt }], - system: systemPrompt, - stream: true - }); - - // Update loading indicator to show streaming progress - let dotCount = 0; - streamingInterval = setInterval(() => { - readline.cursorTo(process.stdout, 0); - process.stdout.write(`Receiving streaming response from Claude${'.'.repeat(dotCount)}`); - dotCount = (dotCount + 1) % 4; - }, 500); - - // Process the stream - for await (const chunk of stream) { - if (chunk.type === 'content_block_delta' && chunk.delta.text) { - fullResponse += chunk.delta.text; - } - } - - clearInterval(streamingInterval); - stopLoadingIndicator(loadingIndicator); - - log('info', "Completed streaming response from Claude API!"); - log('debug', `Streaming response length: ${fullResponse.length} characters`); - - try { - const updatedTasks = JSON.parse(fullResponse); - - data.tasks = data.tasks.map(task => { - const updatedTask = updatedTasks.find(t => t.id === task.id); - return updatedTask || task; - }); - - // Validate and fix dependencies after task updates - log('info', "Validating dependencies after task updates..."); - const dependencyChanges = validateAndFixDependencies(data, null); - if (dependencyChanges) { - log('info', "Fixed some dependencies that became invalid after task updates"); - } - - writeJSON(tasksPath, data); - log('info', "Tasks updated successfully."); - - // Add call to generate task files - log('info', "Regenerating task files..."); - await generateTaskFiles(tasksPath, path.dirname(tasksPath)); - - } catch (parseError) { - log('error', "Failed to parse Claude's response as JSON:", parseError); - log('debug', "Response content:", fullResponse); - process.exit(1); - } - } catch (error) { - if (streamingInterval) clearInterval(streamingInterval); - stopLoadingIndicator(loadingIndicator); - log('error', "Error during streaming response:", error); - process.exit(1); - } -} - -// -// 3) generate -// -function generateTaskFiles(tasksPath, outputDir) { - log('info', `Reading tasks from ${tasksPath}...`); - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', "No valid tasks to generate files for."); - process.exit(1); - } - - log('info', `Found ${data.tasks.length} tasks to generate files for.`); - - // Validate and fix dependencies before generating files - log('info', "Validating and fixing dependencies before generating files..."); - const changesDetected = validateAndFixDependencies(data, tasksPath); - if (changesDetected) { - log('info', "Fixed some invalid dependencies in the tasks"); - } else { - log('debug', "All dependencies are valid"); - } - - // The outputDir is now the same directory as tasksPath, so we don't need to check if it exists - // since we already did that in the main function - - log('info', "Generating individual task files..."); - data.tasks.forEach(task => { - const filename = `task_${String(task.id).padStart(3, '0')}.txt`; - const filepath = path.join(outputDir, filename); - - // Create the base content - const contentParts = [ - `# Task ID: ${task.id}`, - `# Title: ${task.title}`, - `# Status: ${task.status}`, - `# Dependencies: ${formatDependenciesWithStatus(task.dependencies, data.tasks, false)}`, - `# Priority: ${task.priority}`, - `# Description: ${task.description}`, - `# Details:\n${task.details}\n`, - `# Test Strategy:`, - `${task.testStrategy}\n` - ]; - - // Add subtasks if they exist - if (task.subtasks && task.subtasks.length > 0) { - contentParts.push(`# Subtasks:`); - task.subtasks.forEach(subtask => { - // Format subtask dependencies correctly by converting numeric IDs to parent.subtask format - let formattedDeps = []; - if (subtask.dependencies && subtask.dependencies.length > 0) { - // Format each dependency - this is a key change - formattedDeps = subtask.dependencies.map(depId => { - // If it already has a dot notation (e.g. "1.2"), keep it as is - if (typeof depId === 'string' && depId.includes('.')) { - // Validate that this subtask dependency actually exists - const [parentId, subId] = depId.split('.').map(id => isNaN(id) ? id : Number(id)); - const parentTask = data.tasks.find(t => t.id === parentId); - if (!parentTask || !parentTask.subtasks || !parentTask.subtasks.some(s => s.id === Number(subId))) { - log('warn', `Skipping non-existent subtask dependency: ${depId}`); - return null; - } - return depId; - } - // If it's a number, it's probably referencing a parent subtask in the same task - // Format it as "parentTaskId.subtaskId" - else if (typeof depId === 'number') { - // Check if this is likely a subtask ID (small number) within the same parent task - if (depId < 100) { // Assume subtask IDs are small numbers - // Validate that this subtask exists - if (!task.subtasks.some(s => s.id === depId)) { - log('warn', `Skipping non-existent subtask dependency: ${task.id}.${depId}`); - return null; - } - return `${task.id}.${depId}`; - } else { - // It's a reference to another task - validate it exists - if (!data.tasks.some(t => t.id === depId)) { - log('warn', `Skipping non-existent task dependency: ${depId}`); - return null; - } - return depId; - } - } - return depId; - }).filter(dep => dep !== null); // Remove null entries (invalid dependencies) - } - - const subtaskDeps = formattedDeps.length > 0 - ? formatDependenciesWithStatus(formattedDeps, data.tasks, false) - : "None"; - - contentParts.push(`## Subtask ID: ${subtask.id}`); - contentParts.push(`## Title: ${subtask.title}`); - contentParts.push(`## Status: ${subtask.status}`); - contentParts.push(`## Dependencies: ${subtaskDeps}`); - contentParts.push(`## Description: ${subtask.description}`); - if (subtask.acceptanceCriteria) { - contentParts.push(`## Acceptance Criteria:\n${subtask.acceptanceCriteria}\n`); - } - }); - } - - const content = contentParts.join('\n'); - fs.writeFileSync(filepath, content, 'utf8'); - log('info', `Generated: ${filename}`); - }); - - log('info', `All ${data.tasks.length} tasks have been generated into '${outputDir}'.`); -} - -// -// 4) set-status -// -function setTaskStatus(tasksPath, taskIdInput, newStatus) { - displayBanner(); - - // Validate inputs - if (!taskIdInput || !newStatus) { - log('error', 'Task ID and new status are required'); - process.exit(1); - } - - // Read fresh data for each status update - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', 'No valid tasks found in tasks.json'); - process.exit(1); - } - - console.log(boxen( - chalk.white.bold(`Updating Task Status to: ${getStatusWithColor(newStatus)}`), - { padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } } - )); - - // Handle multiple task IDs (comma-separated) - if (typeof taskIdInput === 'string' && taskIdInput.includes(',')) { - const taskIds = taskIdInput.split(',').map(id => id.trim()); - log('info', `Processing multiple task IDs: ${taskIds.join(', ')}`); - - // Create a summary table for the updates - const summaryTable = new Table({ - head: [ - chalk.cyan.bold('ID'), - chalk.cyan.bold('Title'), - chalk.cyan.bold('Old Status'), - chalk.cyan.bold('New Status') - ], - colWidths: [8, 40, 15, 15], - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { - 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' - } - }); - - // Process each task ID individually - taskIds.forEach(id => { - const result = updateSingleTaskStatus(tasksPath, id, newStatus, data); - if (result) { - summaryTable.push([ - result.id, - truncate(result.title, 37), - getStatusWithColor(result.oldStatus), - getStatusWithColor(result.newStatus) - ]); - - // Add subtask updates if any - if (result.subtasks && result.subtasks.length > 0) { - result.subtasks.forEach(sub => { - summaryTable.push([ - ` ${sub.id}`, - ` ↳ ${truncate(sub.title, 35)}`, - getStatusWithColor(sub.oldStatus), - getStatusWithColor(sub.newStatus) - ]); - }); - } - } - }); - - // Validate dependencies after status updates - log('info', "Validating dependencies after status updates..."); - const dependencyChanges = validateAndFixDependencies(data, null); - if (dependencyChanges) { - log('info', "Fixed some dependencies that became invalid after status changes"); - } - - // Save the changes - writeJSON(tasksPath, data); - - // Regenerate task files - log('info', "Regenerating task files..."); - generateTaskFiles(tasksPath, path.dirname(tasksPath)); - - // Show the summary table - console.log(boxen( - chalk.white.bold('Status Update Summary:'), - { padding: { left: 2, right: 2, top: 0, bottom: 0 }, margin: { top: 1, bottom: 0 }, borderColor: 'blue', borderStyle: 'round' } - )); - console.log(summaryTable.toString()); - - return; - } - - // Handle regular task ID or subtask ID - const result = updateSingleTaskStatus(tasksPath, taskIdInput, newStatus, data); - - if (result) { - // Validate dependencies after status update - log('info', "Validating dependencies after status update..."); - const dependencyChanges = validateAndFixDependencies(data, null); - if (dependencyChanges) { - log('info', "Fixed some dependencies that became invalid after status change"); - } - - // Save the changes - writeJSON(tasksPath, data); - - // Regenerate task files - log('info', "Regenerating task files..."); - generateTaskFiles(tasksPath, path.dirname(tasksPath)); - - // Show success message - const successBox = boxen( - chalk.green(`Successfully updated task ${chalk.bold(result.id)} status:\n`) + - `From: ${getStatusWithColor(result.oldStatus)}\n` + - `To: ${getStatusWithColor(result.newStatus)}`, - { padding: 1, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } } - ); - console.log(successBox); - - // If subtasks were also updated, show them - if (result.subtasks && result.subtasks.length > 0) { - const subtaskTable = new Table({ - head: [ - chalk.cyan.bold('ID'), - chalk.cyan.bold('Title'), - chalk.cyan.bold('Old Status'), - chalk.cyan.bold('New Status') - ], - colWidths: [8, 40, 15, 15], - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { - 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' - } - }); - - result.subtasks.forEach(sub => { - subtaskTable.push([ - ` ${sub.id}`, - ` ↳ ${truncate(sub.title, 35)}`, - getStatusWithColor(sub.oldStatus), - getStatusWithColor(sub.newStatus) - ]); - }); - - console.log(boxen( - chalk.white.bold('Subtasks Also Updated:'), - { padding: { left: 2, right: 2, top: 0, bottom: 0 }, margin: { top: 1, bottom: 0 }, borderColor: 'blue', borderStyle: 'round' } - )); - console.log(subtaskTable.toString()); - } - } -} - -// Helper function to update a single task status and return details for UI -function updateSingleTaskStatus(tasksPath, taskIdInput, newStatus, data) { - // Handle subtask IDs (e.g., "1.1") - if (String(taskIdInput).includes('.')) { - const [parentIdStr, subtaskIdStr] = String(taskIdInput).split('.'); - const parentId = parseInt(parentIdStr, 10); - const subtaskId = parseInt(subtaskIdStr, 10); - - if (isNaN(parentId) || isNaN(subtaskId)) { - log('error', `Invalid subtask ID format: ${taskIdInput}`); - return null; - } - - // Find the parent task - const parentTask = data.tasks.find(t => t.id === parentId); - if (!parentTask) { - log('error', `Parent task ${parentId} not found`); - return null; - } - - // Ensure subtasks array exists - if (!parentTask.subtasks || !Array.isArray(parentTask.subtasks)) { - log('error', `Parent task ${parentId} has no subtasks array`); - return null; - } - - // Find and update the subtask - const subtask = parentTask.subtasks.find(st => st.id === subtaskId); - if (!subtask) { - log('error', `Subtask ${subtaskId} not found in task ${parentId}`); - return null; - } - - // Update the subtask status - const oldStatus = subtask.status || 'pending'; - subtask.status = newStatus; - - return { - id: `${parentId}.${subtaskId}`, - title: subtask.title, - oldStatus: oldStatus, - newStatus: newStatus - }; - } - - // Handle regular task ID - const taskId = parseInt(String(taskIdInput), 10); - if (isNaN(taskId)) { - log('error', `Invalid task ID: ${taskIdInput}`); - return null; - } - - // Find the task - const task = data.tasks.find(t => t.id === taskId); - if (!task) { - log('error', `Task ${taskId} not found`); - return null; - } - - // Update the task status - const oldStatus = task.status || 'pending'; - task.status = newStatus; - - const result = { - id: taskId.toString(), - title: task.title, - oldStatus: oldStatus, - newStatus: newStatus, - subtasks: [] - }; - - // Automatically update subtasks if the parent task is being marked as done - if (newStatus === 'done' && task.subtasks && Array.isArray(task.subtasks) && task.subtasks.length > 0) { - log('info', `Task ${taskId} has ${task.subtasks.length} subtasks that will be marked as done too.`); - - task.subtasks.forEach(subtask => { - const oldSubtaskStatus = subtask.status || 'pending'; - subtask.status = newStatus; - - result.subtasks.push({ - id: `${taskId}.${subtask.id}`, - title: subtask.title, - oldStatus: oldSubtaskStatus, - newStatus: newStatus - }); - }); - } - - return result; -} - -/** - * Get a colored version of the status string - * @param {string} status - The status string - * @returns {string} - The colored status string - */ -function getStatusWithColor(status) { - const statusColor = { - 'done': chalk.green, - 'completed': chalk.green, - 'pending': chalk.yellow, - 'in-progress': chalk.blue, - 'deferred': chalk.gray - }[status] || chalk.white; - - return statusColor(status); -} - -/** - * Format dependencies with emoji indicators for completion status - * In CLI/console output use colors, in file output use emojis - * @param {Array} dependencies - Array of dependency IDs - * @param {Array} allTasks - Array of all tasks - * @param {boolean} forConsole - Whether this is for console output (true) or file output (false) - * @returns {string} - Formatted dependencies with status indicators - */ -function formatDependenciesWithStatus(dependencies, allTasks, forConsole = false) { - if (!dependencies || dependencies.length === 0) { - return 'None'; - } - - // Create a map of completed task IDs for quick lookup - const completedTaskIds = new Set( - allTasks - .filter(t => t.status === 'done' || t.status === 'completed') - .map(t => t.id) - ); - - // Create a map of subtask statuses for quick lookup - const subtaskStatusMap = new Map(); - allTasks.forEach(task => { - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach(subtask => { - subtaskStatusMap.set(`${task.id}.${subtask.id}`, subtask.status || 'pending'); - }); - } - }); - - // Map each dependency to include its status indicator - return dependencies.map(depId => { - // Check if it's a subtask dependency (e.g., "1.2") - const isSubtask = typeof depId === 'string' && depId.includes('.'); - - let isDone = false; - let status = 'pending'; - - if (isSubtask) { - // For subtask dependency - status = subtaskStatusMap.get(depId) || 'pending'; - isDone = status === 'done' || status === 'completed'; - } else { - // For regular task dependency - isDone = completedTaskIds.has(depId); - // Find the task to get its status - const depTask = allTasks.find(t => t.id === depId); - status = depTask ? (depTask.status || 'pending') : 'pending'; - } - - if (forConsole) { - // For console output, use colors - if (status === 'done' || status === 'completed') { - return chalk.green(depId.toString()); - } else if (status === 'in-progress') { - return chalk.yellow(depId.toString()); - } else { - return chalk.red(depId.toString()); - } - } else { - // For file output, use emojis - let statusEmoji = '⏱️'; // Default to pending - if (status === 'done' || status === 'completed') { - statusEmoji = '✅'; - } else if (status === 'in-progress') { - statusEmoji = '🔄'; - } - return `${depId} ${statusEmoji}`; - } - }).join(', '); -} - -// -// 5) list tasks -// -function listTasks(tasksPath, statusFilter, withSubtasks = false) { - displayBanner(); - - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', "No valid tasks found."); - process.exit(1); - } - - // Filter tasks by status if a filter is provided - const filteredTasks = statusFilter - ? data.tasks.filter(t => t.status === statusFilter) - : data.tasks; - - // Get terminal width for dynamic sizing - const terminalWidth = process.stdout.columns || 100; - - // Create a table for better visualization - const table = new Table({ - head: [ - chalk.cyan.bold('ID'), - chalk.cyan.bold('Status'), - chalk.cyan.bold('Priority'), - chalk.cyan.bold('Title'), - chalk.cyan.bold('Dependencies') - ], - colWidths: [ - 8, // ID - 12, // Status - 10, // Priority - Math.floor(terminalWidth * 0.45), // Title - 45% of terminal width - Math.floor(terminalWidth * 0.25) // Dependencies - 25% of terminal width - ], - style: { - head: [], - border: [] - }, - chars: { - 'top': '─', - 'top-mid': '┬', - 'top-left': '┌', - 'top-right': '┐', - 'bottom': '─', - 'bottom-mid': '┴', - 'bottom-left': '└', - 'bottom-right': '┘', - 'left': '│', - 'left-mid': '├', - 'mid': '─', - 'mid-mid': '┼', - 'right': '│', - 'right-mid': '┤', - 'middle': '│' - }, - wordWrap: true - }); - - // Status colors - const statusColors = { - 'done': chalk.green, - 'completed': chalk.green, - 'pending': chalk.yellow, - 'deferred': chalk.gray, - 'in-progress': chalk.blue, - 'blocked': chalk.red - }; - - // Priority colors - const priorityColors = { - 'high': chalk.red.bold, - 'medium': chalk.yellow, - 'low': chalk.gray - }; - - filteredTasks.forEach(t => { - const statusColor = statusColors[t.status] || chalk.white; - const priorityColor = priorityColors[t.priority] || chalk.white; - - // Format dependencies with status indicators for parent tasks - const formattedDeps = formatDependenciesWithStatus(t.dependencies, data.tasks, true); - - // Get the max title length for the title column with some margin for padding - const titleMaxLength = Math.floor(terminalWidth * 0.45) - 5; - - // Truncate long titles if necessary - const truncatedTitle = t.title.length > titleMaxLength - ? t.title.substring(0, titleMaxLength - 3) + '...' - : t.title; - - table.push([ - t.id.toString(), - statusColor(t.status), - priorityColor(t.priority || 'medium'), - truncatedTitle, - formattedDeps - ]); - - // Display subtasks if requested and they exist - if (withSubtasks && t.subtasks && t.subtasks.length > 0) { - t.subtasks.forEach(st => { - const subtaskStatusColor = statusColors[st.status || 'pending'] || chalk.white; - - // Format subtask dependencies with status indicators - let subtaskDeps = 'None'; - if (st.dependencies && st.dependencies.length > 0) { - // Convert numeric dependencies to proper format if they're likely subtask references - const formattedSubtaskDeps = st.dependencies.map(depId => { - if (typeof depId === 'number' && depId < 100) { - return `${t.id}.${depId}`; - } - return depId; - }); - - subtaskDeps = formatDependenciesWithStatus(formattedSubtaskDeps, data.tasks, true); - } - - // Truncate subtask titles - const subtaskTitleMaxLength = Math.floor(terminalWidth * 0.45) - 7; // Slightly shorter to account for the arrow prefix - const truncatedSubtaskTitle = st.title.length > subtaskTitleMaxLength - ? st.title.substring(0, subtaskTitleMaxLength - 3) + '...' - : st.title; - - table.push([ - ` ${t.id}.${st.id}`, - subtaskStatusColor(st.status || 'pending'), - '', - ` ↳ ${truncatedSubtaskTitle}`, - subtaskDeps - ]); - }); - } - }); - - if (filteredTasks.length === 0) { - console.log(boxen( - chalk.yellow(`No tasks found${statusFilter ? ` with status '${statusFilter}'` : ''}.`), - { padding: 1, borderColor: 'yellow', borderStyle: 'round' } - )); - } else { - // Display the header with task count and filter info - const header = statusFilter - ? `Tasks with status: ${chalk.bold(statusFilter)} (${filteredTasks.length} of ${data.tasks.length} total)` - : `All Tasks (${filteredTasks.length})`; - - console.log(boxen(chalk.white.bold(header), { - padding: { left: 2, right: 2, top: 0, bottom: 0 }, - margin: { top: 1, bottom: 1 }, - borderColor: 'blue', - borderStyle: 'round' - })); - - console.log(table.toString()); - - // Summary box - const doneCount = data.tasks.filter(t => t.status === 'done' || t.status === 'completed').length; - const pendingCount = data.tasks.filter(t => t.status === 'pending').length; - const otherCount = data.tasks.length - doneCount - pendingCount; - - const progressPercent = Math.round((doneCount / data.tasks.length) * 100); - const progressBar = createProgressBar(progressPercent, 30); - - console.log(boxen( - `${chalk.bold('Project Progress:')} ${progressBar} ${progressPercent}%\n` + - `${chalk.green.bold('Done:')} ${doneCount} ${chalk.yellow.bold('Pending:')} ${pendingCount} ${chalk.gray.bold('Other:')} ${otherCount}`, - { padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1 } } - )); - } -} - -// Helper function to create a progress bar -function createProgressBar(percent, length) { - const filledLength = Math.round(length * percent / 100); - const emptyLength = length - filledLength; - - const filled = '█'.repeat(filledLength); - const empty = '░'.repeat(emptyLength); - - return chalk.green(filled) + chalk.gray(empty); -} - -// -// 6) expand task with subtasks -// -/** - * Expand a task by generating subtasks - * @param {string} taskId - The ID of the task to expand - * @param {number} numSubtasks - The number of subtasks to generate - * @param {boolean} useResearch - Whether to use Perplexity for research-backed subtask generation - * @returns {Promise} - */ -async function expandTask(taskId, numSubtasks = CONFIG.defaultSubtasks, useResearch = false, additionalContext = '') { - try { - // Get the tasks - const tasksData = readJSON(path.join(process.cwd(), 'tasks', 'tasks.json')); - const task = tasksData.tasks.find(t => t.id === parseInt(taskId)); - - if (!task) { - console.error(chalk.red(`Task with ID ${taskId} not found.`)); - return; - } - - // Check if the task is already completed - if (task.status === 'completed' || task.status === 'done') { - console.log(chalk.yellow(`Task ${taskId} is already completed. Skipping expansion.`)); - return; - } - - // Check for complexity report - const complexityReport = readComplexityReport(); - let recommendedSubtasks = numSubtasks; - let recommendedPrompt = additionalContext; - - // If report exists and has data for this task, use it - if (complexityReport) { - const taskAnalysis = findTaskInComplexityReport(complexityReport, parseInt(taskId)); - if (taskAnalysis) { - // Only use report values if not explicitly overridden by command line - if (numSubtasks === CONFIG.defaultSubtasks && taskAnalysis.recommendedSubtasks) { - recommendedSubtasks = taskAnalysis.recommendedSubtasks; - console.log(chalk.blue(`Using recommended subtask count from complexity analysis: ${recommendedSubtasks}`)); - } - - if (!additionalContext && taskAnalysis.expansionPrompt) { - recommendedPrompt = taskAnalysis.expansionPrompt; - console.log(chalk.blue(`Using recommended prompt from complexity analysis`)); - console.log(chalk.gray(`Prompt: ${recommendedPrompt.substring(0, 100)}...`)); - } - } - } - - // Initialize subtasks array if it doesn't exist - if (!task.subtasks) { - task.subtasks = []; - } - - // Calculate the next subtask ID - const nextSubtaskId = task.subtasks.length > 0 - ? Math.max(...task.subtasks.map(st => st.id)) + 1 - : 1; - - // Generate subtasks - let subtasks; - if (useResearch) { - console.log(chalk.blue(`Using Perplexity AI for research-backed subtask generation...`)); - subtasks = await generateSubtasksWithPerplexity(task, recommendedSubtasks, nextSubtaskId, recommendedPrompt); - } else { - subtasks = await generateSubtasks(task, recommendedSubtasks, nextSubtaskId, recommendedPrompt); - } - - // Add the subtasks to the task - task.subtasks = [...task.subtasks, ...subtasks]; - - // Validate and fix dependencies for the newly generated subtasks - console.log(chalk.blue(`Validating dependencies for generated subtasks...`)); - const dependencyChanges = validateAndFixDependencies(tasksData, null); - if (dependencyChanges) { - console.log(chalk.yellow(`Fixed some invalid dependencies in the generated subtasks`)); - } else { - console.log(chalk.green(`All dependencies in generated subtasks are valid`)); - } - - // Ensure at least one subtask has no dependencies (entry point) - const hasIndependentSubtask = task.subtasks.some(st => - !st.dependencies || !Array.isArray(st.dependencies) || st.dependencies.length === 0 - ); - - if (!hasIndependentSubtask && subtasks.length > 0) { - console.log(chalk.yellow(`Ensuring at least one independent subtask in task ${taskId}`)); - const firstSubtask = subtasks[0]; - firstSubtask.dependencies = []; - } - - // Save the updated tasks - fs.writeFileSync( - path.join(process.cwd(), 'tasks', 'tasks.json'), - JSON.stringify(tasksData, null, 2) - ); - - console.log(chalk.green(`Added ${subtasks.length} subtasks to task ${taskId}.`)); - - // Log the added subtasks - subtasks.forEach(st => { - console.log(chalk.cyan(` ${st.id}. ${st.title}`)); - console.log(chalk.gray(` ${st.description.substring(0, 100)}${st.description.length > 100 ? '...' : ''}`)); - }); - - // Generate task files to update the task file with the new subtasks - console.log(chalk.blue(`Regenerating task files to include new subtasks...`)); - await generateTaskFiles('tasks/tasks.json', 'tasks'); - - } catch (error) { - console.error(chalk.red('Error expanding task:'), error); - } -} - -/** - * Expand all tasks that are not completed - * @param {number} numSubtasks - The number of subtasks to generate for each task - * @param {boolean} useResearch - Whether to use Perplexity for research-backed subtask generation - * @returns {Promise} - The number of tasks expanded - */ -async function expandAllTasks(numSubtasks = CONFIG.defaultSubtasks, useResearch = false, additionalContext = '', forceFlag = false) { - try { - // Get the tasks - const tasksData = readJSON(path.join(process.cwd(), 'tasks', 'tasks.json')); - - if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { - console.error(chalk.red('No valid tasks found.')); - return 0; - } - - // Filter tasks that are not completed - let tasksToExpand = tasksData.tasks.filter(task => - task.status !== 'completed' && task.status !== 'done' - ); - - if (tasksToExpand.length === 0) { - console.log(chalk.yellow('No tasks to expand. All tasks are already completed.')); - return 0; - } - - // Check for complexity report - const complexityReport = readComplexityReport(); - let usedComplexityReport = false; - - // If complexity report exists, sort tasks by complexity - if (complexityReport && complexityReport.complexityAnalysis) { - console.log(chalk.blue('Found complexity report. Prioritizing tasks by complexity score.')); - usedComplexityReport = true; - - // Create a map of task IDs to their complexity scores - const complexityMap = new Map(); - complexityReport.complexityAnalysis.forEach(analysis => { - complexityMap.set(analysis.taskId, analysis.complexityScore); - }); - - // Sort tasks by complexity score (highest first) - tasksToExpand.sort((a, b) => { - const scoreA = complexityMap.get(a.id) || 0; - const scoreB = complexityMap.get(b.id) || 0; - return scoreB - scoreA; - }); - - // Log the sorted tasks - console.log(chalk.blue('Tasks will be expanded in this order (by complexity):')); - tasksToExpand.forEach(task => { - const score = complexityMap.get(task.id) || 'N/A'; - console.log(chalk.blue(` Task ${task.id}: ${task.title} (Complexity: ${score})`)); - }); - } - - console.log(chalk.blue(`\nExpanding ${tasksToExpand.length} tasks...`)); - - let tasksExpanded = 0; - // Keep track of expanded tasks and their results for verification - const expandedTaskIds = []; - - // Expand each task - for (const task of tasksToExpand) { - console.log(chalk.blue(`\nExpanding task ${task.id}: ${task.title}`)); - - // The check for usedComplexityReport is redundant since expandTask will handle it anyway - await expandTask(task.id, numSubtasks, useResearch, additionalContext); - expandedTaskIds.push(task.id); - - tasksExpanded++; - } - - // Verification step - Check for dummy/generic subtasks - // Read fresh data after all expansions - const updatedTasksData = readJSON(path.join(process.cwd(), 'tasks', 'tasks.json')); - const tasksNeedingRetry = []; - - console.log(chalk.blue('\nVerifying subtask quality...')); - - for (const taskId of expandedTaskIds) { - const task = updatedTasksData.tasks.find(t => t.id === taskId); - if (!task || !task.subtasks || task.subtasks.length === 0) continue; - - // Check for generic subtasks - patterns to look for: - // 1. Auto-generated subtask in description - // 2. Generic titles like "Subtask X" - // 3. Common dummy titles we created like "Implementation" without custom descriptions - const dummySubtasks = task.subtasks.filter(st => - st.description.includes("Auto-generated subtask") || - st.title.match(/^Subtask \d+$/) || - (st.description.includes("Update this auto-generated subtask") && - ["Implementation", "Testing", "Documentation", "Integration", "Error Handling", - "Refactoring", "Validation", "Configuration"].includes(st.title)) - ); - - // If more than half the subtasks are generic, mark for retry - if (dummySubtasks.length > Math.floor(task.subtasks.length / 2)) { - tasksNeedingRetry.push({ - id: task.id, - title: task.title, - dummyCount: dummySubtasks.length, - totalCount: task.subtasks.length - }); - } - } - - // If we found tasks with poor subtask quality, offer to retry them - if (tasksNeedingRetry.length > 0) { - console.log(chalk.yellow(`\nFound ${tasksNeedingRetry.length} tasks with low-quality subtasks that need retry:`)); - - for (const task of tasksNeedingRetry) { - console.log(chalk.yellow(` Task ${task.id}: ${task.title} (${task.dummyCount}/${task.totalCount} generic subtasks)`)); - } - - // Ask user if they want to retry these tasks - const readline = require('readline').createInterface({ - input: process.stdin, - output: process.stdout - }); - - const answer = await new Promise(resolve => { - readline.question(chalk.cyan('\nWould you like to retry expanding these tasks with enhanced prompts? (y/n): '), resolve); - }); - readline.close(); - - if (answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes') { - console.log(chalk.blue('\nRetrying task expansion with enhanced prompts...')); - - // For each task needing retry, we'll expand it again with a modified prompt - let retryCount = 0; - for (const task of tasksNeedingRetry) { - console.log(chalk.blue(`\nRetrying expansion for task ${task.id}...`)); - - // Enhanced context to encourage better subtask generation - const enhancedContext = `${additionalContext ? additionalContext + "\n\n" : ""} -IMPORTANT: The previous expansion attempt generated mostly generic subtasks. Please provide highly specific, -detailed, and technically relevant subtasks for this task. Each subtask should be: -1. Specifically related to the task at hand, not generic -2. Technically detailed with clear implementation guidance -3. Unique and addressing a distinct aspect of the parent task - -Be creative and thorough in your analysis. The subtasks should collectively represent a complete solution to the parent task.`; - - // Try with different settings - if research was off, turn it on, or increase subtasks slightly - const retryResearch = useResearch ? true : !useResearch; // Try opposite of current setting, but prefer ON - const retrySubtasks = numSubtasks < 6 ? numSubtasks + 1 : numSubtasks; // Increase subtasks slightly if not already high - - // Delete existing subtasks for this task before regenerating - const currentTaskData = readJSON(path.join(process.cwd(), 'tasks', 'tasks.json')); - const taskToUpdate = currentTaskData.tasks.find(t => t.id === task.id); - if (taskToUpdate) { - taskToUpdate.subtasks = []; // Clear existing subtasks - // Save before expanding again - fs.writeFileSync( - path.join(process.cwd(), 'tasks', 'tasks.json'), - JSON.stringify(currentTaskData, null, 2) - ); - } - - // Try expansion again with enhanced context and possibly different settings - await expandTask(task.id, retrySubtasks, retryResearch, enhancedContext); - retryCount++; - } - - console.log(chalk.green(`\nCompleted retry expansion for ${retryCount} tasks.`)); - tasksExpanded += retryCount; // Add retries to total expanded count - } else { - console.log(chalk.blue('\nSkipping retry. You can manually retry task expansion using the expand command.')); - } - } else { - console.log(chalk.green('\nVerification complete. All subtasks appear to be of good quality.')); - } - - console.log(chalk.green(`\nExpanded ${tasksExpanded} tasks.`)); - return tasksExpanded; - } catch (error) { - console.error(chalk.red('Error expanding all tasks:'), error); - return 0; - } -} - -// -// Generate subtasks using Claude -// -async function generateSubtasks(task, numSubtasks, nextSubtaskId, additionalContext = '') { - log('info', `Generating ${numSubtasks} subtasks for task: ${task.title}`); - - const existingSubtasksText = task.subtasks && task.subtasks.length > 0 - ? `\nExisting subtasks:\n${task.subtasks.map(st => `${st.id}. ${st.title}: ${st.description}`).join('\n')}` - : ''; - - const prompt = ` -Task Title: ${task.title} -Task Description: ${task.description} -Task Details: ${task.details || ''} -${existingSubtasksText} -${additionalContext ? `\nAdditional Context: ${additionalContext}` : ''} - -Please generate ${numSubtasks} detailed subtasks for this task. Each subtask should be specific, actionable, and help accomplish the main task. The subtasks should cover different aspects of the main task and provide clear guidance on implementation. - -For each subtask, provide: -1. A concise title -2. A detailed description -3. Dependencies (if any) -4. Acceptance criteria - -Format each subtask as follows: - -Subtask ${nextSubtaskId}: [Title] -Description: [Detailed description] -Dependencies: [List any dependencies by ID, or "None" if there are no dependencies] -Acceptance Criteria: [List specific criteria that must be met for this subtask to be considered complete] - -Then continue with Subtask ${nextSubtaskId + 1}, and so on. -`; - - log('info', "Calling Claude to generate subtasks..."); - - // Start loading indicator - const loadingIndicator = startLoadingIndicator("Waiting for Claude to generate subtasks..."); - - let fullResponse = ''; - let streamingInterval = null; - - try { - const stream = await anthropic.messages.create({ - max_tokens: CONFIG.maxTokens, - model: CONFIG.model, - temperature: CONFIG.temperature, - messages: [ - { - role: "user", - content: prompt - } - ], - system: "You are a helpful assistant that generates detailed subtasks for software development tasks. Your subtasks should be specific, actionable, and help accomplish the main task. Format each subtask with a title, description, dependencies, and acceptance criteria.", - stream: true - }); - - // Update loading indicator to show streaming progress - let dotCount = 0; - streamingInterval = setInterval(() => { - readline.cursorTo(process.stdout, 0); - process.stdout.write(`Receiving streaming response from Claude${'.'.repeat(dotCount)}`); - dotCount = (dotCount + 1) % 4; - }, 500); - - // Process the stream - for await (const chunk of stream) { - if (chunk.type === 'content_block_delta' && chunk.delta.text) { - fullResponse += chunk.delta.text; - } - } - - clearInterval(streamingInterval); - - // Stop loading indicator - stopLoadingIndicator(loadingIndicator); - log('info', "Received complete response from Claude API!"); - - // Log the first part of the response for debugging - log('debug', "Response preview:", fullResponse.substring(0, 200) + "..."); - - // Parse the subtasks from the text response - const subtasks = parseSubtasksFromText(fullResponse, nextSubtaskId, numSubtasks, task.id); - - return subtasks; - } catch (error) { - if (streamingInterval) clearInterval(streamingInterval); - stopLoadingIndicator(loadingIndicator); - log('error', "Error during streaming response:", error); - throw error; - } -} - -// -// Parse subtasks from Claude's text response -// -function parseSubtasksFromText(text, startId, expectedCount, parentTaskId) { - log('info', "Parsing subtasks from Claude's response..."); - - const subtasks = []; - - // Enhanced regex that's more tolerant of variations in formatting - // This handles more cases like: subtask headings with or without numbers, different separators, etc. - const subtaskRegex = /(?:^|\n)\s*(?:(?:Subtask\s*(?:\d+)?[:.-]?\s*)|(?:\d+\.\s*))([^\n]+)(?:\n|$)(?:(?:\n|^)Description\s*[:.-]?\s*([^]*?))?(?:(?:\n|^)Dependencies\s*[:.-]?\s*([^]*?))?(?:(?:\n|^)Acceptance Criteria\s*[:.-]?\s*([^]*?))?(?=\n\s*(?:(?:Subtask\s*(?:\d+)?[:.-]?\s*)|(?:\d+\.\s*))|$)/gmi; - - let match; - while ((match = subtaskRegex.exec(text)) !== null) { - const [_, title, descriptionRaw, dependenciesRaw, acceptanceCriteriaRaw] = match; - - // Skip if we couldn't extract a meaningful title - if (!title || title.trim().length === 0) continue; - - // Clean up the description - if description is undefined, use the first paragraph of the section - let description = descriptionRaw ? descriptionRaw.trim() : ''; - if (!description) { - // Try to extract the first paragraph after the title as the description - const sectionText = text.substring(match.index + match[0].indexOf(title) + title.length); - const nextSection = sectionText.match(/\n\s*(?:Subtask|Dependencies|Acceptance Criteria)/i); - if (nextSection) { - description = sectionText.substring(0, nextSection.index).trim(); - } - } - - // Extract dependencies - let dependencies = []; - if (dependenciesRaw) { - const depText = dependenciesRaw.trim(); - if (depText && !/(none|n\/a|no dependencies)/i.test(depText)) { - // Extract numbers and subtask IDs (like 1.2) from dependencies text - const depMatches = depText.match(/\d+(?:\.\d+)?/g); - if (depMatches) { - dependencies = depMatches.map(dep => { - // Check if it's a subtask ID (contains a dot) - if (dep.includes('.')) { - return dep; // Keep as string for subtask IDs - } else { - // Try to parse as number - const numDep = parseInt(dep, 10); - // Small numbers (likely 1-9) are probably subtask IDs within the current task - // This is a heuristic - when Claude says "Depends on subtask 1", - // it likely means subtask 1 of the current task - if (numDep < 10) { - // This is likely a subtask number - leave as number for the generateTaskFiles function - // to format correctly with the parent task ID - return numDep; - } else { - // Larger numbers are probably full task IDs - return numDep; - } - } - }); - - // Filter out any potential self-dependencies - // The subtask ID is not yet fully formed at this point, but we can check if - // a string dependency matches the expected pattern of parent.currentSubtaskId - const currentSubtaskId = startId + subtasks.length; - dependencies = dependencies.filter(dep => { - // Handle string dependencies in format "parentId.subtaskId" - if (typeof dep === 'string' && dep.includes('.')) { - // Check if the dependency points to this subtask itself - if (dep === `${parentTaskId}.${currentSubtaskId}`) { - log('warn', `Removing self-dependency from subtask ${parentTaskId}.${currentSubtaskId}`); - return false; - } - } - // Handle numeric dependencies that could become self-dependencies - else if (typeof dep === 'number' && dep === currentSubtaskId) { - log('warn', `Removing self-dependency from subtask ${parentTaskId}.${currentSubtaskId}`); - return false; - } - return true; - }); - } - } - } - - // Log for debugging - log('debug', `Parsed dependencies: ${JSON.stringify(dependencies)}`); - - // Extract acceptance criteria - let acceptanceCriteria = ''; - if (acceptanceCriteriaRaw) { - acceptanceCriteria = acceptanceCriteriaRaw.trim(); - } else { - // Try to find acceptance criteria in the section if not explicitly labeled - const acMatch = match[0].match(/(?:criteria|must|should|requirements|tests)(?:\s*[:.-])?\s*([^]*?)(?=\n\s*(?:Subtask|Dependencies)|$)/i); - if (acMatch) { - acceptanceCriteria = acMatch[1].trim(); - } - } - - // Create the subtask object - const subtask = { - id: startId + subtasks.length, - title: title.trim(), - description: description || `Implement ${title.trim()}`, // Ensure we have at least a basic description - status: "pending", - dependencies: dependencies, - acceptanceCriteria: acceptanceCriteria - }; - - subtasks.push(subtask); - - // Break if we've found the expected number of subtasks - if (subtasks.length >= expectedCount) { - break; - } - } - - // If regex parsing failed or didn't find enough subtasks, try additional parsing methods - if (subtasks.length < expectedCount) { - log('info', `Regex parsing found only ${subtasks.length} subtasks, trying alternative parsing...`); - - // Look for numbered lists (1. Task title) - const numberedListRegex = /(?:^|\n)\s*(\d+)\.\s+([^\n]+)(?:\n|$)([^]*?)(?=(?:\n\s*\d+\.\s+)|$)/g; - while (subtasks.length < expectedCount && (match = numberedListRegex.exec(text)) !== null) { - const [_, num, title, detailsRaw] = match; - - // Skip if we've already captured this (might be duplicated by the first regex) - if (subtasks.some(st => st.title.trim() === title.trim())) { - continue; - } - - const details = detailsRaw ? detailsRaw.trim() : ''; - - // Create the subtask - const subtask = { - id: startId + subtasks.length, - title: title.trim(), - description: details || `Implement ${title.trim()}`, - status: "pending", - dependencies: [], - acceptanceCriteria: '' - }; - - subtasks.push(subtask); - } - - // Look for bulleted lists (- Task title or * Task title) - const bulletedListRegex = /(?:^|\n)\s*[-*]\s+([^\n]+)(?:\n|$)([^]*?)(?=(?:\n\s*[-*]\s+)|$)/g; - while (subtasks.length < expectedCount && (match = bulletedListRegex.exec(text)) !== null) { - const [_, title, detailsRaw] = match; - - // Skip if we've already captured this - if (subtasks.some(st => st.title.trim() === title.trim())) { - continue; - } - - const details = detailsRaw ? detailsRaw.trim() : ''; - - // Create the subtask - const subtask = { - id: startId + subtasks.length, - title: title.trim(), - description: details || `Implement ${title.trim()}`, - status: "pending", - dependencies: [], - acceptanceCriteria: '' - }; - - subtasks.push(subtask); - } - - // As a last resort, look for potential titles using heuristics (e.g., sentences followed by paragraphs) - if (subtasks.length < expectedCount) { - const lines = text.split('\n').filter(line => line.trim().length > 0); - - for (let i = 0; i < lines.length && subtasks.length < expectedCount; i++) { - const line = lines[i].trim(); - - // Skip if the line is too long to be a title, or contains typical non-title content - if (line.length > 100 || /^(Description|Dependencies|Acceptance Criteria|Implementation|Details|Approach):/i.test(line)) { - continue; - } - - // Skip if it matches a pattern we already tried to parse - if (/^(Subtask|Task|\d+\.|-|\*)/.test(line)) { - continue; - } - - // Skip if we've already captured this title - if (subtasks.some(st => st.title.trim() === line.trim())) { - continue; - } - - // Get the next few lines as potential description - let description = ''; - if (i + 1 < lines.length) { - description = lines.slice(i + 1, i + 4).join('\n').trim(); - } - - // Create the subtask - const subtask = { - id: startId + subtasks.length, - title: line, - description: description || `Implement ${line}`, - status: "pending", - dependencies: [], - acceptanceCriteria: '' - }; - - subtasks.push(subtask); - } - } - } - - // If we still don't have enough subtasks, create more meaningful dummy ones based on context - if (subtasks.length < expectedCount) { - log('info', `Parsing found only ${subtasks.length} subtasks, creating intelligent dummy ones to reach ${expectedCount}...`); - - // Create a list of common subtask patterns to use - const dummyTaskPatterns = [ - { title: "Implementation", description: "Implement the core functionality required for this task." }, - { title: "Testing", description: "Create comprehensive tests for the implemented functionality." }, - { title: "Documentation", description: "Document the implemented functionality and usage examples." }, - { title: "Integration", description: "Integrate the functionality with other components of the system." }, - { title: "Error Handling", description: "Implement robust error handling and edge case management." }, - { title: "Refactoring", description: "Refine and optimize the implementation for better performance and maintainability." }, - { title: "Validation", description: "Implement validation logic to ensure data integrity and security." }, - { title: "Configuration", description: "Create configuration options and settings for the functionality." } - ]; - - for (let i = subtasks.length; i < expectedCount; i++) { - // Select a pattern based on the current subtask number - const patternIndex = i % dummyTaskPatterns.length; - const pattern = dummyTaskPatterns[patternIndex]; - - subtasks.push({ - id: startId + i, - title: pattern.title, - description: pattern.description, - status: "pending", - dependencies: [], - acceptanceCriteria: 'Update this auto-generated subtask with specific details relevant to the parent task.' - }); - } - } - - log('info', `Successfully parsed ${subtasks.length} subtasks.`); - return subtasks; -} - -/** - * Generate subtasks for a task using Perplexity AI with research capabilities - * @param {Object} task - The task to generate subtasks for - * @param {number} numSubtasks - The number of subtasks to generate - * @param {number} nextSubtaskId - The ID to start assigning to subtasks - * @returns {Promise} - The generated subtasks - */ -async function generateSubtasksWithPerplexity(task, numSubtasks = 3, nextSubtaskId = 1, additionalContext = '') { - const { title, description, details = '', subtasks = [] } = task; - - console.log(chalk.blue(`Generating ${numSubtasks} subtasks for task: ${title}`)); - if (subtasks.length > 0) { - console.log(chalk.yellow(`Task already has ${subtasks.length} subtasks. Adding ${numSubtasks} more.`)); - } - - // Get the tasks.json content for context - let tasksData = {}; - try { - tasksData = readJSON(path.join(process.cwd(), 'tasks', 'tasks.json')); - } catch (error) { - console.log(chalk.yellow('Could not read tasks.json for context. Proceeding without it.')); - } - - // Get the PRD content for context if available - let prdContent = ''; - if (tasksData.meta && tasksData.meta.source) { - try { - prdContent = fs.readFileSync(path.join(process.cwd(), tasksData.meta.source), 'utf8'); - console.log(chalk.green(`Successfully loaded PRD from ${tasksData.meta.source} (${prdContent.length} characters)`)); - } catch (error) { - console.log(chalk.yellow(`Could not read PRD at ${tasksData.meta.source}. Proceeding without it.`)); - } - } - - // Get the specific task file for more detailed context if available - let taskFileContent = ''; - try { - const taskFileName = `task_${String(task.id).padStart(3, '0')}.txt`; - const taskFilePath = path.join(process.cwd(), 'tasks', taskFileName); - if (fs.existsSync(taskFilePath)) { - taskFileContent = fs.readFileSync(taskFilePath, 'utf8'); - console.log(chalk.green(`Successfully loaded task file ${taskFileName} for additional context`)); - } - } catch (error) { - console.log(chalk.yellow(`Could not read task file for task ${task.id}. Proceeding without it.`)); - } - - // Get dependency task details for better context - let dependencyDetails = ''; - if (task.dependencies && task.dependencies.length > 0) { - dependencyDetails = 'Dependency Tasks:\n'; - for (const depId of task.dependencies) { - const depTask = tasksData.tasks.find(t => t.id === depId); - if (depTask) { - dependencyDetails += `Task ${depId}: ${depTask.title}\n`; - dependencyDetails += `Description: ${depTask.description}\n`; - if (depTask.details) { - dependencyDetails += `Details: ${depTask.details.substring(0, 200)}${depTask.details.length > 200 ? '...' : ''}\n`; - } - dependencyDetails += '\n'; - } - } - } - - // Extract project metadata for context - const projectContext = tasksData.meta ? - `Project: ${tasksData.meta.projectName || 'Unknown'} -Version: ${tasksData.meta.version || '1.0.0'} -Description: ${tasksData.meta.description || 'No description available'}` : ''; - - // Construct the prompt for Perplexity/Anthropic with enhanced context - const prompt = `I need to break down the following task into ${numSubtasks} detailed subtasks for a software development project. - -${projectContext} - -CURRENT TASK: -Task ID: ${task.id} -Task Title: ${title} -Task Description: ${description} -Priority: ${task.priority || 'medium'} -Additional Details: ${details} -${additionalContext ? `\nADDITIONAL CONTEXT PROVIDED BY USER:\n${additionalContext}` : ''} - -${taskFileContent ? `DETAILED TASK INFORMATION: -${taskFileContent}` : ''} - -${dependencyDetails ? dependencyDetails : ''} - -${subtasks.length > 0 ? `Existing Subtasks: -${subtasks.map(st => `- ${st.title}: ${st.description}`).join('\n')}` : ''} - -${prdContent ? `PRODUCT REQUIREMENTS DOCUMENT: -${prdContent}` : ''} - -${tasksData.tasks ? `PROJECT CONTEXT - OTHER RELATED TASKS: -${JSON.stringify( - tasksData.tasks - .filter(t => t.id !== task.id) - // Prioritize tasks that are dependencies or depend on this task - .sort((a, b) => { - const aIsRelated = task.dependencies?.includes(a.id) || a.dependencies?.includes(task.id); - const bIsRelated = task.dependencies?.includes(b.id) || b.dependencies?.includes(task.id); - return bIsRelated - aIsRelated; - }) - .slice(0, 5) // Limit to 5 most relevant tasks to avoid context overload - .map(t => ({ - id: t.id, - title: t.title, - description: t.description, - status: t.status, - dependencies: t.dependencies - })), - null, 2 -)}` : ''} - -Please generate ${numSubtasks} subtasks that are: -1. Specific and actionable -2. Relevant to the current technology stack and project requirements -3. Properly sequenced with clear dependencies -4. Detailed enough to be implemented without further clarification - -For each subtask, provide: -1. A clear, concise title -2. A detailed description explaining what needs to be done -3. Dependencies (if any) - list the IDs of tasks this subtask depends on -4. Acceptance criteria - specific conditions that must be met for the subtask to be considered complete - -Format each subtask as follows: - -Subtask 1: [Title] -Description: [Detailed description] -Dependencies: [List of task IDs, or "None" if no dependencies] -Acceptance Criteria: [List of criteria] - -Subtask 2: [Title] -... - -Research the task thoroughly and ensure the subtasks are comprehensive, specific, and actionable. Focus on technical implementation details rather than generic steps.`; - - // Define the research prompt for Perplexity - const researchPrompt = `You are a software development expert tasked with breaking down complex development tasks into detailed subtasks. Please research the following task thoroughly, and generate ${numSubtasks} specific, actionable subtasks. - -${prompt} - -Format your response as specific, well-defined subtasks that could be assigned to developers. Be precise and technical in your descriptions.`; - - // Start loading indicator - const loadingInterval = startLoadingIndicator('Researching and generating subtasks with AI'); - - try { - let responseText; - - try { - // Try to use Perplexity first - console.log(chalk.blue('Using Perplexity AI for research-backed subtask generation...')); - const result = await getPerplexityClient().chat.completions.create({ - model: PERPLEXITY_MODEL, - messages: [ - { - role: "system", - content: "You are a technical analysis AI that helps break down software development tasks into detailed subtasks. Provide specific, actionable steps with clear definitions." - }, - { - role: "user", - content: researchPrompt - } - ], - temperature: TEMPERATURE, - max_tokens: MAX_TOKENS, - }); - - // Extract the response text - responseText = result.choices[0].message.content; - console.log(chalk.green('Successfully generated subtasks with Perplexity AI')); - } catch (perplexityError) { - console.log(chalk.yellow('Falling back to Anthropic for subtask generation...')); - console.log(chalk.gray('Perplexity error:'), perplexityError.message); - - // Use Anthropic as fallback - const stream = await anthropic.messages.create({ - model: MODEL, - max_tokens: MAX_TOKENS, - temperature: TEMPERATURE, - system: "You are an expert software developer and project manager. Your task is to break down software development tasks into detailed subtasks that are specific, actionable, and technically relevant.", - messages: [ - { - role: "user", - content: prompt - } - ], - stream: true - }); - - // Process the stream - responseText = ''; - for await (const chunk of stream) { - if (chunk.type === 'content_block_delta' && chunk.delta.text) { - responseText += chunk.delta.text; - } - } - - console.log(chalk.green('Successfully generated subtasks with Anthropic AI')); - } - - // Stop loading indicator - stopLoadingIndicator(loadingInterval); - - if (CONFIG.debug) { - console.log(chalk.gray('AI Response:')); - console.log(chalk.gray(responseText)); - } - - // Parse the subtasks from the response text - const subtasks = parseSubtasksFromText(responseText, nextSubtaskId, numSubtasks, task.id); - return subtasks; - } catch (error) { - stopLoadingIndicator(loadingInterval); - console.error(chalk.red('Error generating subtasks:'), error); - throw error; - } -} - -// ------------------------------------------ -// Main CLI -// ------------------------------------------ -async function main() { - // Add custom help - program.on('--help', function() { - displayHelp(); - }); - - if (process.argv.length <= 2) { - displayHelp(); - process.exit(0); - } - - program - .name('dev') - .description('AI-driven development task management') - .version(() => { - // Read version directly from package.json - try { - const packageJsonPath = path.join(__dirname, '..', 'package.json'); - if (fs.existsSync(packageJsonPath)) { - const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8')); - return packageJson.version; - } - } catch (error) { - // Silently fall back to default version - } - return "1.5.0"; // Default fallback - }); - - program - .command('parse-prd') - .description('Parse a PRD file and generate tasks') - .argument('', 'Path to the PRD file') - .option('-o, --output ', 'Output file path', 'tasks/tasks.json') - .option('-n, --num-tasks ', 'Number of tasks to generate', '10') - .action(async (file, options) => { - const numTasks = parseInt(options.numTasks, 10); - const outputPath = options.output; - - console.log(chalk.blue(`Parsing PRD file: ${file}`)); - console.log(chalk.blue(`Generating ${numTasks} tasks...`)); - - await parsePRD(file, outputPath, numTasks); - }); - - program - .command('update') - .description('Update tasks based on new information or implementation changes') - .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') - .option('--from ', 'Task ID to start updating from (tasks with ID >= this value will be updated)', '1') - .option('-p, --prompt ', 'Prompt explaining the changes or new context (required)') - .action(async (options) => { - const tasksPath = options.file; - const fromId = parseInt(options.from, 10); - const prompt = options.prompt; - - if (!prompt) { - console.error(chalk.red('Error: --prompt parameter is required. Please provide information about the changes.')); - process.exit(1); - } - - console.log(chalk.blue(`Updating tasks from ID >= ${fromId} with prompt: "${prompt}"`)); - console.log(chalk.blue(`Tasks file: ${tasksPath}`)); - - await updateTasks(tasksPath, fromId, prompt); - }); - - program - .command('generate') - .description('Generate task files from tasks.json') - .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') - .option('-o, --output ', 'Output directory', 'tasks') - .action(async (options) => { - const tasksPath = options.file; - const outputDir = options.output; - - console.log(chalk.blue(`Generating task files from: ${tasksPath}`)); - console.log(chalk.blue(`Output directory: ${outputDir}`)); - - await generateTaskFiles(tasksPath, outputDir); - }); - - program - .command('set-status') - .description('Set the status of a task') - .option('-i, --id ', 'Task ID (can be comma-separated for multiple tasks)') - .option('-s, --status ', 'New status (todo, in-progress, review, done)') - .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') - .action(async (options) => { - const tasksPath = options.file; - const taskId = options.id; - const status = options.status; - - if (!taskId || !status) { - console.error(chalk.red('Error: Both --id and --status are required')); - process.exit(1); - } - - console.log(chalk.blue(`Setting status of task(s) ${taskId} to: ${status}`)); - - await setTaskStatus(tasksPath, taskId, status); - }); - - program - .command('list') - .description('List all tasks') - .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') - .option('-s, --status ', 'Filter by status') - .option('--with-subtasks', 'Show subtasks for each task') - .action(async (options) => { - const tasksPath = options.file; - const statusFilter = options.status; - const withSubtasks = options.withSubtasks || false; - - console.log(chalk.blue(`Listing tasks from: ${tasksPath}`)); - if (statusFilter) { - console.log(chalk.blue(`Filtering by status: ${statusFilter}`)); - } - if (withSubtasks) { - console.log(chalk.blue('Including subtasks in listing')); - } - - await listTasks(tasksPath, statusFilter, withSubtasks); - }); - - program - .command('expand') - .description('Expand tasks with subtasks') - .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') - .option('-i, --id ', 'Task ID to expand') - .option('-a, --all', 'Expand all tasks') - .option('-n, --num ', 'Number of subtasks to generate', CONFIG.defaultSubtasks.toString()) - .option('-r, --no-research', 'Disable Perplexity AI for research-backed subtask generation') - .option('-p, --prompt ', 'Additional context to guide subtask generation') - .option('--force', 'Force regeneration of subtasks for tasks that already have them') - .action(async (options) => { - const tasksPath = options.file; - const idArg = options.id ? parseInt(options.id, 10) : null; - const allFlag = options.all; - const numSubtasks = parseInt(options.num, 10); - const forceFlag = options.force; - // Fix: The issue is here - research should be false when --no-research is specified - // This will correctly check if research is false - const useResearch = options.research === true; - // Debug log to verify the value - console.log(`Debug - options.research value: ${options.research}`); - const additionalContext = options.prompt || ''; - - if (allFlag) { - console.log(chalk.blue(`Expanding all tasks with ${numSubtasks} subtasks each...`)); - if (useResearch) { - console.log(chalk.blue('Using Perplexity AI for research-backed subtask generation')); - } else { - console.log(chalk.yellow('Research-backed subtask generation disabled')); - } - if (additionalContext) { - console.log(chalk.blue(`Additional context: "${additionalContext}"`)); - } - await expandAllTasks(numSubtasks, useResearch, additionalContext, forceFlag); - } else if (idArg) { - console.log(chalk.blue(`Expanding task ${idArg} with ${numSubtasks} subtasks...`)); - if (useResearch) { - console.log(chalk.blue('Using Perplexity AI for research-backed subtask generation')); - } else { - console.log(chalk.yellow('Research-backed subtask generation disabled')); - } - if (additionalContext) { - console.log(chalk.blue(`Additional context: "${additionalContext}"`)); - } - await expandTask(idArg, numSubtasks, useResearch, additionalContext); - } else { - console.error(chalk.red('Error: Please specify a task ID with --id= or use --all to expand all tasks.')); - } - }); - - program - .command('analyze-complexity') - .description('Analyze tasks and generate complexity-based expansion recommendations') - .option('-o, --output ', 'Output file path for the report', 'scripts/task-complexity-report.json') - .option('-m, --model ', 'LLM model to use for analysis (defaults to configured model)') - .option('-t, --threshold ', 'Minimum complexity score to recommend expansion (1-10)', '5') - .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') - .option('-r, --research', 'Use Perplexity AI for research-backed complexity analysis') - .action(async (options) => { - const tasksPath = options.file || 'tasks/tasks.json'; - const outputPath = options.output; - const modelOverride = options.model; - const thresholdScore = parseFloat(options.threshold); - const useResearch = options.research || false; - - console.log(chalk.blue(`Analyzing task complexity from: ${tasksPath}`)); - console.log(chalk.blue(`Output report will be saved to: ${outputPath}`)); - - if (useResearch) { - console.log(chalk.blue('Using Perplexity AI for research-backed complexity analysis')); - } - - await analyzeTaskComplexity(options); - }); - - program - .command('clear-subtasks') - .description('Clear subtasks from specified tasks') - .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') - .option('-i, --id ', 'Task IDs (comma-separated) to clear subtasks from') - .option('--all', 'Clear subtasks from all tasks') - .action(async (options) => { - const tasksPath = options.file; - const taskIds = options.id; - const all = options.all; - - if (!taskIds && !all) { - console.error(chalk.red('Error: Please specify task IDs with --id= or use --all to clear all tasks')); - process.exit(1); - } - - if (all) { - // If --all is specified, get all task IDs - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - console.error(chalk.red('Error: No valid tasks found')); - process.exit(1); - } - const allIds = data.tasks.map(t => t.id).join(','); - clearSubtasks(tasksPath, allIds); - } else { - clearSubtasks(tasksPath, taskIds); - } - }); - - program - .command('add-task') - .description('Add a new task to tasks.json using AI') - .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') - .option('-p, --prompt ', 'Description of the task to add (required)') - .option('-d, --dependencies ', 'Comma-separated list of task IDs this task depends on') - .option('--priority ', 'Task priority (high, medium, low)', 'medium') - .action(async (options) => { - const tasksPath = options.file; - const prompt = options.prompt; - const dependencies = options.dependencies ? options.dependencies.split(',').map(id => parseInt(id.trim(), 10)) : []; - const priority = options.priority; - - if (!prompt) { - console.error(chalk.red('Error: --prompt parameter is required. Please provide a description of the task.')); - process.exit(1); - } - - console.log(chalk.blue(`Adding new task with prompt: "${prompt}"`)); - console.log(chalk.blue(`Tasks file: ${tasksPath}`)); - - await addTask(tasksPath, prompt, dependencies, priority); - }); - - program - .command('next') - .description('Show the next task to work on based on dependencies and status') - .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') - .action(async (options) => { - const tasksPath = options.file; - await displayNextTask(tasksPath); - }); - - program - .command('show') - .description('Show details of a specific task by ID') - .argument('[id]', 'Task ID to show') - .option('-i, --id ', 'Task ID to show (alternative to argument)') - .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') - .action(async (id, options) => { - const taskId = id || options.id; - - if (!taskId) { - console.error(chalk.red('Error: Task ID is required. Provide it as an argument or with --id option.')); - console.error(chalk.yellow('Examples:')); - console.error(chalk.yellow(' node scripts/dev.js show 1')); - console.error(chalk.yellow(' node scripts/dev.js show --id=1')); - process.exit(1); - } - - const tasksPath = options.file; - await displayTaskById(tasksPath, taskId); - }); - - program - .command('add-dependency') - .description('Add a dependency to a task') - .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') - .option('-i, --id ', 'ID of the task to add dependency to') - .option('-d, --depends-on ', 'ID of the task to add as dependency') - .action(async (options) => { - const tasksPath = options.file; - const taskId = options.id; - const dependencyId = options.dependsOn; - - if (!taskId || !dependencyId) { - console.error(chalk.red('Error: Both --id and --depends-on parameters are required.')); - console.error(chalk.yellow('Example:')); - console.error(chalk.yellow(' node scripts/dev.js add-dependency --id=22 --depends-on=21')); - process.exit(1); - } - - await addDependency(tasksPath, taskId, dependencyId); - }); - - program - .command('remove-dependency') - .description('Remove a dependency from a task') - .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') - .option('-i, --id ', 'ID of the task to remove dependency from') - .option('-d, --depends-on ', 'ID of the task to remove as dependency') - .action(async (options) => { - const tasksPath = options.file; - const taskId = options.id; - const dependencyId = options.dependsOn; - - if (!taskId || !dependencyId) { - console.error(chalk.red('Error: Both --id and --depends-on parameters are required.')); - console.error(chalk.yellow('Example:')); - console.error(chalk.yellow(' node scripts/dev.js remove-dependency --id=22 --depends-on=21')); - process.exit(1); - } - - await removeDependency(tasksPath, taskId, dependencyId); - }); - - program - .command('validate-dependencies') - .description('Check for and remove invalid dependencies from tasks') - .option('-f, --file ', 'Path to the tasks.json file', 'tasks/tasks.json') - .action(async (options) => { - try { - await validateDependenciesCommand(options.file); - } catch (error) { - log('error', "Error in validate-dependencies command:", error); - process.exit(1); - } - }); - - program - .command('fix-dependencies') - .description('Find and fix all invalid dependencies in tasks.json and task files') - .option('-f, --file ', 'Path to the tasks.json file', 'tasks/tasks.json') - .action(async (options) => { - try { - await fixDependenciesCommand(options.file); - } catch (error) { - log('error', "Error in fix-dependencies command:", error); - process.exit(1); - } - }); - - program - .command('*') - .description('Handle unknown commands') - .action(async (command) => { - console.error(chalk.red(`Unknown command: ${command}`)); - displayHelp(); - process.exit(1); - }); - - await program.parseAsync(process.argv); -} - -/** - * Generates the prompt for the LLM to analyze task complexity - * @param {Object} tasksData The tasks data from tasks.json - * @returns {string} The prompt for the LLM - */ -function generateComplexityAnalysisPrompt(tasksData) { - return ` -You are an expert software architect and project manager. Your task is to analyze the complexity of development tasks and determine how many subtasks each should be broken down into. - -Below is a list of development tasks with their descriptions and details. For each task: -1. Assess its complexity on a scale of 1-10 -2. Recommend the optimal number of subtasks (between ${Math.max(3, CONFIG.defaultSubtasks - 1)}-${Math.min(8, CONFIG.defaultSubtasks + 2)}) -3. Suggest a specific prompt that would help generate good subtasks for this task -4. Explain your reasoning briefly - -Tasks: -${tasksData.tasks.map(task => ` -ID: ${task.id} -Title: ${task.title} -Description: ${task.description} -Details: ${task.details} -Dependencies: ${JSON.stringify(task.dependencies || [])} -Priority: ${task.priority || 'medium'} -`).join('\n---\n')} - -Analyze each task and return a JSON array with the following structure for each task: -[ - { - "taskId": number, - "taskTitle": string, - "complexityScore": number (1-10), - "recommendedSubtasks": number (${Math.max(3, CONFIG.defaultSubtasks - 1)}-${Math.min(8, CONFIG.defaultSubtasks + 2)}), - "expansionPrompt": string (a specific prompt for generating good subtasks), - "reasoning": string (brief explanation of your assessment) - }, - ... -] - -IMPORTANT: Make sure to include an analysis for EVERY task listed above, with the correct taskId matching each task's ID. -`; -} - -/** - * Sanitizes a prompt string for use in a shell command - * @param {string} prompt The prompt to sanitize - * @returns {string} Sanitized prompt - */ -function sanitizePrompt(prompt) { - // Replace double quotes with escaped double quotes - return prompt.replace(/"/g, '\\"'); -} - -/** - * Reads and parses the complexity report if it exists - * @param {string} customPath - Optional custom path to the report - * @returns {Object|null} The parsed complexity report or null if not found - */ -function readComplexityReport(customPath = null) { - try { - const reportPath = customPath || path.join(process.cwd(), 'scripts', 'task-complexity-report.json'); - if (!fs.existsSync(reportPath)) { - return null; - } - - const reportData = fs.readFileSync(reportPath, 'utf8'); - return JSON.parse(reportData); - } catch (error) { - console.log(chalk.yellow(`Could not read complexity report: ${error.message}`)); - return null; - } -} - -/** - * Finds a task analysis in the complexity report - * @param {Object} report - The complexity report - * @param {number} taskId - The task ID to find - * @returns {Object|null} The task analysis or null if not found - */ -function findTaskInComplexityReport(report, taskId) { - if (!report || !report.complexityAnalysis || !Array.isArray(report.complexityAnalysis)) { - return null; - } - - return report.complexityAnalysis.find(task => task.taskId === taskId); -} - -// -// Clear subtasks from tasks -// -function clearSubtasks(tasksPath, taskIds) { - displayBanner(); - - log('info', `Reading tasks from ${tasksPath}...`); - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', "No valid tasks found."); - process.exit(1); - } - - console.log(boxen( - chalk.white.bold('Clearing Subtasks'), - { padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } } - )); - - // Handle multiple task IDs (comma-separated) - const taskIdArray = taskIds.split(',').map(id => id.trim()); - let clearedCount = 0; - - // Create a summary table for the cleared subtasks - const summaryTable = new Table({ - head: [ - chalk.cyan.bold('Task ID'), - chalk.cyan.bold('Task Title'), - chalk.cyan.bold('Subtasks Cleared') - ], - colWidths: [10, 50, 20], - style: { head: [], border: [] } - }); - - taskIdArray.forEach(taskId => { - const id = parseInt(taskId, 10); - if (isNaN(id)) { - log('error', `Invalid task ID: ${taskId}`); - return; - } - - const task = data.tasks.find(t => t.id === id); - if (!task) { - log('error', `Task ${id} not found`); - return; - } - - if (!task.subtasks || task.subtasks.length === 0) { - log('info', `Task ${id} has no subtasks to clear`); - summaryTable.push([ - id.toString(), - truncate(task.title, 47), - chalk.yellow('No subtasks') - ]); - return; - } - - const subtaskCount = task.subtasks.length; - task.subtasks = []; - clearedCount++; - log('info', `Cleared ${subtaskCount} subtasks from task ${id}`); - - summaryTable.push([ - id.toString(), - truncate(task.title, 47), - chalk.green(`${subtaskCount} subtasks cleared`) - ]); - }); - - if (clearedCount > 0) { - writeJSON(tasksPath, data); - - // Show summary table - console.log(boxen( - chalk.white.bold('Subtask Clearing Summary:'), - { padding: { left: 2, right: 2, top: 0, bottom: 0 }, margin: { top: 1, bottom: 0 }, borderColor: 'blue', borderStyle: 'round' } - )); - console.log(summaryTable.toString()); - - // Regenerate task files to reflect changes - log('info', "Regenerating task files..."); - generateTaskFiles(tasksPath, path.dirname(tasksPath)); - - // Success message - console.log(boxen( - chalk.green(`Successfully cleared subtasks from ${chalk.bold(clearedCount)} task(s)`), - { padding: 1, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } } - )); - - // Next steps suggestion - console.log(boxen( - chalk.white.bold('Next Steps:') + '\n\n' + - `${chalk.cyan('1.')} Run ${chalk.yellow('node scripts/dev.js expand --id=')} to generate new subtasks\n` + - `${chalk.cyan('2.')} Run ${chalk.yellow('node scripts/dev.js list --with-subtasks')} to verify changes`, - { padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1 } } - )); - - } else { - console.log(boxen( - chalk.yellow('No subtasks were cleared'), - { padding: 1, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } } - )); - } -} - -// ---------------------------------------- -// Custom help display -// ---------------------------------------- -function displayHelp() { - displayBanner(); - - console.log(boxen( - chalk.white.bold('Claude Task Master CLI'), - { padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } } - )); - - // Command categories - const commandCategories = [ - { - title: 'Task Generation', - color: 'cyan', - commands: [ - { name: 'parse-prd', args: '--input= [--tasks=10]', - desc: 'Generate tasks from a PRD document' }, - { name: 'generate', args: '', - desc: 'Create individual task files from tasks.json' } - ] - }, - { - title: 'Task Management', - color: 'green', - commands: [ - { name: 'list', args: '[--status=] [--with-subtasks]', - desc: 'List all tasks with their status' }, - { name: 'set-status', args: '--id= --status=', - desc: 'Update task status (done, pending, etc.)' }, - { name: 'update', args: '--from= --prompt=""', - desc: 'Update tasks based on new requirements' }, - { name: 'add-dependency', args: '--id= --depends-on=', - desc: 'Add a dependency to a task' }, - { name: 'remove-dependency', args: '--id= --depends-on=', - desc: 'Remove a dependency from a task' } - ] - }, - { - title: 'Task Analysis & Detail', - color: 'yellow', - commands: [ - { name: 'analyze-complexity', args: '[--research] [--threshold=5]', - desc: 'Analyze tasks and generate expansion recommendations' }, - { name: 'expand', args: '--id= [--num=5] [--research] [--prompt=""]', - desc: 'Break down tasks into detailed subtasks' }, - { name: 'expand --all', args: '[--force] [--research]', - desc: 'Expand all pending tasks with subtasks' }, - { name: 'clear-subtasks', args: '--id=', - desc: 'Remove subtasks from specified tasks' } - ] - } - ]; - - // Display each category - commandCategories.forEach(category => { - console.log(boxen( - chalk[category.color].bold(category.title), - { - padding: { left: 2, right: 2, top: 0, bottom: 0 }, - margin: { top: 1, bottom: 0 }, - borderColor: category.color, - borderStyle: 'round' - } - )); - - const commandTable = new Table({ - colWidths: [20, 40, 50], - chars: { - 'top': '', 'top-mid': '', 'top-left': '', 'top-right': '', - 'bottom': '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '', - 'left': '', 'left-mid': '', 'mid': '', 'mid-mid': '', - 'right': '', 'right-mid': '', 'middle': ' ' - }, - style: { border: [], 'padding-left': 4 } - }); - - category.commands.forEach(cmd => { - commandTable.push([ - chalk.bold(cmd.name), - chalk.blue(cmd.args), - cmd.desc - ]); - }); - - console.log(commandTable.toString()); - }); - - // Environment variables section - console.log(boxen( - chalk.magenta.bold('Environment Variables'), - { - padding: { left: 2, right: 2, top: 0, bottom: 0 }, - margin: { top: 1, bottom: 0 }, - borderColor: 'magenta', - borderStyle: 'round' - } - )); - - const envTable = new Table({ - colWidths: [25, 20, 65], - chars: { - 'top': '', 'top-mid': '', 'top-left': '', 'top-right': '', - 'bottom': '', 'bottom-mid': '', 'bottom-left': '', 'bottom-right': '', - 'left': '', 'left-mid': '', 'mid': '', 'mid-mid': '', - 'right': '', 'right-mid': '', 'middle': ' ' - }, - style: { border: [], 'padding-left': 4 } - }); - - envTable.push( - [chalk.bold('ANTHROPIC_API_KEY'), chalk.red('Required'), 'Your Anthropic API key for Claude'], - [chalk.bold('MODEL'), chalk.gray('Optional'), `Claude model to use (default: ${MODEL})`], - [chalk.bold('PERPLEXITY_API_KEY'), chalk.gray('Optional'), 'API key for research-backed features'], - [chalk.bold('PROJECT_NAME'), chalk.gray('Optional'), `Project name in metadata (default: ${CONFIG.projectName})`] - ); - - console.log(envTable.toString()); - - // Example usage section - console.log(boxen( - chalk.white.bold('Example Workflow'), - { - padding: 1, - margin: { top: 1, bottom: 1 }, - borderColor: 'white', - borderStyle: 'round' - } - )); - - console.log(chalk.cyan(' 1. Generate tasks:')); - console.log(` ${chalk.yellow('node scripts/dev.js parse-prd --input=prd.txt')}`); - console.log(chalk.cyan(' 2. Generate task files:')); - console.log(` ${chalk.yellow('node scripts/dev.js generate')}`); - console.log(chalk.cyan(' 3. Analyze task complexity:')); - console.log(` ${chalk.yellow('node scripts/dev.js analyze-complexity --research')}`); - console.log(chalk.cyan(' 4. Break down complex tasks:')); - console.log(` ${chalk.yellow('node scripts/dev.js expand --id=3 --research')}`); - console.log(chalk.cyan(' 5. Track progress:')); - console.log(` ${chalk.yellow('node scripts/dev.js list --with-subtasks')}`); - console.log(chalk.cyan(' 6. Update task status:')); - console.log(` ${chalk.yellow('node scripts/dev.js set-status --id=1 --status=done')}`); - - console.log('\n'); -} - -async function addTask(tasksPath, prompt, dependencies = [], priority = 'medium') { - displayBanner(); - - // Read the existing tasks - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', "Invalid or missing tasks.json."); - process.exit(1); - } - - // Find the highest task ID to determine the next ID - const highestId = Math.max(...data.tasks.map(t => t.id)); - const newTaskId = highestId + 1; - - console.log(boxen( - chalk.white.bold(`Creating New Task #${newTaskId}`), - { padding: 1, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 1 } } - )); - - // Validate dependencies before proceeding - const invalidDeps = dependencies.filter(depId => { - return !data.tasks.some(t => t.id === depId); - }); - - if (invalidDeps.length > 0) { - log('warn', `The following dependencies do not exist: ${invalidDeps.join(', ')}`); - log('info', 'Removing invalid dependencies...'); - dependencies = dependencies.filter(depId => !invalidDeps.includes(depId)); - } - - // Create the system prompt for Claude - const systemPrompt = "You are a helpful assistant that creates well-structured tasks for a software development project. Generate a single new task based on the user's description."; - - // Create the user prompt with context from existing tasks - let contextTasks = ''; - if (dependencies.length > 0) { - // Provide context for the dependent tasks - const dependentTasks = data.tasks.filter(t => dependencies.includes(t.id)); - contextTasks = `\nThis task depends on the following tasks:\n${dependentTasks.map(t => - `- Task ${t.id}: ${t.title} - ${t.description}`).join('\n')}`; - } else { - // Provide a few recent tasks as context - const recentTasks = [...data.tasks].sort((a, b) => b.id - a.id).slice(0, 3); - contextTasks = `\nRecent tasks in the project:\n${recentTasks.map(t => - `- Task ${t.id}: ${t.title} - ${t.description}`).join('\n')}`; - } - - const taskStructure = ` - { - "title": "Task title goes here", - "description": "A concise one or two sentence description of what the task involves", - "details": "In-depth details including specifics on implementation, considerations, and anything important for the developer to know. This should be detailed enough to guide implementation.", - "testStrategy": "A detailed approach for verifying the task has been correctly implemented. Include specific test cases or validation methods." - }`; - - const userPrompt = `Create a comprehensive new task (Task #${newTaskId}) for a software development project based on this description: "${prompt}" - - ${contextTasks} - - Return your answer as a single JSON object with the following structure: - ${taskStructure} - - Don't include the task ID, status, dependencies, or priority as those will be added automatically. - Make sure the details and test strategy are thorough and specific. - - IMPORTANT: Return ONLY the JSON object, nothing else.`; - - // Start the loading indicator - const loadingIndicator = startLoadingIndicator('Generating new task with Claude AI...'); - - let fullResponse = ''; - let streamingInterval = null; - - try { - // Call Claude with streaming enabled - const stream = await anthropic.messages.create({ - max_tokens: CONFIG.maxTokens, - model: CONFIG.model, - temperature: CONFIG.temperature, - messages: [{ role: "user", content: userPrompt }], - system: systemPrompt, - stream: true - }); - - // Update loading indicator to show streaming progress - let dotCount = 0; - streamingInterval = setInterval(() => { - readline.cursorTo(process.stdout, 0); - process.stdout.write(`Receiving streaming response from Claude${'.'.repeat(dotCount)}`); - dotCount = (dotCount + 1) % 4; - }, 500); - - // Process the stream - for await (const chunk of stream) { - if (chunk.type === 'content_block_delta' && chunk.delta.text) { - fullResponse += chunk.delta.text; - } - } - - if (streamingInterval) clearInterval(streamingInterval); - stopLoadingIndicator(loadingIndicator); - - log('info', "Completed streaming response from Claude API!"); - log('debug', `Streaming response length: ${fullResponse.length} characters`); - - // Parse the response - handle potential JSON formatting issues - let taskData; - try { - // Check if the response is wrapped in a code block - const jsonMatch = fullResponse.match(/```(?:json)?([^`]+)```/); - const jsonContent = jsonMatch ? jsonMatch[1] : fullResponse; - - // Parse the JSON - taskData = JSON.parse(jsonContent); - - // Check that we have the required fields - if (!taskData.title || !taskData.description) { - throw new Error("Missing required fields in the generated task"); - } - } catch (error) { - log('error', "Failed to parse Claude's response as valid task JSON:", error); - log('debug', "Response content:", fullResponse); - process.exit(1); - } - - // Create the new task object - const newTask = { - id: newTaskId, - title: taskData.title, - description: taskData.description, - status: "pending", - dependencies: dependencies, - priority: priority, - details: taskData.details || "", - testStrategy: taskData.testStrategy || "Manually verify the implementation works as expected." - }; - - // Add the new task to the tasks array - data.tasks.push(newTask); - - // Validate dependencies in the entire task set - log('info', "Validating dependencies after adding new task..."); - const dependencyChanges = validateAndFixDependencies(data, null); - if (dependencyChanges) { - log('info', "Fixed some dependencies that became invalid after adding the new task"); - } - - // Write the updated tasks back to the file - writeJSON(tasksPath, data); - - // Show success message - const successBox = boxen( - chalk.green(`Successfully added new task #${newTaskId}:\n`) + - chalk.white.bold(newTask.title) + "\n\n" + - chalk.white(newTask.description), - { padding: 1, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } } - ); - console.log(successBox); - - // Next steps suggestion - console.log(boxen( - chalk.white.bold('Next Steps:') + '\n\n' + - `${chalk.cyan('1.')} Run ${chalk.yellow('node scripts/dev.js generate')} to update task files\n` + - `${chalk.cyan('2.')} Run ${chalk.yellow('node scripts/dev.js expand --id=' + newTaskId)} to break it down into subtasks\n` + - `${chalk.cyan('3.')} Run ${chalk.yellow('node scripts/dev.js list --with-subtasks')} to see all tasks`, - { padding: 1, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1 } } - )); - - return newTaskId; - } catch (error) { - if (streamingInterval) clearInterval(streamingInterval); - stopLoadingIndicator(loadingIndicator); - log('error', "Error generating task:", error.message); - process.exit(1); - } -} - -/** - * Find the next pending task based on dependencies - * @param {Object[]} tasks - The array of tasks - * @returns {Object|null} The next task to work on or null if no eligible tasks - */ -function findNextTask(tasks) { - // Get all completed task IDs - const completedTaskIds = new Set( - tasks - .filter(t => t.status === 'done' || t.status === 'completed') - .map(t => t.id) - ); - - // Filter for pending tasks whose dependencies are all satisfied - const eligibleTasks = tasks.filter(task => - (task.status === 'pending' || task.status === 'in-progress') && - task.dependencies && // Make sure dependencies array exists - task.dependencies.every(depId => completedTaskIds.has(depId)) - ); - - if (eligibleTasks.length === 0) { - return null; - } - - // Sort eligible tasks by: - // 1. Priority (high > medium > low) - // 2. Dependencies count (fewer dependencies first) - // 3. ID (lower ID first) - const priorityValues = { 'high': 3, 'medium': 2, 'low': 1 }; - - const nextTask = eligibleTasks.sort((a, b) => { - // Sort by priority first - const priorityA = priorityValues[a.priority || 'medium'] || 2; - const priorityB = priorityValues[b.priority || 'medium'] || 2; - - if (priorityB !== priorityA) { - return priorityB - priorityA; // Higher priority first - } - - // If priority is the same, sort by dependency count - if (a.dependencies && b.dependencies && a.dependencies.length !== b.dependencies.length) { - return a.dependencies.length - b.dependencies.length; // Fewer dependencies first - } - - // If dependency count is the same, sort by ID - return a.id - b.id; // Lower ID first - })[0]; // Return the first (highest priority) task - - return nextTask; -} - -/** - * Display the next task to work on - * @param {string} tasksPath - Path to the tasks.json file - */ -async function displayNextTask(tasksPath) { - displayBanner(); - - // Read the tasks file - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', "No valid tasks found."); - process.exit(1); - } - - // Find the next task - const nextTask = findNextTask(data.tasks); - - if (!nextTask) { - console.log(boxen( - chalk.yellow('No eligible tasks found!\n\n') + - 'All pending tasks have unsatisfied dependencies, or all tasks are completed.', - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } } - )); - return; - } - - // Display the task in a nice format - console.log(boxen( - chalk.white.bold(`Next Task: #${nextTask.id} - ${nextTask.title}`), - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 0 } } - )); - - // Create a table with task details - const taskTable = new Table({ - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { - 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' - }, - colWidths: [15, 75] - }); - - // Priority with color - const priorityColors = { - 'high': chalk.red.bold, - 'medium': chalk.yellow, - 'low': chalk.gray - }; - const priorityColor = priorityColors[nextTask.priority || 'medium'] || chalk.white; - - // Add task details to table - taskTable.push( - [chalk.cyan.bold('ID:'), nextTask.id.toString()], - [chalk.cyan.bold('Title:'), nextTask.title], - [chalk.cyan.bold('Priority:'), priorityColor(nextTask.priority || 'medium')], - [chalk.cyan.bold('Dependencies:'), formatDependenciesWithStatus(nextTask.dependencies, data.tasks, true)], - [chalk.cyan.bold('Description:'), nextTask.description] - ); - - console.log(taskTable.toString()); - - // If task has details, show them in a separate box - if (nextTask.details && nextTask.details.trim().length > 0) { - console.log(boxen( - chalk.white.bold('Implementation Details:') + '\n\n' + - nextTask.details, - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 0 } } - )); - } - - // Show subtasks if they exist - if (nextTask.subtasks && nextTask.subtasks.length > 0) { - console.log(boxen( - chalk.white.bold('Subtasks'), - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, margin: { top: 1, bottom: 0 }, borderColor: 'magenta', borderStyle: 'round' } - )); - - // Create a table for subtasks - const subtaskTable = new Table({ - head: [ - chalk.magenta.bold('ID'), - chalk.magenta.bold('Status'), - chalk.magenta.bold('Title'), - chalk.magenta.bold('Dependencies') - ], - colWidths: [6, 12, 50, 20], - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { - 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' - } - }); - - // Add subtasks to table - nextTask.subtasks.forEach(st => { - const statusColor = { - 'done': chalk.green, - 'completed': chalk.green, - 'pending': chalk.yellow, - 'in-progress': chalk.blue - }[st.status || 'pending'] || chalk.white; - - // Format subtask dependencies - let subtaskDeps = 'None'; - if (st.dependencies && st.dependencies.length > 0) { - // Format dependencies with correct notation - const formattedDeps = st.dependencies.map(depId => { - if (typeof depId === 'number' && depId < 100) { - return `${nextTask.id}.${depId}`; - } - return depId; - }); - subtaskDeps = formatDependenciesWithStatus(formattedDeps, data.tasks, true); - } - - subtaskTable.push([ - `${nextTask.id}.${st.id}`, - statusColor(st.status || 'pending'), - st.title, - subtaskDeps - ]); - }); - - console.log(subtaskTable.toString()); - } else { - // Suggest expanding if no subtasks - console.log(boxen( - chalk.yellow('No subtasks found. Consider breaking down this task:') + '\n' + - chalk.white(`Run: ${chalk.cyan(`node scripts/dev.js expand --id=${nextTask.id}`)}`), - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1, bottom: 0 } } - )); - } - - // Show action suggestions - console.log(boxen( - chalk.white.bold('Suggested Actions:') + '\n' + - `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`node scripts/dev.js set-status --id=${nextTask.id} --status=in-progress`)}\n` + - `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`node scripts/dev.js set-status --id=${nextTask.id} --status=done`)}\n` + - (nextTask.subtasks && nextTask.subtasks.length > 0 - ? `${chalk.cyan('3.')} Update subtask status: ${chalk.yellow(`node scripts/dev.js set-status --id=${nextTask.id}.1 --status=done`)}` - : `${chalk.cyan('3.')} Break down into subtasks: ${chalk.yellow(`node scripts/dev.js expand --id=${nextTask.id}`)}`), - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } } - )); -} - -/** - * Find a task by its ID - * @param {Array} tasks - The array of tasks from tasks.json - * @param {string|number} taskId - The ID of the task to find (can be a subtask ID like "1.1") - * @returns {Object|null} - The found task or null if not found - */ -function findTaskById(tasks, taskId) { - // Convert to string for comparison - const idStr = String(taskId); - - // Check if it's a subtask ID (contains a dot) - if (idStr.includes('.')) { - const [parentId, subtaskId] = idStr.split('.'); - - // Find the parent task - const parentTask = tasks.find(t => String(t.id) === parentId); - - // If parent found and has subtasks, find the specific subtask - if (parentTask && parentTask.subtasks && parentTask.subtasks.length > 0) { - const subtask = parentTask.subtasks.find(st => String(st.id) === subtaskId); - if (subtask) { - // Create a copy with parent information - return { - ...subtask, - parentId: parentTask.id, - parentTitle: parentTask.title - }; - } - } - return null; - } - - // Regular task ID - return tasks.find(t => String(t.id) === idStr) || null; -} - -/** - * Display a specific task by ID - * @param {string} tasksPath - Path to the tasks.json file - * @param {string|number} taskId - The ID of the task to display - */ -async function displayTaskById(tasksPath, taskId) { - displayBanner(); - - // Read the tasks file - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', "No valid tasks found."); - process.exit(1); - } - - // Find the task by ID - const task = findTaskById(data.tasks, taskId); - - if (!task) { - console.log(boxen( - chalk.yellow(`Task with ID ${taskId} not found!`), - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1 } } - )); - return; - } - - // Handle subtask display specially - if (task.parentId !== undefined) { - console.log(boxen( - chalk.white.bold(`Subtask: #${task.parentId}.${task.id} - ${task.title}`), - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'magenta', borderStyle: 'round', margin: { top: 1, bottom: 0 } } - )); - - // Create a table with subtask details - const taskTable = new Table({ - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { - 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' - }, - colWidths: [15, 75] - }); - - // Add subtask details to table - taskTable.push( - [chalk.cyan.bold('ID:'), `${task.parentId}.${task.id}`], - [chalk.cyan.bold('Parent Task:'), `#${task.parentId} - ${task.parentTitle}`], - [chalk.cyan.bold('Title:'), task.title], - [chalk.cyan.bold('Status:'), getStatusWithColor(task.status || 'pending')], - [chalk.cyan.bold('Description:'), task.description || 'No description provided.'] - ); - - console.log(taskTable.toString()); - - // Show action suggestions for subtask - console.log(boxen( - chalk.white.bold('Suggested Actions:') + '\n' + - `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`node scripts/dev.js set-status --id=${task.parentId}.${task.id} --status=in-progress`)}\n` + - `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`node scripts/dev.js set-status --id=${task.parentId}.${task.id} --status=done`)}\n` + - `${chalk.cyan('3.')} View parent task: ${chalk.yellow(`node scripts/dev.js show --id=${task.parentId}`)}`, - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } } - )); - - return; - } - - // Display a regular task - console.log(boxen( - chalk.white.bold(`Task: #${task.id} - ${task.title}`), - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'blue', borderStyle: 'round', margin: { top: 1, bottom: 0 } } - )); - - // Create a table with task details - const taskTable = new Table({ - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { - 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' - }, - colWidths: [15, 75] - }); - - // Priority with color - const priorityColors = { - 'high': chalk.red.bold, - 'medium': chalk.yellow, - 'low': chalk.gray - }; - const priorityColor = priorityColors[task.priority || 'medium'] || chalk.white; - - // Add task details to table - taskTable.push( - [chalk.cyan.bold('ID:'), task.id.toString()], - [chalk.cyan.bold('Title:'), task.title], - [chalk.cyan.bold('Status:'), getStatusWithColor(task.status || 'pending')], - [chalk.cyan.bold('Priority:'), priorityColor(task.priority || 'medium')], - [chalk.cyan.bold('Dependencies:'), formatDependenciesWithStatus(task.dependencies, data.tasks, true)], - [chalk.cyan.bold('Description:'), task.description] - ); - - console.log(taskTable.toString()); - - // If task has details, show them in a separate box - if (task.details && task.details.trim().length > 0) { - console.log(boxen( - chalk.white.bold('Implementation Details:') + '\n\n' + - task.details, - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 0 } } - )); - } - - // Show test strategy if available - if (task.testStrategy && task.testStrategy.trim().length > 0) { - console.log(boxen( - chalk.white.bold('Test Strategy:') + '\n\n' + - task.testStrategy, - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'cyan', borderStyle: 'round', margin: { top: 1, bottom: 0 } } - )); - } - - // Show subtasks if they exist - if (task.subtasks && task.subtasks.length > 0) { - console.log(boxen( - chalk.white.bold('Subtasks'), - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, margin: { top: 1, bottom: 0 }, borderColor: 'magenta', borderStyle: 'round' } - )); - - // Create a table for subtasks - const subtaskTable = new Table({ - head: [ - chalk.magenta.bold('ID'), - chalk.magenta.bold('Status'), - chalk.magenta.bold('Title'), - chalk.magenta.bold('Dependencies') - ], - colWidths: [6, 12, 50, 20], - style: { - head: [], - border: [], - 'padding-top': 0, - 'padding-bottom': 0, - compact: true - }, - chars: { - 'mid': '', 'left-mid': '', 'mid-mid': '', 'right-mid': '' - } - }); - - // Add subtasks to table - task.subtasks.forEach(st => { - const statusColor = { - 'done': chalk.green, - 'completed': chalk.green, - 'pending': chalk.yellow, - 'in-progress': chalk.blue - }[st.status || 'pending'] || chalk.white; - - // Format subtask dependencies - let subtaskDeps = 'None'; - if (st.dependencies && st.dependencies.length > 0) { - // Format dependencies with correct notation - const formattedDeps = st.dependencies.map(depId => { - if (typeof depId === 'number' && depId < 100) { - return `${task.id}.${depId}`; - } - return depId; - }); - subtaskDeps = formatDependenciesWithStatus(formattedDeps, data.tasks, true); - } - - subtaskTable.push([ - `${task.id}.${st.id}`, - statusColor(st.status || 'pending'), - st.title, - subtaskDeps - ]); - }); - - console.log(subtaskTable.toString()); - } else { - // Suggest expanding if no subtasks - console.log(boxen( - chalk.yellow('No subtasks found. Consider breaking down this task:') + '\n' + - chalk.white(`Run: ${chalk.cyan(`node scripts/dev.js expand --id=${task.id}`)}`), - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'yellow', borderStyle: 'round', margin: { top: 1, bottom: 0 } } - )); - } - - // Show action suggestions - console.log(boxen( - chalk.white.bold('Suggested Actions:') + '\n' + - `${chalk.cyan('1.')} Mark as in-progress: ${chalk.yellow(`node scripts/dev.js set-status --id=${task.id} --status=in-progress`)}\n` + - `${chalk.cyan('2.')} Mark as done when completed: ${chalk.yellow(`node scripts/dev.js set-status --id=${task.id} --status=done`)}\n` + - (task.subtasks && task.subtasks.length > 0 - ? `${chalk.cyan('3.')} Update subtask status: ${chalk.yellow(`node scripts/dev.js set-status --id=${task.id}.1 --status=done`)}` - : `${chalk.cyan('3.')} Break down into subtasks: ${chalk.yellow(`node scripts/dev.js expand --id=${task.id}`)}`), - { padding: { top: 0, bottom: 0, left: 1, right: 1 }, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } } - )); -} - -/** - * Format a task or subtask ID into the correct string format - * @param {string|number} id - The task or subtask ID to format - * @returns {string|number} - The formatted ID - */ -function formatTaskId(id) { - // If it's already a string with a dot notation, leave as is - if (typeof id === 'string' && id.includes('.')) { - return id; - } - - // If it's a number or a string without a dot, convert to number - if (typeof id === 'number' || !id.includes('.')) { - return parseInt(id, 10); - } - - return id; -} - -/** - * Check if a task or subtask with the given ID exists - * @param {Array} tasks - All tasks - * @param {string|number} taskId - ID to check - * @returns {boolean} - True if the task or subtask exists - */ -function taskExists(tasks, taskId) { - // Check if it's a subtask ID (e.g., "1.2") - const isSubtask = typeof taskId === 'string' && taskId.includes('.'); - - if (isSubtask) { - // Parse parent and subtask IDs - const [parentId, subtaskId] = taskId.split('.').map(id => isNaN(id) ? id : Number(id)); - const parentTask = tasks.find(t => t.id === parentId); - - // Check if parent task exists and has the specific subtask - if (parentTask && parentTask.subtasks) { - return parentTask.subtasks.some(s => s.id === Number(subtaskId)); - } - return false; - } else { - // Regular task (not a subtask) - return tasks.some(t => t.id === Number(taskId)); - } -} - -async function addDependency(tasksPath, taskId, dependencyId) { - log('info', `Adding dependency ${dependencyId} to task ${taskId}...`); - - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', 'No valid tasks found in tasks.json'); - process.exit(1); - } - - // Format the task and dependency IDs correctly - const formattedTaskId = typeof taskId === 'string' && taskId.includes('.') - ? taskId : parseInt(taskId, 10); - - const formattedDependencyId = formatTaskId(dependencyId); - - // Check if the dependency task or subtask actually exists - if (!taskExists(data.tasks, formattedDependencyId)) { - log('error', `Dependency target ${formattedDependencyId} does not exist in tasks.json`); - process.exit(1); - } - - // Find the task to update - let targetTask = null; - let isSubtask = false; - - if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) { - // Handle dot notation for subtasks (e.g., "1.2") - const [parentId, subtaskId] = formattedTaskId.split('.').map(id => parseInt(id, 10)); - const parentTask = data.tasks.find(t => t.id === parentId); - - if (!parentTask) { - log('error', `Parent task ${parentId} not found.`); - process.exit(1); - } - - if (!parentTask.subtasks) { - log('error', `Parent task ${parentId} has no subtasks.`); - process.exit(1); - } - - targetTask = parentTask.subtasks.find(s => s.id === subtaskId); - isSubtask = true; - - if (!targetTask) { - log('error', `Subtask ${formattedTaskId} not found.`); - process.exit(1); - } - } else { - // Regular task (not a subtask) - targetTask = data.tasks.find(t => t.id === formattedTaskId); - - if (!targetTask) { - log('error', `Task ${formattedTaskId} not found.`); - process.exit(1); - } - } - - // Initialize dependencies array if it doesn't exist - if (!targetTask.dependencies) { - targetTask.dependencies = []; - } - - // Check if dependency already exists - if (targetTask.dependencies.some(d => { - // Convert both to strings for comparison to handle both numeric and string IDs - return String(d) === String(formattedDependencyId); - })) { - log('warn', `Dependency ${formattedDependencyId} already exists in task ${formattedTaskId}.`); - return; - } - - // Check if the task is trying to depend on itself - if (String(formattedTaskId) === String(formattedDependencyId)) { - log('error', `Task ${formattedTaskId} cannot depend on itself.`); - process.exit(1); - } - - // Check for circular dependencies - let dependencyChain = [formattedTaskId]; - if (!isCircularDependency(data.tasks, formattedDependencyId, dependencyChain)) { - // Add the dependency - targetTask.dependencies.push(formattedDependencyId); - - // Sort dependencies numerically or by parent task ID first, then subtask ID - targetTask.dependencies.sort((a, b) => { - if (typeof a === 'number' && typeof b === 'number') { - return a - b; - } else if (typeof a === 'string' && typeof b === 'string') { - const [aParent, aChild] = a.split('.').map(Number); - const [bParent, bChild] = b.split('.').map(Number); - return aParent !== bParent ? aParent - bParent : aChild - bChild; - } else if (typeof a === 'number') { - return -1; // Numbers come before strings - } else { - return 1; // Strings come after numbers - } - }); - - // Save changes - writeJSON(tasksPath, data); - log('success', `Added dependency ${formattedDependencyId} to task ${formattedTaskId}`); - - // Generate updated task files - await generateTaskFiles(tasksPath, 'tasks'); - - log('info', 'Task files regenerated with updated dependencies.'); - } else { - log('error', `Cannot add dependency ${formattedDependencyId} to task ${formattedTaskId} as it would create a circular dependency.`); - process.exit(1); - } -} - -/** - * Remove a dependency from a task - * @param {string} tasksPath - Path to the tasks.json file - * @param {number|string} taskId - ID of the task to remove dependency from - * @param {number|string} dependencyId - ID of the task to remove as dependency - */ -async function removeDependency(tasksPath, taskId, dependencyId) { - log('info', `Removing dependency ${dependencyId} from task ${taskId}...`); - - // Read tasks file - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', "No valid tasks found."); - process.exit(1); - } - - // Format the task and dependency IDs correctly - const formattedTaskId = typeof taskId === 'string' && taskId.includes('.') - ? taskId : parseInt(taskId, 10); - - const formattedDependencyId = formatTaskId(dependencyId); - - // Find the task to update - let targetTask = null; - let isSubtask = false; - - if (typeof formattedTaskId === 'string' && formattedTaskId.includes('.')) { - // Handle dot notation for subtasks (e.g., "1.2") - const [parentId, subtaskId] = formattedTaskId.split('.').map(id => parseInt(id, 10)); - const parentTask = data.tasks.find(t => t.id === parentId); - - if (!parentTask) { - log('error', `Parent task ${parentId} not found.`); - process.exit(1); - } - - if (!parentTask.subtasks) { - log('error', `Parent task ${parentId} has no subtasks.`); - process.exit(1); - } - - targetTask = parentTask.subtasks.find(s => s.id === subtaskId); - isSubtask = true; - - if (!targetTask) { - log('error', `Subtask ${formattedTaskId} not found.`); - process.exit(1); - } - } else { - // Regular task (not a subtask) - targetTask = data.tasks.find(t => t.id === formattedTaskId); - - if (!targetTask) { - log('error', `Task ${formattedTaskId} not found.`); - process.exit(1); - } - } - - // Check if the task has any dependencies - if (!targetTask.dependencies || targetTask.dependencies.length === 0) { - log('info', `Task ${formattedTaskId} has no dependencies, nothing to remove.`); - return; - } - - // Normalize the dependency ID for comparison to handle different formats - const normalizedDependencyId = String(formattedDependencyId); - - // Check if the dependency exists by comparing string representations - const dependencyIndex = targetTask.dependencies.findIndex(dep => { - // Convert both to strings for comparison - let depStr = String(dep); - - // Special handling for numeric IDs that might be subtask references - if (typeof dep === 'number' && dep < 100 && isSubtask) { - // It's likely a reference to another subtask in the same parent task - // Convert to full format for comparison (e.g., 2 -> "1.2" for a subtask in task 1) - const [parentId] = formattedTaskId.split('.'); - depStr = `${parentId}.${dep}`; - } - - return depStr === normalizedDependencyId; - }); - - if (dependencyIndex === -1) { - log('info', `Task ${formattedTaskId} does not depend on ${formattedDependencyId}, no changes made.`); - return; - } - - // Remove the dependency - targetTask.dependencies.splice(dependencyIndex, 1); - - // Save the updated tasks - writeJSON(tasksPath, data); - - // Success message - log('success', `Removed dependency: Task ${formattedTaskId} no longer depends on ${formattedDependencyId}`); - - // Display a more visually appealing success message - console.log(boxen( - chalk.green(`Successfully removed dependency:\n\n`) + - `Task ${chalk.bold(formattedTaskId)} no longer depends on ${chalk.bold(formattedDependencyId)}`, - { padding: 1, borderColor: 'green', borderStyle: 'round', margin: { top: 1 } } - )); - - // Regenerate task files - await generateTaskFiles(tasksPath, 'tasks'); -} - -/** - * Check if adding a dependency would create a circular dependency - * @param {Array} tasks - All tasks - * @param {number|string} dependencyId - ID of the dependency being added - * @param {Array} chain - Current dependency chain being checked - * @returns {boolean} - True if circular dependency would be created, false otherwise - */ -function isCircularDependency(tasks, dependencyId, chain = []) { - // Convert chain elements and dependencyId to strings for consistent comparison - const chainStrs = chain.map(id => String(id)); - const depIdStr = String(dependencyId); - - // If the dependency is already in the chain, it would create a circular dependency - if (chainStrs.includes(depIdStr)) { - log('error', `Circular dependency detected: ${chainStrs.join(' -> ')} -> ${depIdStr}`); - return true; - } - - // Check if this is a subtask dependency (e.g., "1.2") - const isSubtask = depIdStr.includes('.'); - - // Find the task or subtask by ID - let dependencyTask = null; - let dependencySubtask = null; - - if (isSubtask) { - // Parse parent and subtask IDs - const [parentId, subtaskId] = depIdStr.split('.').map(id => isNaN(id) ? id : Number(id)); - const parentTask = tasks.find(t => t.id === parentId); - - if (parentTask && parentTask.subtasks) { - dependencySubtask = parentTask.subtasks.find(s => s.id === Number(subtaskId)); - // For a subtask, we need to check dependencies of both the subtask and its parent - if (dependencySubtask && dependencySubtask.dependencies && dependencySubtask.dependencies.length > 0) { - // Recursively check each of the subtask's dependencies - const newChain = [...chainStrs, depIdStr]; - const hasCircular = dependencySubtask.dependencies.some(depId => { - // Handle relative subtask references (e.g., numeric IDs referring to subtasks in the same parent task) - const normalizedDepId = typeof depId === 'number' && depId < 100 - ? `${parentId}.${depId}` - : depId; - return isCircularDependency(tasks, normalizedDepId, newChain); - }); - - if (hasCircular) return true; - } - - // Also check if parent task has dependencies that could create a cycle - if (parentTask.dependencies && parentTask.dependencies.length > 0) { - // If any of the parent's dependencies create a cycle, return true - const newChain = [...chainStrs, depIdStr]; - if (parentTask.dependencies.some(depId => isCircularDependency(tasks, depId, newChain))) { - return true; - } - } - - return false; - } - } else { - // Regular task (not a subtask) - const depId = isNaN(dependencyId) ? dependencyId : Number(dependencyId); - dependencyTask = tasks.find(t => t.id === depId); - - // If task not found or has no dependencies, there's no circular dependency - if (!dependencyTask || !dependencyTask.dependencies || dependencyTask.dependencies.length === 0) { - return false; - } - - // Recursively check each of the dependency's dependencies - const newChain = [...chainStrs, depIdStr]; - if (dependencyTask.dependencies.some(depId => isCircularDependency(tasks, depId, newChain))) { - return true; - } - - // Also check for cycles through subtasks of this task - if (dependencyTask.subtasks && dependencyTask.subtasks.length > 0) { - for (const subtask of dependencyTask.subtasks) { - if (subtask.dependencies && subtask.dependencies.length > 0) { - // Check if any of this subtask's dependencies create a cycle - const subtaskId = `${dependencyTask.id}.${subtask.id}`; - const newSubtaskChain = [...chainStrs, depIdStr, subtaskId]; - - for (const subDepId of subtask.dependencies) { - // Handle relative subtask references - const normalizedDepId = typeof subDepId === 'number' && subDepId < 100 - ? `${dependencyTask.id}.${subDepId}` - : subDepId; - - if (isCircularDependency(tasks, normalizedDepId, newSubtaskChain)) { - return true; - } - } - } - } - } - } - - return false; -} - -// At the very end of the file -main().catch(err => { - console.error('ERROR in main:', err); - process.exit(1); -}); - -/** - * Validate and clean up task dependencies to ensure they only reference existing tasks - * @param {Array} tasks - Array of tasks to validate - * @param {string} tasksPath - Optional path to tasks.json to save changes - * @returns {boolean} - True if any changes were made to dependencies - */ -function validateTaskDependencies(tasks, tasksPath = null) { - // Create a set of valid task IDs for fast lookup - const validTaskIds = new Set(tasks.map(t => t.id)); - - // Create a set of valid subtask IDs (in the format "parentId.subtaskId") - const validSubtaskIds = new Set(); - tasks.forEach(task => { - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach(subtask => { - validSubtaskIds.add(`${task.id}.${subtask.id}`); - }); - } - }); - - // Flag to track if any changes were made - let changesDetected = false; - - // Validate all tasks and their dependencies - tasks.forEach(task => { - if (task.dependencies && Array.isArray(task.dependencies)) { - // First check for and remove duplicate dependencies - const uniqueDeps = new Set(); - const uniqueDependencies = task.dependencies.filter(depId => { - // Convert to string for comparison to handle both numeric and string IDs - const depIdStr = String(depId); - if (uniqueDeps.has(depIdStr)) { - log('warn', `Removing duplicate dependency from task ${task.id}: ${depId}`); - changesDetected = true; - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - - // If we removed duplicates, update the array - if (uniqueDependencies.length !== task.dependencies.length) { - task.dependencies = uniqueDependencies; - changesDetected = true; - } - - const validDependencies = uniqueDependencies.filter(depId => { - const isSubtask = typeof depId === 'string' && depId.includes('.'); - - if (isSubtask) { - // Check if the subtask exists - if (!validSubtaskIds.has(depId)) { - log('warn', `Removing invalid subtask dependency from task ${task.id}: ${depId} (subtask does not exist)`); - return false; - } - return true; - } else { - // Check if the task exists - const numericId = typeof depId === 'string' ? parseInt(depId, 10) : depId; - if (!validTaskIds.has(numericId)) { - log('warn', `Removing invalid task dependency from task ${task.id}: ${depId} (task does not exist)`); - return false; - } - return true; - } - }); - - // Update the task's dependencies array - if (validDependencies.length !== uniqueDependencies.length) { - task.dependencies = validDependencies; - changesDetected = true; - } - } - - // Validate subtask dependencies - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach(subtask => { - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - // First check for and remove duplicate dependencies - const uniqueDeps = new Set(); - const uniqueDependencies = subtask.dependencies.filter(depId => { - // Convert to string for comparison to handle both numeric and string IDs - const depIdStr = String(depId); - if (uniqueDeps.has(depIdStr)) { - log('warn', `Removing duplicate dependency from subtask ${task.id}.${subtask.id}: ${depId}`); - changesDetected = true; - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - - // If we removed duplicates, update the array - if (uniqueDependencies.length !== subtask.dependencies.length) { - subtask.dependencies = uniqueDependencies; - changesDetected = true; - } - - // Check for and remove self-dependencies - const subtaskId = `${task.id}.${subtask.id}`; - const selfDependencyIndex = subtask.dependencies.findIndex(depId => { - return String(depId) === String(subtaskId); - }); - - if (selfDependencyIndex !== -1) { - log('warn', `Removing self-dependency from subtask ${subtaskId} (subtask cannot depend on itself)`); - subtask.dependencies.splice(selfDependencyIndex, 1); - changesDetected = true; - } - - // Then validate remaining dependencies - const validSubtaskDeps = subtask.dependencies.filter(depId => { - const isSubtask = typeof depId === 'string' && depId.includes('.'); - - if (isSubtask) { - // Check if the subtask exists - if (!validSubtaskIds.has(depId)) { - log('warn', `Removing invalid subtask dependency from subtask ${task.id}.${subtask.id}: ${depId} (subtask does not exist)`); - return false; - } - return true; - } else { - // Check if the task exists - const numericId = typeof depId === 'string' ? parseInt(depId, 10) : depId; - if (!validTaskIds.has(numericId)) { - log('warn', `Removing invalid task dependency from task ${task.id}: ${depId} (task does not exist)`); - return false; - } - return true; - } - }); - - // Update the subtask's dependencies array - if (validSubtaskDeps.length !== subtask.dependencies.length) { - subtask.dependencies = validSubtaskDeps; - changesDetected = true; - } - } - }); - } - }); - - // Save changes if tasksPath is provided and changes were detected - if (tasksPath && changesDetected) { - try { - const data = readJSON(tasksPath); - if (data) { - data.tasks = tasks; - writeJSON(tasksPath, data); - log('info', 'Updated tasks.json to remove invalid and duplicate dependencies'); - } - } catch (error) { - log('error', 'Failed to save changes to tasks.json', error); - } - } - - return changesDetected; -} - -async function validateDependenciesCommand(tasksPath) { - displayBanner(); - - log('info', 'Checking for invalid dependencies in task files...'); - - // Read tasks data - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', 'No valid tasks found in tasks.json'); - process.exit(1); - } - - // Count of tasks and subtasks for reporting - const taskCount = data.tasks.length; - let subtaskCount = 0; - data.tasks.forEach(task => { - if (task.subtasks && Array.isArray(task.subtasks)) { - subtaskCount += task.subtasks.length; - } - }); - - log('info', `Analyzing dependencies for ${taskCount} tasks and ${subtaskCount} subtasks...`); - - // Track validation statistics - const stats = { - nonExistentDependenciesRemoved: 0, - selfDependenciesRemoved: 0, - tasksFixed: 0, - subtasksFixed: 0 - }; - - // Monkey patch the log function to capture warnings and count fixes - const originalLog = log; - const warnings = []; - log = function(level, ...args) { - if (level === 'warn') { - warnings.push(args.join(' ')); - - // Count the type of fix based on the warning message - const msg = args.join(' '); - if (msg.includes('self-dependency')) { - stats.selfDependenciesRemoved++; - } else if (msg.includes('invalid')) { - stats.nonExistentDependenciesRemoved++; - } - - // Count if it's a task or subtask being fixed - if (msg.includes('from subtask')) { - stats.subtasksFixed++; - } else if (msg.includes('from task')) { - stats.tasksFixed++; - } - } - // Call the original log function - return originalLog(level, ...args); - }; - - // Run validation - try { - const changesDetected = validateTaskDependencies(data.tasks, tasksPath); - - // Create a detailed report - if (changesDetected) { - log('success', 'Invalid dependencies were removed from tasks.json'); - - // Show detailed stats in a nice box - console.log(boxen( - chalk.green(`Dependency Validation Results:\n\n`) + - `${chalk.cyan('Tasks checked:')} ${taskCount}\n` + - `${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` + - `${chalk.cyan('Non-existent dependencies removed:')} ${stats.nonExistentDependenciesRemoved}\n` + - `${chalk.cyan('Self-dependencies removed:')} ${stats.selfDependenciesRemoved}\n` + - `${chalk.cyan('Tasks fixed:')} ${stats.tasksFixed}\n` + - `${chalk.cyan('Subtasks fixed:')} ${stats.subtasksFixed}`, - { padding: 1, borderColor: 'green', borderStyle: 'round', margin: { top: 1, bottom: 1 } } - )); - - // Show all warnings in a collapsible list if there are many - if (warnings.length > 0) { - console.log(chalk.yellow('\nDetailed fixes:')); - warnings.forEach(warning => { - console.log(` ${warning}`); - }); - } - - // Regenerate task files to reflect the changes - await generateTaskFiles(tasksPath, path.dirname(tasksPath)); - log('info', 'Task files regenerated to reflect dependency changes'); - } else { - log('success', 'No invalid dependencies found - all dependencies are valid'); - - // Show validation summary - console.log(boxen( - chalk.green(`All Dependencies Are Valid\n\n`) + - `${chalk.cyan('Tasks checked:')} ${taskCount}\n` + - `${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` + - `${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`, - { padding: 1, borderColor: 'green', borderStyle: 'round', margin: { top: 1, bottom: 1 } } - )); - } - } finally { - // Restore the original log function - log = originalLog; - } -} - -/** - * Helper function to count all dependencies across tasks and subtasks - * @param {Array} tasks - All tasks - * @returns {number} - Total number of dependencies - */ -function countAllDependencies(tasks) { - let count = 0; - - tasks.forEach(task => { - // Count main task dependencies - if (task.dependencies && Array.isArray(task.dependencies)) { - count += task.dependencies.length; - } - - // Count subtask dependencies - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach(subtask => { - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - count += subtask.dependencies.length; - } - }); - } - }); - - return count; -} - -// New command implementation -async function fixDependenciesCommand(tasksPath) { - displayBanner(); - - log('info', 'Checking for and fixing invalid dependencies in tasks.json...'); - - try { - // Read tasks data - const data = readJSON(tasksPath); - if (!data || !data.tasks) { - log('error', 'No valid tasks found in tasks.json'); - process.exit(1); - } - - // Create a deep copy of the original data for comparison - const originalData = JSON.parse(JSON.stringify(data)); - - // Track fixes for reporting - const stats = { - nonExistentDependenciesRemoved: 0, - selfDependenciesRemoved: 0, - duplicateDependenciesRemoved: 0, - circularDependenciesFixed: 0, - tasksFixed: 0, - subtasksFixed: 0 - }; - - // First phase: Remove duplicate dependencies in tasks - data.tasks.forEach(task => { - if (task.dependencies && Array.isArray(task.dependencies)) { - const uniqueDeps = new Set(); - const originalLength = task.dependencies.length; - task.dependencies = task.dependencies.filter(depId => { - const depIdStr = String(depId); - if (uniqueDeps.has(depIdStr)) { - log('info', `Removing duplicate dependency from task ${task.id}: ${depId}`); - stats.duplicateDependenciesRemoved++; - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - if (task.dependencies.length < originalLength) { - stats.tasksFixed++; - } - } - - // Check for duplicates in subtasks - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach(subtask => { - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - const uniqueDeps = new Set(); - const originalLength = subtask.dependencies.length; - subtask.dependencies = subtask.dependencies.filter(depId => { - let depIdStr = String(depId); - if (typeof depId === 'number' && depId < 100) { - depIdStr = `${task.id}.${depId}`; - } - if (uniqueDeps.has(depIdStr)) { - log('info', `Removing duplicate dependency from subtask ${task.id}.${subtask.id}: ${depId}`); - stats.duplicateDependenciesRemoved++; - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - if (subtask.dependencies.length < originalLength) { - stats.subtasksFixed++; - } - } - }); - } - }); - - // Create validity maps for tasks and subtasks - const validTaskIds = new Set(data.tasks.map(t => t.id)); - const validSubtaskIds = new Set(); - data.tasks.forEach(task => { - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach(subtask => { - validSubtaskIds.add(`${task.id}.${subtask.id}`); - }); - } - }); - - // Second phase: Remove invalid task dependencies (non-existent tasks) - data.tasks.forEach(task => { - if (task.dependencies && Array.isArray(task.dependencies)) { - const originalLength = task.dependencies.length; - task.dependencies = task.dependencies.filter(depId => { - const isSubtask = typeof depId === 'string' && depId.includes('.'); - - if (isSubtask) { - // Check if the subtask exists - if (!validSubtaskIds.has(depId)) { - log('info', `Removing invalid subtask dependency from task ${task.id}: ${depId} (subtask does not exist)`); - stats.nonExistentDependenciesRemoved++; - return false; - } - return true; - } else { - // Check if the task exists - const numericId = typeof depId === 'string' ? parseInt(depId, 10) : depId; - if (!validTaskIds.has(numericId)) { - log('info', `Removing invalid task dependency from task ${task.id}: ${depId} (task does not exist)`); - stats.nonExistentDependenciesRemoved++; - return false; - } - return true; - } - }); - - if (task.dependencies.length < originalLength) { - stats.tasksFixed++; - } - } - - // Check subtask dependencies for invalid references - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach(subtask => { - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - const originalLength = subtask.dependencies.length; - const subtaskId = `${task.id}.${subtask.id}`; - - // First check for self-dependencies - const hasSelfDependency = subtask.dependencies.some(depId => { - if (typeof depId === 'string' && depId.includes('.')) { - return depId === subtaskId; - } else if (typeof depId === 'number' && depId < 100) { - return depId === subtask.id; - } - return false; - }); - - if (hasSelfDependency) { - subtask.dependencies = subtask.dependencies.filter(depId => { - const normalizedDepId = typeof depId === 'number' && depId < 100 - ? `${task.id}.${depId}` - : String(depId); - - if (normalizedDepId === subtaskId) { - log('info', `Removing self-dependency from subtask ${subtaskId}`); - stats.selfDependenciesRemoved++; - return false; - } - return true; - }); - } - - // Then check for non-existent dependencies - subtask.dependencies = subtask.dependencies.filter(depId => { - if (typeof depId === 'string' && depId.includes('.')) { - if (!validSubtaskIds.has(depId)) { - log('info', `Removing invalid subtask dependency from subtask ${subtaskId}: ${depId} (subtask does not exist)`); - stats.nonExistentDependenciesRemoved++; - return false; - } - return true; - } - - // Handle numeric dependencies - const numericId = typeof depId === 'number' ? depId : parseInt(depId, 10); - - // Small numbers likely refer to subtasks in the same task - if (numericId < 100) { - const fullSubtaskId = `${task.id}.${numericId}`; - - if (!validSubtaskIds.has(fullSubtaskId)) { - log('info', `Removing invalid subtask dependency from subtask ${subtaskId}: ${numericId}`); - stats.nonExistentDependenciesRemoved++; - return false; - } - - return true; - } - - // Otherwise it's a task reference - if (!validTaskIds.has(numericId)) { - log('info', `Removing invalid task dependency from subtask ${subtaskId}: ${numericId}`); - stats.nonExistentDependenciesRemoved++; - return false; - } - - return true; - }); - - if (subtask.dependencies.length < originalLength) { - stats.subtasksFixed++; - } - } - }); - } - }); - - // Third phase: Check for circular dependencies - log('info', 'Checking for circular dependencies...'); - - // Build the dependency map for subtasks - const subtaskDependencyMap = new Map(); - data.tasks.forEach(task => { - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach(subtask => { - const subtaskId = `${task.id}.${subtask.id}`; - - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - const normalizedDeps = subtask.dependencies.map(depId => { - if (typeof depId === 'string' && depId.includes('.')) { - return depId; - } else if (typeof depId === 'number' && depId < 100) { - return `${task.id}.${depId}`; - } - return String(depId); - }); - subtaskDependencyMap.set(subtaskId, normalizedDeps); - } else { - subtaskDependencyMap.set(subtaskId, []); - } - }); - } - }); - - // Check for and fix circular dependencies - for (const [subtaskId, dependencies] of subtaskDependencyMap.entries()) { - const visited = new Set(); - const recursionStack = new Set(); - - // Detect cycles - const cycleEdges = findCycles(subtaskId, subtaskDependencyMap, visited, recursionStack); - - if (cycleEdges.length > 0) { - const [taskId, subtaskNum] = subtaskId.split('.').map(part => Number(part)); - const task = data.tasks.find(t => t.id === taskId); - - if (task && task.subtasks) { - const subtask = task.subtasks.find(st => st.id === subtaskNum); - - if (subtask && subtask.dependencies) { - const originalLength = subtask.dependencies.length; - - const edgesToRemove = cycleEdges.map(edge => { - if (edge.includes('.')) { - const [depTaskId, depSubtaskId] = edge.split('.').map(part => Number(part)); - - if (depTaskId === taskId) { - return depSubtaskId; - } - - return edge; - } - - return Number(edge); - }); - - subtask.dependencies = subtask.dependencies.filter(depId => { - const normalizedDepId = typeof depId === 'number' && depId < 100 - ? `${taskId}.${depId}` - : String(depId); - - if (edgesToRemove.includes(depId) || edgesToRemove.includes(normalizedDepId)) { - log('info', `Breaking circular dependency: Removing ${normalizedDepId} from subtask ${subtaskId}`); - stats.circularDependenciesFixed++; - return false; - } - return true; - }); - - if (subtask.dependencies.length < originalLength) { - stats.subtasksFixed++; - } - } - } - } - } - - // Check if any changes were made by comparing with original data - const dataChanged = JSON.stringify(data) !== JSON.stringify(originalData); - - if (dataChanged) { - // Save the changes - writeJSON(tasksPath, data); - log('success', 'Fixed dependency issues in tasks.json'); - - // Regenerate task files - log('info', 'Regenerating task files to reflect dependency changes...'); - await generateTaskFiles(tasksPath, path.dirname(tasksPath)); - } else { - log('info', 'No changes needed to fix dependencies'); - } - - // Show detailed statistics report - const totalFixedAll = stats.nonExistentDependenciesRemoved + - stats.selfDependenciesRemoved + - stats.duplicateDependenciesRemoved + - stats.circularDependenciesFixed; - - if (totalFixedAll > 0) { - log('success', `Fixed ${totalFixedAll} dependency issues in total!`); - - console.log(boxen( - chalk.green(`Dependency Fixes Summary:\n\n`) + - `${chalk.cyan('Invalid dependencies removed:')} ${stats.nonExistentDependenciesRemoved}\n` + - `${chalk.cyan('Self-dependencies removed:')} ${stats.selfDependenciesRemoved}\n` + - `${chalk.cyan('Duplicate dependencies removed:')} ${stats.duplicateDependenciesRemoved}\n` + - `${chalk.cyan('Circular dependencies fixed:')} ${stats.circularDependenciesFixed}\n\n` + - `${chalk.cyan('Tasks fixed:')} ${stats.tasksFixed}\n` + - `${chalk.cyan('Subtasks fixed:')} ${stats.subtasksFixed}\n`, - { padding: 1, borderColor: 'green', borderStyle: 'round', margin: { top: 1, bottom: 1 } } - )); - } else { - log('success', 'No dependency issues found - all dependencies are valid'); - - console.log(boxen( - chalk.green(`All Dependencies Are Valid\n\n`) + - `${chalk.cyan('Tasks checked:')} ${data.tasks.length}\n` + - `${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`, - { padding: 1, borderColor: 'green', borderStyle: 'round', margin: { top: 1, bottom: 1 } } - )); - } - } catch (error) { - log('error', "Error in fix-dependencies command:", error); - process.exit(1); - } -} - -// Add a new function to clean up task dependencies before line 4030 -/** - * Clean up all subtask dependencies by removing any references to non-existent subtasks/tasks - * @param {Object} data - The tasks data object from tasks.json - * @param {string} tasksPath - Path to the tasks.json file - * @returns {number} - The number of dependencies fixed - */ -function cleanupTaskDependencies(data, tasksPath) { - if (!data || !data.tasks || !Array.isArray(data.tasks)) { - log('error', 'Invalid tasks data'); - return 0; - } - - log('info', 'Cleaning up all invalid subtask dependencies in tasks.json...'); - - let totalFixed = 0; - let totalCircularDepsFixed = 0; - let totalDuplicatesRemoved = 0; - - // Create a set of valid task IDs and subtask IDs for validation - const validTaskIds = new Set(data.tasks.map(t => t.id)); - const validSubtaskIds = new Set(); - - // Create a map of subtask ID to its dependencies for cycle detection - const subtaskDependencyMap = new Map(); - - data.tasks.forEach(task => { - // First, check for and remove duplicate dependencies in the main task - if (task.dependencies && Array.isArray(task.dependencies)) { - const uniqueDeps = new Set(); - const originalLength = task.dependencies.length; - - task.dependencies = task.dependencies.filter(depId => { - // Convert to string for comparison - const depIdStr = String(depId); - if (uniqueDeps.has(depIdStr)) { - log('info', `Removing duplicate dependency from task ${task.id}: ${depId}`); - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - - const duplicatesRemoved = originalLength - task.dependencies.length; - totalDuplicatesRemoved += duplicatesRemoved; - } - - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach(subtask => { - const subtaskId = `${task.id}.${subtask.id}`; - validSubtaskIds.add(subtaskId); - - // First, check for and remove duplicate dependencies in subtasks - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - const uniqueDeps = new Set(); - const originalLength = subtask.dependencies.length; - - subtask.dependencies = subtask.dependencies.filter(depId => { - // Convert to string for comparison, handling special case for subtask references - let depIdStr = String(depId); - - // For numeric IDs that are likely subtask references in the same parent task - if (typeof depId === 'number' && depId < 100) { - depIdStr = `${task.id}.${depId}`; - } - - if (uniqueDeps.has(depIdStr)) { - log('info', `Removing duplicate dependency from subtask ${subtaskId}: ${depId}`); - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - - const duplicatesRemoved = originalLength - subtask.dependencies.length; - totalDuplicatesRemoved += duplicatesRemoved; - - // Add to dependency map for later cycle detection - const normalizedDeps = subtask.dependencies.map(depId => { - if (typeof depId === 'string' && depId.includes('.')) { - return depId; // It's already a fully qualified subtask ID - } else if (typeof depId === 'number' && depId < 100) { - return `${task.id}.${depId}`; // A subtask in the current task - } - return String(depId); // A task ID - }); - - subtaskDependencyMap.set(subtaskId, normalizedDeps); - } else { - subtaskDependencyMap.set(subtaskId, []); - } - }); - } - }); - - // Now process non-existent dependencies - data.tasks.forEach(task => { - if (!task.subtasks || !Array.isArray(task.subtasks)) { - return; - } - - // Process each subtask's dependencies - task.subtasks.forEach(subtask => { - if (!subtask.dependencies || !Array.isArray(subtask.dependencies)) { - return; - } - - const originalLength = subtask.dependencies.length; - const subtaskId = `${task.id}.${subtask.id}`; - - // Filter out invalid dependencies (non-existent or self-references) - subtask.dependencies = subtask.dependencies.filter(depId => { - // Check if it's a subtask reference (e.g., "1.2") - if (typeof depId === 'string' && depId.includes('.')) { - // It's invalid if it's not in our list of valid subtask IDs - if (!validSubtaskIds.has(depId)) { - log('info', `Removing invalid subtask dependency from subtask ${subtaskId}: ${depId}`); - return false; - } - - // Check for self-dependency - if (depId === subtaskId) { - log('info', `Removing self-dependency from subtask ${subtaskId}`); - return false; - } - - return true; - } - - // For task references or numeric IDs - const numericId = typeof depId === 'number' ? depId : parseInt(depId, 10); - - // It's a reference to a subtask in the same task - if (numericId < 100) { - const fullSubtaskId = `${task.id}.${numericId}`; - - // Check for self-dependency - if (fullSubtaskId === subtaskId) { - log('info', `Removing self-dependency from subtask ${subtaskId}`); - return false; - } - - if (!validSubtaskIds.has(fullSubtaskId)) { - log('info', `Removing invalid subtask dependency from subtask ${subtaskId}: ${numericId}`); - return false; - } - - return true; - } - - // It's a reference to another task - if (!validTaskIds.has(numericId)) { - log('info', `Removing invalid task dependency from subtask ${subtaskId}: ${numericId}`); - return false; - } - - return true; - }); - - // Check if we fixed anything - if (subtask.dependencies.length < originalLength) { - totalFixed += (originalLength - subtask.dependencies.length); - } - }); - }); - - // After fixing invalid dependencies, detect and fix circular dependencies - log('info', 'Checking for circular dependencies between subtasks...'); - - // For each subtask, check if there are circular dependencies - for (const [subtaskId, dependencies] of subtaskDependencyMap.entries()) { - const visited = new Set(); - const recursionStack = new Set(); - - // Clean up dependency map first - remove any non-existent dependencies - subtaskDependencyMap.set(subtaskId, dependencies.filter(depId => { - if (depId.includes('.')) { - return validSubtaskIds.has(depId); - } - return validTaskIds.has(Number(depId)); - })); - - // Detect cycles - const cycleEdges = findCycles(subtaskId, subtaskDependencyMap, visited, recursionStack); - - // Break cycles by removing dependencies - if (cycleEdges.length > 0) { - // Extract the task ID and subtask ID - const [taskId, subtaskNum] = subtaskId.split('.').map(part => Number(part)); - const task = data.tasks.find(t => t.id === taskId); - - if (task && task.subtasks) { - const subtask = task.subtasks.find(st => st.id === subtaskNum); - - if (subtask && subtask.dependencies) { - // Filter out dependencies that cause cycles - const originalLength = subtask.dependencies.length; - - // Convert cycleEdges to the format used in the task data - const edgesToRemove = cycleEdges.map(edge => { - if (edge.includes('.')) { - const [depTaskId, depSubtaskId] = edge.split('.').map(part => Number(part)); - - // If it's a subtask in the same task, return just the subtask ID as a number - if (depTaskId === taskId) { - return depSubtaskId; - } - - // Otherwise, return the full subtask ID as a string - return edge; // Full subtask ID string - } - - // If it's a task ID, return as a number - return Number(edge); // Task ID - }); - - // Remove the dependencies that cause cycles - subtask.dependencies = subtask.dependencies.filter(depId => { - const normalizedDepId = typeof depId === 'number' && depId < 100 - ? `${taskId}.${depId}` - : String(depId); - - if (edgesToRemove.includes(depId) || edgesToRemove.includes(normalizedDepId)) { - log('info', `Breaking circular dependency: Removing ${normalizedDepId} from ${subtaskId}`); - return false; - } - return true; - }); - - // Count fixed circular dependencies - const fixed = originalLength - subtask.dependencies.length; - totalCircularDepsFixed += fixed; - - // Also update the dependency map - subtaskDependencyMap.set(subtaskId, subtask.dependencies.map(depId => { - if (typeof depId === 'string' && depId.includes('.')) { - return depId; - } else if (typeof depId === 'number' && depId < 100) { - return `${taskId}.${depId}`; - } - return String(depId); - })); - } - } - } - } - - // Output summary of fixes - const totalFixedAll = totalFixed + totalCircularDepsFixed + totalDuplicatesRemoved; - if (totalFixedAll > 0) { - log('success', `Fixed ${totalFixed} invalid dependencies, ${totalCircularDepsFixed} circular dependencies, and ${totalDuplicatesRemoved} duplicate dependencies in tasks.json`); - writeJSON(tasksPath, data); - } else { - log('info', 'No invalid, circular, or duplicate subtask dependencies found in tasks.json'); - } - - return totalFixedAll; -} - -/** - * 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)) { - // Find the position of the dependency in the path - const cycleStartIndex = path.indexOf(depId); - // The last edge in the cycle is what we want to remove - const cycleEdges = path.slice(cycleStartIndex); - // We'll remove the last edge in the cycle (the one that points back) - cyclesToBreak.push(depId); - } - } - - // Remove the node from recursion stack before returning - recursionStack.delete(subtaskId); - - return cyclesToBreak; -} - -/** - * Validate and fix dependencies across all tasks and subtasks - * This function is designed to be called after any task modification - * @param {Object} tasksData - The tasks data object with tasks array - * @param {string} tasksPath - Optional path to save the changes - * @returns {boolean} - True if any changes were made - */ -function validateAndFixDependencies(tasksData, tasksPath = null) { - if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { - log('error', 'Invalid tasks data'); - return false; - } - - log('debug', 'Validating and fixing dependencies...'); - - let changesDetected = false; - - // 1. Remove duplicate dependencies from tasks and subtasks - const hasDuplicates = removeDuplicateDependencies(tasksData); - if (hasDuplicates) changesDetected = true; - - // 2. Remove invalid task dependencies (non-existent tasks) - const validationChanges = validateTaskDependencies(tasksData.tasks); - if (validationChanges) changesDetected = true; - - // 3. Clean up subtask dependencies - const subtaskChanges = cleanupSubtaskDependencies(tasksData); - if (subtaskChanges) changesDetected = true; - - // 4. Ensure at least one subtask has no dependencies in each task - const noDepChanges = ensureAtLeastOneIndependentSubtask(tasksData); - if (noDepChanges) changesDetected = true; - - // Save changes if needed - if (tasksPath && changesDetected) { - try { - writeJSON(tasksPath, tasksData); - log('debug', 'Saved dependency fixes to tasks.json'); - } catch (error) { - log('error', 'Failed to save dependency fixes to tasks.json', error); - } - } - - return changesDetected; -} - -/** - * Remove duplicate dependencies from tasks and subtasks - * @param {Object} tasksData - The tasks data object with tasks array - * @returns {boolean} - True if any changes were made - */ -function removeDuplicateDependencies(tasksData) { - if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { - return false; - } - - let changesDetected = false; - - tasksData.tasks.forEach(task => { - // Remove duplicates from main task dependencies - if (task.dependencies && Array.isArray(task.dependencies)) { - const uniqueDeps = new Set(); - const originalLength = task.dependencies.length; - - task.dependencies = task.dependencies.filter(depId => { - const depIdStr = String(depId); - if (uniqueDeps.has(depIdStr)) { - log('debug', `Removing duplicate dependency from task ${task.id}: ${depId}`); - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - - if (task.dependencies.length < originalLength) { - changesDetected = true; - } - } - - // Remove duplicates from subtask dependencies - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach(subtask => { - if (subtask.dependencies && Array.isArray(subtask.dependencies)) { - const uniqueDeps = new Set(); - const originalLength = subtask.dependencies.length; - - subtask.dependencies = subtask.dependencies.filter(depId => { - // Convert to string for comparison, handling special case for subtask references - let depIdStr = String(depId); - - // For numeric IDs that are likely subtask references in the same parent task - if (typeof depId === 'number' && depId < 100) { - depIdStr = `${task.id}.${depId}`; - } - - if (uniqueDeps.has(depIdStr)) { - log('debug', `Removing duplicate dependency from subtask ${task.id}.${subtask.id}: ${depId}`); - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - - if (subtask.dependencies.length < originalLength) { - changesDetected = true; - } - } - }); - } - }); - - return changesDetected; -} - -/** - * Clean up subtask dependencies by removing references to non-existent subtasks/tasks - * @param {Object} tasksData - The tasks data object with tasks array - * @returns {boolean} - True if any changes were made - */ -function cleanupSubtaskDependencies(tasksData) { - if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { - return false; - } - - log('debug', 'Cleaning up subtask dependencies...'); - - let changesDetected = false; - let duplicatesRemoved = 0; - - // Create validity maps for fast lookup - const validTaskIds = new Set(tasksData.tasks.map(t => t.id)); - const validSubtaskIds = new Set(); - - // Create a dependency map for cycle detection - const subtaskDependencyMap = new Map(); - - // Populate the validSubtaskIds set - tasksData.tasks.forEach(task => { - if (task.subtasks && Array.isArray(task.subtasks)) { - task.subtasks.forEach(subtask => { - validSubtaskIds.add(`${task.id}.${subtask.id}`); - }); - } - }); - - // Clean up each task's subtasks - tasksData.tasks.forEach(task => { - if (!task.subtasks || !Array.isArray(task.subtasks)) { - return; - } - - task.subtasks.forEach(subtask => { - if (!subtask.dependencies || !Array.isArray(subtask.dependencies)) { - return; - } - - const originalLength = subtask.dependencies.length; - const subtaskId = `${task.id}.${subtask.id}`; - - // First remove duplicate dependencies - const uniqueDeps = new Set(); - subtask.dependencies = subtask.dependencies.filter(depId => { - // Convert to string for comparison, handling special case for subtask references - let depIdStr = String(depId); - - // For numeric IDs that are likely subtask references in the same parent task - if (typeof depId === 'number' && depId < 100) { - depIdStr = `${task.id}.${depId}`; - } - - if (uniqueDeps.has(depIdStr)) { - log('debug', `Removing duplicate dependency from subtask ${subtaskId}: ${depId}`); - duplicatesRemoved++; - return false; - } - uniqueDeps.add(depIdStr); - return true; - }); - - // Then filter invalid dependencies - subtask.dependencies = subtask.dependencies.filter(depId => { - // Handle string dependencies with dot notation - if (typeof depId === 'string' && depId.includes('.')) { - if (!validSubtaskIds.has(depId)) { - log('debug', `Removing invalid subtask dependency from ${subtaskId}: ${depId}`); - return false; - } - if (depId === subtaskId) { - log('debug', `Removing self-dependency from ${subtaskId}`); - return false; - } - return true; - } - - // Handle numeric dependencies - const numericId = typeof depId === 'number' ? depId : parseInt(depId, 10); - - // Small numbers likely refer to subtasks in the same task - if (numericId < 100) { - const fullSubtaskId = `${task.id}.${numericId}`; - - if (fullSubtaskId === subtaskId) { - log('debug', `Removing self-dependency from ${subtaskId}`); - return false; - } - - if (!validSubtaskIds.has(fullSubtaskId)) { - log('debug', `Removing invalid subtask dependency from ${subtaskId}: ${numericId}`); - return false; - } - - return true; - } - - // Otherwise it's a task reference - if (!validTaskIds.has(numericId)) { - log('debug', `Removing invalid task dependency from ${subtaskId}: ${numericId}`); - return false; - } - - return true; - }); - - if (subtask.dependencies.length < originalLength) { - changesDetected = true; - } - - // Build dependency map for cycle detection - subtaskDependencyMap.set(subtaskId, subtask.dependencies.map(depId => { - if (typeof depId === 'string' && depId.includes('.')) { - return depId; - } else if (typeof depId === 'number' && depId < 100) { - return `${task.id}.${depId}`; - } - return String(depId); - })); - }); - }); - - // Break circular dependencies in subtasks - tasksData.tasks.forEach(task => { - if (!task.subtasks || !Array.isArray(task.subtasks)) { - return; - } - - task.subtasks.forEach(subtask => { - const subtaskId = `${task.id}.${subtask.id}`; - - // Skip if no dependencies - if (!subtask.dependencies || !Array.isArray(subtask.dependencies) || subtask.dependencies.length === 0) { - return; - } - - // Detect cycles for this subtask - const visited = new Set(); - const recursionStack = new Set(); - const cyclesToBreak = findCycles(subtaskId, subtaskDependencyMap, visited, recursionStack); - - if (cyclesToBreak.length > 0) { - const originalLength = subtask.dependencies.length; - - // Format cycle paths for removal - const edgesToRemove = cyclesToBreak.map(edge => { - if (edge.includes('.')) { - const [depTaskId, depSubtaskId] = edge.split('.').map(Number); - if (depTaskId === task.id) { - return depSubtaskId; // Return just subtask ID if in the same task - } - return edge; // Full subtask ID string - } - return Number(edge); // Task ID - }); - - // Remove dependencies that cause cycles - subtask.dependencies = subtask.dependencies.filter(depId => { - const normalizedDepId = typeof depId === 'number' && depId < 100 - ? `${task.id}.${depId}` - : String(depId); - - if (edgesToRemove.includes(depId) || edgesToRemove.includes(normalizedDepId)) { - log('debug', `Breaking circular dependency: Removing ${normalizedDepId} from ${subtaskId}`); - return false; - } - return true; - }); - - if (subtask.dependencies.length < originalLength) { - changesDetected = true; - } - } - }); - }); - - if (changesDetected) { - log('debug', `Cleaned up subtask dependencies (removed ${duplicatesRemoved} duplicates and fixed circular references)`); - } - - return changesDetected; -} - -/** - * Ensure at least one subtask in each task has no dependencies - * @param {Object} tasksData - The tasks data object with tasks array - * @returns {boolean} - True if any changes were made - */ -function ensureAtLeastOneIndependentSubtask(tasksData) { - if (!tasksData || !tasksData.tasks || !Array.isArray(tasksData.tasks)) { - return false; - } - - let changesDetected = false; - - tasksData.tasks.forEach(task => { - if (!task.subtasks || !Array.isArray(task.subtasks) || task.subtasks.length === 0) { - return; - } - - // Check if any subtask has no dependencies - const hasIndependentSubtask = task.subtasks.some(st => - !st.dependencies || !Array.isArray(st.dependencies) || st.dependencies.length === 0 - ); - - if (!hasIndependentSubtask) { - // Find the first subtask and clear its dependencies - if (task.subtasks.length > 0) { - const firstSubtask = task.subtasks[0]; - log('debug', `Ensuring at least one independent subtask: Clearing dependencies for subtask ${task.id}.${firstSubtask.id}`); - firstSubtask.dependencies = []; - changesDetected = true; - } - } - }); - - return changesDetected; -} \ No newline at end of file diff --git a/templates/dev_workflow.mdc b/templates/dev_workflow.mdc deleted file mode 100644 index 8a599186..00000000 --- a/templates/dev_workflow.mdc +++ /dev/null @@ -1,221 +0,0 @@ ---- -description: Guide for using meta-development script (scripts/dev.js) to manage task-driven development workflows -globs: **/* -alwaysApply: true ---- - -- **Development Workflow Process** - - Start new projects by running `node scripts/dev.js parse-prd --input=` to generate initial tasks.json - - Begin coding sessions with `node scripts/dev.js list` to see current tasks, status, and IDs - - Analyze task complexity with `node scripts/dev.js analyze-complexity --research` before breaking down tasks - - Select tasks based on dependencies (all marked 'done'), priority level, and ID order - - Clarify tasks by checking task files in tasks/ directory or asking for user input - - View specific task details using `node scripts/dev.js show --id=` to understand implementation requirements - - Break down complex tasks using `node scripts/dev.js expand --id=` with appropriate flags - - Clear existing subtasks if needed using `node scripts/dev.js clear-subtasks --id=` before regenerating - - Implement code following task details, dependencies, and project standards - - Verify tasks according to test strategies before marking as complete - - Mark completed tasks with `node scripts/dev.js set-status --id= --status=done` - - Update dependent tasks when implementation differs from original plan - - Generate task files with `node scripts/dev.js generate` after updating tasks.json - - Respect dependency chains and task priorities when selecting work - - Report progress regularly using the list command - -- **Task Complexity Analysis** - - Run `node scripts/dev.js analyze-complexity --research` for comprehensive analysis - - Review complexity report in scripts/task-complexity-report.json - - Focus on tasks with highest complexity scores (8-10) for detailed breakdown - - Use analysis results to determine appropriate subtask allocation - - Note that reports are automatically used by the expand command - -- **Task Breakdown Process** - - For tasks with complexity analysis, use `node scripts/dev.js expand --id=` - - Otherwise use `node scripts/dev.js expand --id= --subtasks=` - - Add `--research` flag to leverage Perplexity AI for research-backed expansion - - Use `--prompt=""` to provide additional context when needed - - Review and adjust generated subtasks as necessary - - Use `--all` flag to expand multiple pending tasks at once - - If subtasks need regeneration, clear them first with `clear-subtasks` command - -- **Implementation Drift Handling** - - When implementation differs significantly from planned approach - - When future tasks need modification due to current implementation choices - - When new dependencies or requirements emerge - - Call `node scripts/dev.js update --from= --prompt=""` to update tasks.json - -- **Task Status Management** - - Use 'pending' for tasks ready to be worked on - - Use 'done' for completed and verified tasks - - Use 'deferred' for postponed tasks - - Add custom status values as needed for project-specific workflows - -- **Task File Format Reference** - ``` - # Task ID: - # Title: - # Status: <status> - # Dependencies: <comma-separated list of dependency IDs> - # Priority: <priority> - # Description: <brief description> - # Details: - <detailed implementation notes> - - # Test Strategy: - <verification approach> - ``` - -- **Command Reference: parse-prd** - - Syntax: `node scripts/dev.js parse-prd --input=<prd-file.txt>` - - Description: Parses a PRD document and generates a tasks.json file with structured tasks - - Parameters: - - `--input=<file>`: Path to the PRD text file (default: sample-prd.txt) - - Example: `node scripts/dev.js parse-prd --input=requirements.txt` - - Notes: Will overwrite existing tasks.json file. Use with caution. - -- **Command Reference: update** - - Syntax: `node scripts/dev.js update --from=<id> --prompt="<prompt>"` - - Description: Updates tasks with ID >= specified ID based on the provided prompt - - Parameters: - - `--from=<id>`: Task ID from which to start updating (required) - - `--prompt="<text>"`: Explanation of changes or new context (required) - - Example: `node scripts/dev.js update --from=4 --prompt="Now we are using Express instead of Fastify."` - - Notes: Only updates tasks not marked as 'done'. Completed tasks remain unchanged. - -- **Command Reference: generate** - - Syntax: `node scripts/dev.js generate` - - Description: Generates individual task files in tasks/ directory based on tasks.json - - Parameters: None - - Example: `node scripts/dev.js generate` - - Notes: Overwrites existing task files. Creates tasks/ directory if needed. - -- **Command Reference: set-status** - - Syntax: `node scripts/dev.js set-status --id=<id> --status=<status>` - - Description: Updates the status of a specific task in tasks.json - - Parameters: - - `--id=<id>`: ID of the task to update (required) - - `--status=<status>`: New status value (required) - - Example: `node scripts/dev.js set-status --id=3 --status=done` - - Notes: Common values are 'done', 'pending', and 'deferred', but any string is accepted. - -- **Command Reference: list** - - Syntax: `node scripts/dev.js list` - - Description: Lists all tasks in tasks.json with IDs, titles, and status - - Parameters: None - - Example: `node scripts/dev.js list` - - Notes: Provides quick overview of project progress. Use at start of sessions. - -- **Command Reference: expand** - - Syntax: `node scripts/dev.js expand --id=<id> [--num=<number>] [--research] [--prompt="<context>"]` - - Description: Expands a task with subtasks for detailed implementation - - Parameters: - - `--id=<id>`: ID of task to expand (required unless using --all) - - `--all`: Expand all pending tasks, prioritized by complexity - - `--num=<number>`: Number of subtasks to generate (default: from complexity report) - - `--research`: Use Perplexity AI for research-backed generation - - `--prompt="<text>"`: Additional context for subtask generation - - `--force`: Regenerate subtasks even for tasks that already have them - - Example: `node scripts/dev.js expand --id=3 --num=5 --research --prompt="Focus on security aspects"` - - Notes: Uses complexity report recommendations if available. - -- **Command Reference: analyze-complexity** - - Syntax: `node scripts/dev.js analyze-complexity [options]` - - Description: Analyzes task complexity and generates expansion recommendations - - Parameters: - - `--output=<file>, -o`: Output file path (default: scripts/task-complexity-report.json) - - `--model=<model>, -m`: Override LLM model to use - - `--threshold=<number>, -t`: Minimum score for expansion recommendation (default: 5) - - `--file=<path>, -f`: Use alternative tasks.json file - - `--research, -r`: Use Perplexity AI for research-backed analysis - - Example: `node scripts/dev.js analyze-complexity --research` - - Notes: Report includes complexity scores, recommended subtasks, and tailored prompts. - -- **Command Reference: clear-subtasks** - - Syntax: `node scripts/dev.js clear-subtasks --id=<id>` - - Description: Removes subtasks from specified tasks to allow regeneration - - Parameters: - - `--id=<id>`: ID or comma-separated IDs of tasks to clear subtasks from - - `--all`: Clear subtasks from all tasks - - Examples: - - `node scripts/dev.js clear-subtasks --id=3` - - `node scripts/dev.js clear-subtasks --id=1,2,3` - - `node scripts/dev.js clear-subtasks --all` - - Notes: - - Task files are automatically regenerated after clearing subtasks - - Can be combined with expand command to immediately generate new subtasks - - Works with both parent tasks and individual subtasks - -- **Task Structure Fields** - - **id**: Unique identifier for the task (Example: `1`) - - **title**: Brief, descriptive title (Example: `"Initialize Repo"`) - - **description**: Concise summary of what the task involves (Example: `"Create a new repository, set up initial structure."`) - - **status**: Current state of the task (Example: `"pending"`, `"done"`, `"deferred"`) - - **dependencies**: IDs of prerequisite tasks (Example: `[1, 2]`) - - Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) - - This helps quickly identify which prerequisite tasks are blocking work - - **priority**: Importance level (Example: `"high"`, `"medium"`, `"low"`) - - **details**: In-depth implementation instructions (Example: `"Use GitHub client ID/secret, handle callback, set session token."`) - - **testStrategy**: Verification approach (Example: `"Deploy and call endpoint to confirm 'Hello World' response."`) - - **subtasks**: List of smaller, more specific tasks (Example: `[{"id": 1, "title": "Configure OAuth", ...}]`) - -- **Environment Variables Configuration** - - **ANTHROPIC_API_KEY** (Required): Your Anthropic API key for Claude (Example: `ANTHROPIC_API_KEY=sk-ant-api03-...`) - - **MODEL** (Default: `"claude-3-7-sonnet-20250219"`): Claude model to use (Example: `MODEL=claude-3-opus-20240229`) - - **MAX_TOKENS** (Default: `"4000"`): Maximum tokens for responses (Example: `MAX_TOKENS=8000`) - - **TEMPERATURE** (Default: `"0.7"`): Temperature for model responses (Example: `TEMPERATURE=0.5`) - - **DEBUG** (Default: `"false"`): Enable debug logging (Example: `DEBUG=true`) - - **LOG_LEVEL** (Default: `"info"`): Console output level (Example: `LOG_LEVEL=debug`) - - **DEFAULT_SUBTASKS** (Default: `"3"`): Default subtask count (Example: `DEFAULT_SUBTASKS=5`) - - **DEFAULT_PRIORITY** (Default: `"medium"`): Default priority (Example: `DEFAULT_PRIORITY=high`) - - **PROJECT_NAME** (Default: `"MCP SaaS MVP"`): Project name in metadata (Example: `PROJECT_NAME=My Awesome Project`) - - **PROJECT_VERSION** (Default: `"1.0.0"`): Version in metadata (Example: `PROJECT_VERSION=2.1.0`) - - **PERPLEXITY_API_KEY**: For research-backed features (Example: `PERPLEXITY_API_KEY=pplx-...`) - - **PERPLEXITY_MODEL** (Default: `"sonar-medium-online"`): Perplexity model (Example: `PERPLEXITY_MODEL=sonar-large-online`) - -- **Determining the Next Task** - - Run `node scripts/dev.js next` to show the next task to work on - - The next command identifies tasks with all dependencies satisfied - - Tasks are prioritized by priority level, dependency count, and ID - - The command shows comprehensive task information including: - - Basic task details and description - - Implementation details - - Subtasks (if they exist) - - Contextual suggested actions - - Recommended before starting any new development work - - Respects your project's dependency structure - - Ensures tasks are completed in the appropriate sequence - - Provides ready-to-use commands for common task actions - -- **Viewing Specific Task Details** - - Run `node scripts/dev.js show --id=<id>` or `node scripts/dev.js show <id>` to view a specific task - - Use dot notation for subtasks: `node scripts/dev.js show 1.2` (shows subtask 2 of task 1) - - Displays comprehensive information similar to the next command, but for a specific task - - For parent tasks, shows all subtasks and their current status - - For subtasks, shows parent task information and relationship - - Provides contextual suggested actions appropriate for the specific task - - Useful for examining task details before implementation or checking status - -- **Managing Task Dependencies** - - Use `node scripts/dev.js add-dependency --id=<id> --depends-on=<id>` to add a dependency - - Use `node scripts/dev.js remove-dependency --id=<id> --depends-on=<id>` to remove a dependency - - The system prevents circular dependencies and duplicate dependency entries - - Dependencies are checked for existence before being added or removed - - Task files are automatically regenerated after dependency changes - - Dependencies are visualized with status indicators in task listings and files - -- **Command Reference: add-dependency** - - Syntax: `node scripts/dev.js add-dependency --id=<id> --depends-on=<id>` - - Description: Adds a dependency relationship between two tasks - - Parameters: - - `--id=<id>`: ID of task that will depend on another task (required) - - `--depends-on=<id>`: ID of task that will become a dependency (required) - - Example: `node scripts/dev.js add-dependency --id=22 --depends-on=21` - - Notes: Prevents circular dependencies and duplicates; updates task files automatically - -- **Command Reference: remove-dependency** - - Syntax: `node scripts/dev.js remove-dependency --id=<id> --depends-on=<id>` - - Description: Removes a dependency relationship between two tasks - - Parameters: - - `--id=<id>`: ID of task to remove dependency from (required) - - `--depends-on=<id>`: ID of task to remove as a dependency (required) - - Example: `node scripts/dev.js remove-dependency --id=22 --depends-on=21` - - Notes: Checks if dependency actually exists; updates task files automatically diff --git a/templates/env.example b/templates/env.example deleted file mode 100644 index 0dcb9549..00000000 --- a/templates/env.example +++ /dev/null @@ -1,14 +0,0 @@ -# Required -ANTHROPIC_API_KEY=your-api-key-here # Format: sk-ant-api03-... -PERPLEXITY_API_KEY=pplx-abcde - -# Optional - defaults shown -MODEL=claude-3-7-sonnet-20250219 # Recommended models: claude-3-7-sonnet-20250219, claude-3-opus-20240229 -MAX_TOKENS=4000 # Maximum tokens for model responses -TEMPERATURE=0.7 # Temperature for model responses (0.0-1.0) -DEBUG=false # Enable debug logging (true/false) -LOG_LEVEL=info # Log level (debug, info, warn, error) -DEFAULT_SUBTASKS=3 # Default number of subtasks when expanding -DEFAULT_PRIORITY=medium # Default priority for generated tasks (high, medium, low) -PROJECT_NAME={{projectName}} # Project name for tasks.json metadata -PROJECT_VERSION={{projectVersion}} # Project version for tasks.json metadata \ No newline at end of file diff --git a/templates/example_prd.txt b/templates/example_prd.txt deleted file mode 100644 index 194114d0..00000000 --- a/templates/example_prd.txt +++ /dev/null @@ -1,47 +0,0 @@ -<context> -# Overview -[Provide a high-level overview of your product here. Explain what problem it solves, who it's for, and why it's valuable.] - -# Core Features -[List and describe the main features of your product. For each feature, include: -- What it does -- Why it's important -- How it works at a high level] - -# User Experience -[Describe the user journey and experience. Include: -- User personas -- Key user flows -- UI/UX considerations] -</context> -<PRD> -# Technical Architecture -[Outline the technical implementation details: -- System components -- Data models -- APIs and integrations -- Infrastructure requirements] - -# Development Roadmap -[Break down the development process into phases: -- MVP requirements -- Future enhancements -- Do not think about timelines whatsoever -- all that matters is scope and detailing exactly what needs to be build in each phase so it can later be cut up into tasks] - -# Logical Dependency Chain -[Define the logical order of development: -- Which features need to be built first (foundation) -- Getting as quickly as possible to something usable/visible front end that works -- Properly pacing and scoping each feature so it is atomic but can also be built upon and improved as development approaches] - -# Risks and Mitigations -[Identify potential risks and how they'll be addressed: -- Technical challenges -- Figuring out the MVP that we can build upon -- Resource constraints] - -# Appendix -[Include any additional information: -- Research findings -- Technical specifications] -</PRD> \ No newline at end of file diff --git a/templates/gitignore b/templates/gitignore deleted file mode 100644 index bea8734a..00000000 --- a/templates/gitignore +++ /dev/null @@ -1,29 +0,0 @@ -# Logs -logs -*.log -npm-debug.log* -yarn-debug.log* -yarn-error.log* -dev-debug.log - -# Dependency directories -node_modules/ - -# Environment variables -.env - -# Editor directories and files -.idea -.vscode -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? - -# OS specific -.DS_Store - -# Task files -tasks.json -tasks/ \ No newline at end of file diff --git a/templates/scripts_README.md b/templates/scripts_README.md deleted file mode 100644 index f96062ad..00000000 --- a/templates/scripts_README.md +++ /dev/null @@ -1,376 +0,0 @@ -# Meta-Development Script - -This folder contains a **meta-development script** (`dev.js`) and related utilities that manage tasks for an AI-driven or traditional software development workflow. The script revolves around a `tasks.json` file, which holds an up-to-date list of development tasks. - -## Overview - -In an AI-driven development process—particularly with tools like [Cursor](https://www.cursor.so/)—it's beneficial to have a **single source of truth** for tasks. This script allows you to: - -1. **Parse** a PRD or requirements document (`.txt`) to initialize a set of tasks (`tasks.json`). -2. **List** all existing tasks (IDs, statuses, titles). -3. **Update** tasks to accommodate new prompts or architecture changes (useful if you discover "implementation drift"). -4. **Generate** individual task files (e.g., `task_001.txt`) for easy reference or to feed into an AI coding workflow. -5. **Set task status**—mark tasks as `done`, `pending`, or `deferred` based on progress. -6. **Expand** tasks with subtasks—break down complex tasks into smaller, more manageable subtasks. -7. **Research-backed subtask generation**—use Perplexity AI to generate more informed and contextually relevant subtasks. -8. **Clear subtasks**—remove subtasks from specified tasks to allow regeneration or restructuring. -9. **Show task details**—display detailed information about a specific task and its subtasks. - -## Configuration - -The script can be configured through environment variables in a `.env` file at the root of the project: - -### Required Configuration -- `ANTHROPIC_API_KEY`: Your Anthropic API key for Claude - -### Optional Configuration -- `MODEL`: Specify which Claude model to use (default: "claude-3-7-sonnet-20250219") -- `MAX_TOKENS`: Maximum tokens for model responses (default: 4000) -- `TEMPERATURE`: Temperature for model responses (default: 0.7) -- `PERPLEXITY_API_KEY`: Your Perplexity API key for research-backed subtask generation -- `PERPLEXITY_MODEL`: Specify which Perplexity model to use (default: "sonar-medium-online") -- `DEBUG`: Enable debug logging (default: false) -- `LOG_LEVEL`: Log level - debug, info, warn, error (default: info) -- `DEFAULT_SUBTASKS`: Default number of subtasks when expanding (default: 3) -- `DEFAULT_PRIORITY`: Default priority for generated tasks (default: medium) -- `PROJECT_NAME`: Override default project name in tasks.json -- `PROJECT_VERSION`: Override default version in tasks.json - -## How It Works - -1. **`tasks.json`**: - - A JSON file at the project root containing an array of tasks (each with `id`, `title`, `description`, `status`, etc.). - - The `meta` field can store additional info like the project's name, version, or reference to the PRD. - - Tasks can have `subtasks` for more detailed implementation steps. - - Dependencies are displayed with status indicators (✅ for completed, ⏱️ for pending) to easily track progress. - -2. **Script Commands** - You can run the script via: - - ```bash - node scripts/dev.js [command] [options] - ``` - - Available commands: - - - `parse-prd`: Generate tasks from a PRD document - - `list`: Display all tasks with their status - - `update`: Update tasks based on new information - - `generate`: Create individual task files - - `set-status`: Change a task's status - - `expand`: Add subtasks to a task or all tasks - - `clear-subtasks`: Remove subtasks from specified tasks - - `next`: Determine the next task to work on based on dependencies - - `show`: Display detailed information about a specific task - - Run `node scripts/dev.js` without arguments to see detailed usage information. - -## Listing Tasks - -The `list` command allows you to view all tasks and their status: - -```bash -# List all tasks -node scripts/dev.js list - -# List tasks with a specific status -node scripts/dev.js list --status=pending - -# List tasks and include their subtasks -node scripts/dev.js list --with-subtasks - -# List tasks with a specific status and include their subtasks -node scripts/dev.js list --status=pending --with-subtasks -``` - -## Updating Tasks - -The `update` command allows you to update tasks based on new information or implementation changes: - -```bash -# Update tasks starting from ID 4 with a new prompt -node scripts/dev.js update --from=4 --prompt="Refactor tasks from ID 4 onward to use Express instead of Fastify" - -# Update all tasks (default from=1) -node scripts/dev.js update --prompt="Add authentication to all relevant tasks" - -# Specify a different tasks file -node scripts/dev.js update --file=custom-tasks.json --from=5 --prompt="Change database from MongoDB to PostgreSQL" -``` - -Notes: -- The `--prompt` parameter is required and should explain the changes or new context -- Only tasks that aren't marked as 'done' will be updated -- Tasks with ID >= the specified --from value will be updated - -## Setting Task Status - -The `set-status` command allows you to change a task's status: - -```bash -# Mark a task as done -node scripts/dev.js set-status --id=3 --status=done - -# Mark a task as pending -node scripts/dev.js set-status --id=4 --status=pending - -# Mark a specific subtask as done -node scripts/dev.js set-status --id=3.1 --status=done - -# Mark multiple tasks at once -node scripts/dev.js set-status --id=1,2,3 --status=done -``` - -Notes: -- When marking a parent task as "done", all of its subtasks will automatically be marked as "done" as well -- Common status values are 'done', 'pending', and 'deferred', but any string is accepted -- You can specify multiple task IDs by separating them with commas -- Subtask IDs are specified using the format `parentId.subtaskId` (e.g., `3.1`) -- Dependencies are updated to show completion status (✅ for completed, ⏱️ for pending) throughout the system - -## Expanding Tasks - -The `expand` command allows you to break down tasks into subtasks for more detailed implementation: - -```bash -# Expand a specific task with 3 subtasks (default) -node scripts/dev.js expand --id=3 - -# Expand a specific task with 5 subtasks -node scripts/dev.js expand --id=3 --num=5 - -# Expand a task with additional context -node scripts/dev.js expand --id=3 --prompt="Focus on security aspects" - -# Expand all pending tasks that don't have subtasks -node scripts/dev.js expand --all - -# Force regeneration of subtasks for all pending tasks -node scripts/dev.js expand --all --force - -# Use Perplexity AI for research-backed subtask generation -node scripts/dev.js expand --id=3 --research - -# Use Perplexity AI for research-backed generation on all pending tasks -node scripts/dev.js expand --all --research -``` - -## Clearing Subtasks - -The `clear-subtasks` command allows you to remove subtasks from specified tasks: - -```bash -# Clear subtasks from a specific task -node scripts/dev.js clear-subtasks --id=3 - -# Clear subtasks from multiple tasks -node scripts/dev.js clear-subtasks --id=1,2,3 - -# Clear subtasks from all tasks -node scripts/dev.js clear-subtasks --all -``` - -Notes: -- After clearing subtasks, task files are automatically regenerated -- This is useful when you want to regenerate subtasks with a different approach -- Can be combined with the `expand` command to immediately generate new subtasks -- Works with both parent tasks and individual subtasks - -## AI Integration - -The script integrates with two AI services: - -1. **Anthropic Claude**: Used for parsing PRDs, generating tasks, and creating subtasks. -2. **Perplexity AI**: Used for research-backed subtask generation when the `--research` flag is specified. - -The Perplexity integration uses the OpenAI client to connect to Perplexity's API, which provides enhanced research capabilities for generating more informed subtasks. If the Perplexity API is unavailable or encounters an error, the script will automatically fall back to using Anthropic's Claude. - -To use the Perplexity integration: -1. Obtain a Perplexity API key -2. Add `PERPLEXITY_API_KEY` to your `.env` file -3. Optionally specify `PERPLEXITY_MODEL` in your `.env` file (default: "sonar-medium-online") -4. Use the `--research` flag with the `expand` command - -## Logging - -The script supports different logging levels controlled by the `LOG_LEVEL` environment variable: -- `debug`: Detailed information, typically useful for troubleshooting -- `info`: Confirmation that things are working as expected (default) -- `warn`: Warning messages that don't prevent execution -- `error`: Error messages that might prevent execution - -When `DEBUG=true` is set, debug logs are also written to a `dev-debug.log` file in the project root. - -## Analyzing Task Complexity - -The `analyze-complexity` command allows you to automatically assess task complexity and generate expansion recommendations: - -```bash -# Analyze all tasks and generate expansion recommendations -node scripts/dev.js analyze-complexity - -# Specify a custom output file -node scripts/dev.js analyze-complexity --output=custom-report.json - -# Override the model used for analysis -node scripts/dev.js analyze-complexity --model=claude-3-opus-20240229 - -# Set a custom complexity threshold (1-10) -node scripts/dev.js analyze-complexity --threshold=6 - -# Use Perplexity AI for research-backed complexity analysis -node scripts/dev.js analyze-complexity --research -``` - -Notes: -- The command uses Claude to analyze each task's complexity (or Perplexity with --research flag) -- Tasks are scored on a scale of 1-10 -- Each task receives a recommended number of subtasks based on DEFAULT_SUBTASKS configuration -- The default output path is `scripts/task-complexity-report.json` -- Each task in the analysis includes a ready-to-use `expansionCommand` that can be copied directly to the terminal or executed programmatically -- Tasks with complexity scores below the threshold (default: 5) may not need expansion -- The research flag provides more contextual and informed complexity assessments - -### Integration with Expand Command - -The `expand` command automatically checks for and uses complexity analysis if available: - -```bash -# Expand a task, using complexity report recommendations if available -node scripts/dev.js expand --id=8 - -# Expand all tasks, prioritizing by complexity score if a report exists -node scripts/dev.js expand --all - -# Override recommendations with explicit values -node scripts/dev.js expand --id=8 --num=5 --prompt="Custom prompt" -``` - -When a complexity report exists: -- The `expand` command will use the recommended subtask count from the report (unless overridden) -- It will use the tailored expansion prompt from the report (unless a custom prompt is provided) -- When using `--all`, tasks are sorted by complexity score (highest first) -- The `--research` flag is preserved from the complexity analysis to expansion - -The output report structure is: -```json -{ - "meta": { - "generatedAt": "2023-06-15T12:34:56.789Z", - "tasksAnalyzed": 20, - "thresholdScore": 5, - "projectName": "Your Project Name", - "usedResearch": true - }, - "complexityAnalysis": [ - { - "taskId": 8, - "taskTitle": "Develop Implementation Drift Handling", - "complexityScore": 9.5, - "recommendedSubtasks": 6, - "expansionPrompt": "Create subtasks that handle detecting...", - "reasoning": "This task requires sophisticated logic...", - "expansionCommand": "node scripts/dev.js expand --id=8 --num=6 --prompt=\"Create subtasks...\" --research" - }, - // More tasks sorted by complexity score (highest first) - ] -} -``` - -## Finding the Next Task - -The `next` command helps you determine which task to work on next based on dependencies and status: - -```bash -# Show the next task to work on -node scripts/dev.js next - -# Specify a different tasks file -node scripts/dev.js next --file=custom-tasks.json -``` - -This command: - -1. Identifies all **eligible tasks** - pending or in-progress tasks whose dependencies are all satisfied (marked as done) -2. **Prioritizes** these eligible tasks by: - - Priority level (high > medium > low) - - Number of dependencies (fewer dependencies first) - - Task ID (lower ID first) -3. **Displays** comprehensive information about the selected task: - - Basic task details (ID, title, priority, dependencies) - - Detailed description and implementation details - - Subtasks if they exist -4. Provides **contextual suggested actions**: - - Command to mark the task as in-progress - - Command to mark the task as done when completed - - Commands for working with subtasks (update status or expand) - -This feature ensures you're always working on the most appropriate task based on your project's current state and dependency structure. - -## Showing Task Details - -The `show` command allows you to view detailed information about a specific task: - -```bash -# Show details for a specific task -node scripts/dev.js show 1 - -# Alternative syntax with --id option -node scripts/dev.js show --id=1 - -# Show details for a subtask -node scripts/dev.js show --id=1.2 - -# Specify a different tasks file -node scripts/dev.js show 3 --file=custom-tasks.json -``` - -This command: - -1. **Displays comprehensive information** about the specified task: - - Basic task details (ID, title, priority, dependencies, status) - - Full description and implementation details - - Test strategy information - - Subtasks if they exist -2. **Handles both regular tasks and subtasks**: - - For regular tasks, shows all subtasks and their status - - For subtasks, shows the parent task relationship -3. **Provides contextual suggested actions**: - - Commands to update the task status - - Commands for working with subtasks - - For subtasks, provides a link to view the parent task - -This command is particularly useful when you need to examine a specific task in detail before implementing it or when you want to check the status and details of a particular task. - -## Managing Task Dependencies - -The `add-dependency` and `remove-dependency` commands allow you to manage task dependencies: - -```bash -# Add a dependency to a task -node scripts/dev.js add-dependency --id=<id> --depends-on=<id> - -# Remove a dependency from a task -node scripts/dev.js remove-dependency --id=<id> --depends-on=<id> -``` - -These commands: - -1. **Allow precise dependency management**: - - Add dependencies between tasks with automatic validation - - Remove dependencies when they're no longer needed - - Update task files automatically after changes - -2. **Include validation checks**: - - Prevent circular dependencies (a task depending on itself) - - Prevent duplicate dependencies - - Verify that both tasks exist before adding/removing dependencies - - Check if dependencies exist before attempting to remove them - -3. **Provide clear feedback**: - - Success messages confirm when dependencies are added/removed - - Error messages explain why operations failed (if applicable) - -4. **Automatically update task files**: - - Regenerates task files to reflect dependency changes - - Ensures tasks and their files stay synchronized \ No newline at end of file diff --git a/templates/self_improve.mdc b/templates/self_improve.mdc deleted file mode 100644 index a7ea8f28..00000000 --- a/templates/self_improve.mdc +++ /dev/null @@ -1,73 +0,0 @@ ---- -description: Guidelines for continuously improving Cursor rules based on emerging code patterns and best practices. -globs: **/* -alwaysApply: true ---- - -- **Rule Improvement Triggers:** - - New code patterns not covered by existing rules - - Repeated similar implementations across files - - Common error patterns that could be prevented - - New libraries or tools being used consistently - - Emerging best practices in the codebase - -- **Analysis Process:** - - Compare new code with existing rules - - Identify patterns that should be standardized - - Look for references to external documentation - - Check for consistent error handling patterns - - Monitor test patterns and coverage - -- **Rule Updates:** - - **Add New Rules When:** - - A new technology/pattern is used in 3+ files - - Common bugs could be prevented by a rule - - Code reviews repeatedly mention the same feedback - - New security or performance patterns emerge - - - **Modify Existing Rules When:** - - Better examples exist in the codebase - - Additional edge cases are discovered - - Related rules have been updated - - Implementation details have changed - -- **Example Pattern Recognition:** - ```typescript - // If you see repeated patterns like: - const data = await prisma.user.findMany({ - select: { id: true, email: true }, - where: { status: 'ACTIVE' } - }); - - // Consider adding to [prisma.mdc](mdc:.cursor/rules/prisma.mdc): - // - Standard select fields - // - Common where conditions - // - Performance optimization patterns - ``` - -- **Rule Quality Checks:** - - Rules should be actionable and specific - - Examples should come from actual code - - References should be up to date - - Patterns should be consistently enforced - -- **Continuous Improvement:** - - Monitor code review comments - - Track common development questions - - Update rules after major refactors - - Add links to relevant documentation - - Cross-reference related rules - -- **Rule Deprecation:** - - Mark outdated patterns as deprecated - - Remove rules that no longer apply - - Update references to deprecated rules - - Document migration paths for old patterns - -- **Documentation Updates:** - - Keep examples synchronized with code - - Update references to external docs - - Maintain links between related rules - - Document breaking changes - -Follow [cursor_rules.mdc](mdc:.cursor/rules/cursor_rules.mdc) for proper rule formatting and structure. \ No newline at end of file