diff --git a/.changeset/better-seas-sit.md b/.changeset/better-seas-sit.md new file mode 100644 index 00000000..3fd2e576 --- /dev/null +++ b/.changeset/better-seas-sit.md @@ -0,0 +1,15 @@ +--- +'task-master-ai': minor +--- + +Added comprehensive Ollama model validation and interactive setup support + +- **Interactive Setup Enhancement**: Added "Custom Ollama model" option to `task-master models --setup`, matching the existing OpenRouter functionality +- **Live Model Validation**: When setting Ollama models, Taskmaster now validates against the local Ollama instance by querying `/api/tags` endpoint +- **Configurable Endpoints**: Uses the `ollamaBaseUrl` from `.taskmasterconfig` (with role-specific `baseUrl` overrides supported) +- **Robust Error Handling**: + - Detects when Ollama server is not running and provides clear error messages + - Validates model existence and lists available alternatives when model not found + - Graceful fallback behavior for connection issues +- **Full Platform Support**: Both MCP server tools and CLI commands support the new validation +- **Improved User Experience**: Clear feedback during model validation with informative success/error messages diff --git a/.changeset/cuddly-walls-clap.md b/.changeset/cuddly-walls-clap.md new file mode 100644 index 00000000..6d4f2993 --- /dev/null +++ b/.changeset/cuddly-walls-clap.md @@ -0,0 +1,9 @@ +--- +'task-master-ai': minor +--- + +Adds and updates supported AI models with costs: +- Added new OpenRouter models: GPT-4.1 series, O3, Codex Mini, Llama 4 Maverick, Llama 4 Scout, Qwen3-235b +- Added Mistral models: Devstral Small, Mistral Nemo +- Updated Ollama models with latest variants: Devstral, Qwen3, Mistral-small3.1, Llama3.3 +- Updated Gemini model to latest 2.5 Flash preview version diff --git a/.changeset/curly-melons-call.md b/.changeset/curly-melons-call.md new file mode 100644 index 00000000..828a747b --- /dev/null +++ b/.changeset/curly-melons-call.md @@ -0,0 +1,15 @@ +--- +'task-master-ai': minor +--- + +Add `--research` flag to parse-prd command, enabling enhanced task generation from PRD files. When used, Taskmaster leverages the research model to: + +- Research current technologies and best practices relevant to the project +- Identify technical challenges and security concerns not explicitly mentioned in the PRD +- Include specific library recommendations with version numbers +- Provide more detailed implementation guidance based on industry standards +- Create more accurate dependency relationships between tasks + +This results in higher quality, more actionable tasks with minimal additional effort. + +*NOTE* That this is an experimental feature. Research models don't typically do great at structured output. You may find some failures when using research mode, so please share your feedback so we can improve this. diff --git a/.changeset/dark-otters-sip.md b/.changeset/dark-otters-sip.md new file mode 100644 index 00000000..1a7e4923 --- /dev/null +++ b/.changeset/dark-otters-sip.md @@ -0,0 +1,5 @@ +--- +'task-master-ai': patch +--- + +Adjusts default main model model to Claude Sonnet 4. Adjusts default fallback to Claude Sonney 3.7" diff --git a/.changeset/fuzzy-worlds-wish.md b/.changeset/fuzzy-worlds-wish.md new file mode 100644 index 00000000..78bbfbba --- /dev/null +++ b/.changeset/fuzzy-worlds-wish.md @@ -0,0 +1,5 @@ +--- +'task-master-ai': patch +--- + +Adds llms-install.md to the root to enable AI agents to programmatically install the Taskmaster MCP server. This is specifically being introduced for the Cline MCP marketplace and will be adjusted over time for other MCP clients as needed. diff --git a/.changeset/gentle-parents-film.md b/.changeset/gentle-parents-film.md new file mode 100644 index 00000000..a5e30cd9 --- /dev/null +++ b/.changeset/gentle-parents-film.md @@ -0,0 +1,9 @@ +--- +'task-master-ai': minor +--- + +This change significantly enhances the `add-task` command's intelligence. When you add a new task, Taskmaster now automatically: + - Analyzes your existing tasks to find those most relevant to your new task's description. + - Provides the AI with detailed context from these relevant tasks. + +This results in newly created tasks being more accurately placed within your project's dependency structure, saving you time and any need to update tasks just for dependencies, all without significantly increasing AI costs. You'll get smarter, more connected tasks right from the start. diff --git a/.changeset/loud-bobcats-tap.md b/.changeset/loud-bobcats-tap.md new file mode 100644 index 00000000..7d0b1aae --- /dev/null +++ b/.changeset/loud-bobcats-tap.md @@ -0,0 +1,5 @@ +--- +'task-master-ai': patch +--- + +Adds AGENTS.md to power Claude Code integration more natively based on Anthropic's best practice and Claude-specific MCP client behaviours. Also adds in advanced workflows that tie Taskmaster commands together into one Claude workflow." diff --git a/.changeset/metal-forks-yawn.md b/.changeset/metal-forks-yawn.md new file mode 100644 index 00000000..f7c6e8bc --- /dev/null +++ b/.changeset/metal-forks-yawn.md @@ -0,0 +1,7 @@ +--- +'task-master-ai': minor +--- + +Enhance analyze-complexity to support analyzing specific task IDs. + - You can now analyze individual tasks or selected task groups by using the new `--id` option with comma-separated IDs, or `--from` and `--to` options to specify a range of tasks. + - The feature intelligently merges analysis results with existing reports, allowing incremental analysis while preserving previous results. diff --git a/.changeset/modern-parks-fly.md b/.changeset/modern-parks-fly.md new file mode 100644 index 00000000..6ac037b5 --- /dev/null +++ b/.changeset/modern-parks-fly.md @@ -0,0 +1,5 @@ +--- +'task-master-ai': patch +--- + +Fixes issue with force/append flag combinations for parse-prd. diff --git a/.changeset/petite-walls-learn.md b/.changeset/petite-walls-learn.md new file mode 100644 index 00000000..64400a6a --- /dev/null +++ b/.changeset/petite-walls-learn.md @@ -0,0 +1,5 @@ +--- +'task-master-ai': patch +--- + +You can now add tasks to a newly initialized project without having to parse a prd. This will automatically create the missing tasks.json file and create the first task. Lets you vibe if you want to vibe." diff --git a/.changeset/tall-mails-sing.md b/.changeset/tall-mails-sing.md new file mode 100644 index 00000000..3bcde266 --- /dev/null +++ b/.changeset/tall-mails-sing.md @@ -0,0 +1,5 @@ +--- +'task-master-ai': patch +--- + +Fixes an issue where the research fallback would attempt to make API calls without checking for a valid API key first. This ensures proper error handling when the main task generation and first fallback both fail. Closes #421 #519. diff --git a/.changeset/wild-seas-read.md b/.changeset/wild-seas-read.md new file mode 100644 index 00000000..945901ba --- /dev/null +++ b/.changeset/wild-seas-read.md @@ -0,0 +1,29 @@ +--- +'task-master-ai': minor +--- + +Add move command to enable moving tasks and subtasks within the task hierarchy. This new command supports moving standalone tasks to become subtasks, subtasks to become standalone tasks, and moving subtasks between different parents. The implementation handles circular dependencies, validation, and proper updating of parent-child relationships. + +**Usage:** +- CLI command: `task-master move --from= --to=` +- MCP tool: `move_task` with parameters: + - `from`: ID of task/subtask to move (e.g., "5" or "5.2") + - `to`: ID of destination (e.g., "7" or "7.3") + - `file` (optional): Custom path to tasks.json + +**Example scenarios:** +- Move task to become subtask: `--from="5" --to="7"` +- Move subtask to standalone task: `--from="5.2" --to="7"` +- Move subtask to different parent: `--from="5.2" --to="7.3"` +- Reorder subtask within same parent: `--from="5.2" --to="5.4"` +- Move multiple tasks at once: `--from="10,11,12" --to="16,17,18"` +- Move task to new ID: `--from="5" --to="25"` (creates a new task with ID 25) + +**Multiple Task Support:** +The command supports moving multiple tasks simultaneously by providing comma-separated lists for both `--from` and `--to` parameters. The number of source and destination IDs must match. This is particularly useful for resolving merge conflicts in task files when multiple team members have created tasks on different branches. + +**Validation Features:** +- Allows moving tasks to new, non-existent IDs (automatically creates placeholders) +- Prevents moving to existing task IDs that already contain content (to avoid overwriting) +- Validates source tasks exist before attempting to move them +- Ensures proper parent-child relationships are maintained diff --git a/.cursor/rules/dev_workflow.mdc b/.cursor/rules/dev_workflow.mdc index 003251d8..29f1f4c8 100644 --- a/.cursor/rules/dev_workflow.mdc +++ b/.cursor/rules/dev_workflow.mdc @@ -49,6 +49,7 @@ Task Master offers two primary ways to interact: - Maintain valid dependency structure with `add_dependency`/`remove_dependency` tools or `task-master add-dependency`/`remove-dependency` commands, `validate_dependencies` / `task-master validate-dependencies`, and `fix_dependencies` / `task-master fix-dependencies` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) when needed - Respect dependency chains and task priorities when selecting work - Report progress regularly using `get_tasks` / `task-master list` +- Reorganize tasks as needed using `move_task` / `task-master move --from= --to=` (see [`taskmaster.mdc`](mdc:.cursor/rules/taskmaster.mdc)) to change task hierarchy or ordering ## Task Complexity Analysis @@ -154,6 +155,25 @@ Taskmaster configuration is managed through two main mechanisms: - Task files are automatically regenerated after dependency changes - Dependencies are visualized with status indicators in task listings and files +## Task Reorganization + +- Use `move_task` / `task-master move --from= --to=` to move tasks or subtasks within the hierarchy +- This command supports several use cases: + - Moving a standalone task to become a subtask (e.g., `--from=5 --to=7`) + - Moving a subtask to become a standalone task (e.g., `--from=5.2 --to=7`) + - Moving a subtask to a different parent (e.g., `--from=5.2 --to=7.3`) + - Reordering subtasks within the same parent (e.g., `--from=5.2 --to=5.4`) + - Moving a task to a new, non-existent ID position (e.g., `--from=5 --to=25`) + - Moving multiple tasks at once using comma-separated IDs (e.g., `--from=10,11,12 --to=16,17,18`) +- The system includes validation to prevent data loss: + - Allows moving to non-existent IDs by creating placeholder tasks + - Prevents moving to existing task IDs that have content (to avoid overwriting) + - Validates source tasks exist before attempting to move them +- The system maintains proper parent-child relationships and dependency integrity +- Task files are automatically regenerated after the move operation +- This provides greater flexibility in organizing and refining your task structure as project understanding evolves +- This is especially useful when dealing with potential merge conflicts arising from teams creating tasks on separate branches. Solve these conflicts very easily by moving your tasks and keeping theirs. + ## Iterative Subtask Implementation Once a task has been broken down into subtasks using `expand_task` or similar methods, follow this iterative process for implementation: diff --git a/.cursor/rules/taskmaster.mdc b/.cursor/rules/taskmaster.mdc index fd6a8384..edb4d58e 100644 --- a/.cursor/rules/taskmaster.mdc +++ b/.cursor/rules/taskmaster.mdc @@ -269,11 +269,36 @@ This document provides a detailed reference for interacting with Taskmaster, cov * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) * **Usage:** Delete unnecessary subtasks or promote a subtask to a top-level task. +### 17. Move Task (`move_task`) + +* **MCP Tool:** `move_task` +* **CLI Command:** `task-master move [options]` +* **Description:** `Move a task or subtask to a new position within the task hierarchy.` +* **Key Parameters/Options:** + * `from`: `Required. ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated for multiple tasks.` (CLI: `--from `) + * `to`: `Required. ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated.` (CLI: `--to `) + * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) +* **Usage:** Reorganize tasks by moving them within the hierarchy. Supports various scenarios like: + * Moving a task to become a subtask + * Moving a subtask to become a standalone task + * Moving a subtask to a different parent + * Reordering subtasks within the same parent + * Moving a task to a new, non-existent ID (automatically creates placeholders) + * Moving multiple tasks at once with comma-separated IDs +* **Validation Features:** + * Allows moving tasks to non-existent destination IDs (creates placeholder tasks) + * Prevents moving to existing task IDs that already have content (to avoid overwriting) + * Validates that source tasks exist before attempting to move them + * Maintains proper parent-child relationships +* **Example CLI:** `task-master move --from=5.2 --to=7.3` to move subtask 5.2 to become subtask 7.3. +* **Example Multi-Move:** `task-master move --from=10,11,12 --to=16,17,18` to move multiple tasks to new positions. +* **Common Use:** Resolving merge conflicts in tasks.json when multiple team members create tasks on different branches. + --- ## Dependency Management -### 17. Add Dependency (`add_dependency`) +### 18. Add Dependency (`add_dependency`) * **MCP Tool:** `add_dependency` * **CLI Command:** `task-master add-dependency [options]` @@ -284,7 +309,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) * **Usage:** Establish the correct order of execution between tasks. -### 18. Remove Dependency (`remove_dependency`) +### 19. Remove Dependency (`remove_dependency`) * **MCP Tool:** `remove_dependency` * **CLI Command:** `task-master remove-dependency [options]` @@ -295,7 +320,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) * **Usage:** Update task relationships when the order of execution changes. -### 19. Validate Dependencies (`validate_dependencies`) +### 20. Validate Dependencies (`validate_dependencies`) * **MCP Tool:** `validate_dependencies` * **CLI Command:** `task-master validate-dependencies [options]` @@ -304,7 +329,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov * `file`: `Path to your Taskmaster 'tasks.json' file. Default relies on auto-detection.` (CLI: `-f, --file `) * **Usage:** Audit the integrity of your task dependencies. -### 20. Fix Dependencies (`fix_dependencies`) +### 21. Fix Dependencies (`fix_dependencies`) * **MCP Tool:** `fix_dependencies` * **CLI Command:** `task-master fix-dependencies [options]` @@ -317,7 +342,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov ## Analysis & Reporting -### 21. Analyze Project Complexity (`analyze_project_complexity`) +### 22. Analyze Project Complexity (`analyze_project_complexity`) * **MCP Tool:** `analyze_project_complexity` * **CLI Command:** `task-master analyze-complexity [options]` @@ -330,7 +355,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov * **Usage:** Used before breaking down tasks to identify which ones need the most attention. * **Important:** This MCP tool makes AI calls and can take up to a minute to complete. Please inform users to hang tight while the operation is in progress. -### 22. View Complexity Report (`complexity_report`) +### 23. View Complexity Report (`complexity_report`) * **MCP Tool:** `complexity_report` * **CLI Command:** `task-master complexity-report [options]` @@ -343,7 +368,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov ## File Management -### 23. Generate Task Files (`generate`) +### 24. Generate Task Files (`generate`) * **MCP Tool:** `generate` * **CLI Command:** `task-master generate [options]` diff --git a/.taskmasterconfig b/.taskmasterconfig index 2a1e943a..c2535b8c 100644 --- a/.taskmasterconfig +++ b/.taskmasterconfig @@ -2,8 +2,8 @@ "models": { "main": { "provider": "anthropic", - "modelId": "claude-3-7-sonnet-20250219", - "maxTokens": 100000, + "modelId": "claude-sonnet-4-20250514", + "maxTokens": 50000, "temperature": 0.2 }, "research": { @@ -15,7 +15,7 @@ "fallback": { "provider": "anthropic", "modelId": "claude-3-7-sonnet-20250219", - "maxTokens": 8192, + "maxTokens": 128000, "temperature": 0.2 } }, diff --git a/assets/AGENTS.md b/assets/AGENTS.md new file mode 100644 index 00000000..d7cfbfa1 --- /dev/null +++ b/assets/AGENTS.md @@ -0,0 +1,413 @@ +# Task Master AI - Claude Code Integration Guide + +## Essential Commands + +### Core Workflow Commands + +```bash +# Project Setup +task-master init # Initialize Task Master in current project +task-master parse-prd scripts/prd.txt # Generate tasks from PRD document +task-master models --setup # Configure AI models interactively + +# Daily Development Workflow +task-master list # Show all tasks with status +task-master next # Get next available task to work on +task-master show # View detailed task information (e.g., task-master show 1.2) +task-master set-status --id= --status=done # Mark task complete + +# Task Management +task-master add-task --prompt="description" --research # Add new task with AI assistance +task-master expand --id= --research --force # Break task into subtasks +task-master update-task --id= --prompt="changes" # Update specific task +task-master update --from= --prompt="changes" # Update multiple tasks from ID onwards +task-master update-subtask --id= --prompt="notes" # Add implementation notes to subtask + +# Analysis & Planning +task-master analyze-complexity --research # Analyze task complexity +task-master complexity-report # View complexity analysis +task-master expand --all --research # Expand all eligible tasks + +# Dependencies & Organization +task-master add-dependency --id= --depends-on= # Add task dependency +task-master move --from= --to= # Reorganize task hierarchy +task-master validate-dependencies # Check for dependency issues +task-master generate # Update task markdown files (usually auto-called) +``` + +## Key Files & Project Structure + +### Core Files + +- `tasks/tasks.json` - Main task data file (auto-managed) +- `.taskmasterconfig` - AI model configuration (use `task-master models` to modify) +- `scripts/prd.txt` - Product Requirements Document for parsing +- `tasks/*.txt` - Individual task files (auto-generated from tasks.json) +- `.env` - API keys for CLI usage + +### Claude Code Integration Files + +- `CLAUDE.md` - Auto-loaded context for Claude Code (this file) +- `.claude/settings.json` - Claude Code tool allowlist and preferences +- `.claude/commands/` - Custom slash commands for repeated workflows +- `.mcp.json` - MCP server configuration (project-specific) + +### Directory Structure + +``` +project/ +├── tasks/ +│ ├── tasks.json # Main task database +│ ├── task-1.md # Individual task files +│ └── task-2.md +├── scripts/ +│ ├── prd.txt # Product requirements +│ └── task-complexity-report.json +├── .claude/ +│ ├── settings.json # Claude Code configuration +│ └── commands/ # Custom slash commands +├── .taskmasterconfig # AI models & settings +├── .env # API keys +├── .mcp.json # MCP configuration +└── CLAUDE.md # This file - auto-loaded by Claude Code +``` + +## MCP Integration + +Task Master provides an MCP server that Claude Code can connect to. Configure in `.mcp.json`: + +```json +{ + "mcpServers": { + "task-master-ai": { + "command": "npx", + "args": ["-y", "--package=task-master-ai", "task-master-ai"], + "env": { + "ANTHROPIC_API_KEY": "your_key_here", + "PERPLEXITY_API_KEY": "your_key_here", + "OPENAI_API_KEY": "OPENAI_API_KEY_HERE", + "GOOGLE_API_KEY": "GOOGLE_API_KEY_HERE", + "XAI_API_KEY": "XAI_API_KEY_HERE", + "OPENROUTER_API_KEY": "OPENROUTER_API_KEY_HERE", + "MISTRAL_API_KEY": "MISTRAL_API_KEY_HERE", + "AZURE_OPENAI_API_KEY": "AZURE_OPENAI_API_KEY_HERE", + "OLLAMA_API_KEY": "OLLAMA_API_KEY_HERE" + } + } + } +} +``` + +### Essential MCP Tools + +```javascript +help; // = shows available taskmaster commands +// Project setup +initialize_project; // = task-master init +parse_prd; // = task-master parse-prd + +// Daily workflow +get_tasks; // = task-master list +next_task; // = task-master next +get_task; // = task-master show +set_task_status; // = task-master set-status + +// Task management +add_task; // = task-master add-task +expand_task; // = task-master expand +update_task; // = task-master update-task +update_subtask; // = task-master update-subtask +update; // = task-master update + +// Analysis +analyze_project_complexity; // = task-master analyze-complexity +complexity_report; // = task-master complexity-report +``` + +## Claude Code Workflow Integration + +### Standard Development Workflow + +#### 1. Project Initialization + +```bash +# Initialize Task Master +task-master init + +# Create or obtain PRD, then parse it +task-master parse-prd scripts/prd.txt + +# Analyze complexity and expand tasks +task-master analyze-complexity --research +task-master expand --all --research +``` + +If tasks already exist, another PRD can be parsed (with new information only!) using parse-prd with --append flag. This will add the generated tasks to the existing list of tasks.. + +#### 2. Daily Development Loop + +```bash +# Start each session +task-master next # Find next available task +task-master show # Review task details + +# During implementation, check in code context into the tasks and subtasks +task-master update-subtask --id= --prompt="implementation notes..." + +# Complete tasks +task-master set-status --id= --status=done +``` + +#### 3. Multi-Claude Workflows + +For complex projects, use multiple Claude Code sessions: + +```bash +# Terminal 1: Main implementation +cd project && claude + +# Terminal 2: Testing and validation +cd project-test-worktree && claude + +# Terminal 3: Documentation updates +cd project-docs-worktree && claude +``` + +### Custom Slash Commands + +Create `.claude/commands/taskmaster-next.md`: + +```markdown +Find the next available Task Master task and show its details. + +Steps: + +1. Run `task-master next` to get the next task +2. If a task is available, run `task-master show ` for full details +3. Provide a summary of what needs to be implemented +4. Suggest the first implementation step +``` + +Create `.claude/commands/taskmaster-complete.md`: + +```markdown +Complete a Task Master task: $ARGUMENTS + +Steps: + +1. Review the current task with `task-master show $ARGUMENTS` +2. Verify all implementation is complete +3. Run any tests related to this task +4. Mark as complete: `task-master set-status --id=$ARGUMENTS --status=done` +5. Show the next available task with `task-master next` +``` + +## Tool Allowlist Recommendations + +Add to `.claude/settings.json`: + +```json +{ + "allowedTools": [ + "Edit", + "Bash(task-master *)", + "Bash(git commit:*)", + "Bash(git add:*)", + "Bash(npm run *)", + "mcp__task_master_ai__*" + ] +} +``` + +## Configuration & Setup + +### API Keys Required + +At least **one** of these API keys must be configured: + +- `ANTHROPIC_API_KEY` (Claude models) - **Recommended** +- `PERPLEXITY_API_KEY` (Research features) - **Highly recommended** +- `OPENAI_API_KEY` (GPT models) +- `GOOGLE_API_KEY` (Gemini models) +- `MISTRAL_API_KEY` (Mistral models) +- `OPENROUTER_API_KEY` (Multiple models) +- `XAI_API_KEY` (Grok models) + +An API key is required for any provider used across any of the 3 roles defined in the `models` command. + +### Model Configuration + +```bash +# Interactive setup (recommended) +task-master models --setup + +# Set specific models +task-master models --set-main claude-3-5-sonnet-20241022 +task-master models --set-research perplexity-llama-3.1-sonar-large-128k-online +task-master models --set-fallback gpt-4o-mini +``` + +## Task Structure & IDs + +### Task ID Format + +- Main tasks: `1`, `2`, `3`, etc. +- Subtasks: `1.1`, `1.2`, `2.1`, etc. +- Sub-subtasks: `1.1.1`, `1.1.2`, etc. + +### Task Status Values + +- `pending` - Ready to work on +- `in-progress` - Currently being worked on +- `done` - Completed and verified +- `deferred` - Postponed +- `cancelled` - No longer needed +- `blocked` - Waiting on external factors + +### Task Fields + +```json +{ + "id": "1.2", + "title": "Implement user authentication", + "description": "Set up JWT-based auth system", + "status": "pending", + "priority": "high", + "dependencies": ["1.1"], + "details": "Use bcrypt for hashing, JWT for tokens...", + "testStrategy": "Unit tests for auth functions, integration tests for login flow", + "subtasks": [] +} +``` + +## Claude Code Best Practices with Task Master + +### Context Management + +- Use `/clear` between different tasks to maintain focus +- This CLAUDE.md file is automatically loaded for context +- Use `task-master show ` to pull specific task context when needed + +### Iterative Implementation + +1. `task-master show ` - Understand requirements +2. Explore codebase and plan implementation +3. `task-master update-subtask --id= --prompt="detailed plan"` - Log plan +4. `task-master set-status --id= --status=in-progress` - Start work +5. Implement code following logged plan +6. `task-master update-subtask --id= --prompt="what worked/didn't work"` - Log progress +7. `task-master set-status --id= --status=done` - Complete task + +### Complex Workflows with Checklists + +For large migrations or multi-step processes: + +1. Create a markdown PRD file describing the new changes: `touch task-migration-checklist.md` (prds can be .txt or .md) +2. Use Taskmaster to parse the new prd with `task-master parse-prd --append` (also available in MCP) +3. Use Taskmaster to expand the newly generated tasks into subtasks. Consdier using `analyze-complexity` with the correct --to and --from IDs (the new ids) to identify the ideal subtask amounts for each task. Then expand them. +4. Work through items systematically, checking them off as completed +5. Use `task-master update-subtask` to log progress on each task/subtask and/or updating/researching them before/during implementation if getting stuck + +### Git Integration + +Task Master works well with `gh` CLI: + +```bash +# Create PR for completed task +gh pr create --title "Complete task 1.2: User authentication" --body "Implements JWT auth system as specified in task 1.2" + +# Reference task in commits +git commit -m "feat: implement JWT auth (task 1.2)" +``` + +### Parallel Development with Git Worktrees + +```bash +# Create worktrees for parallel task development +git worktree add ../project-auth feature/auth-system +git worktree add ../project-api feature/api-refactor + +# Run Claude Code in each worktree +cd ../project-auth && claude # Terminal 1: Auth work +cd ../project-api && claude # Terminal 2: API work +``` + +## Troubleshooting + +### AI Commands Failing + +```bash +# Check API keys are configured +cat .env # For CLI usage + +# Verify model configuration +task-master models + +# Test with different model +task-master models --set-fallback gpt-4o-mini +``` + +### MCP Connection Issues + +- Check `.mcp.json` configuration +- Verify Node.js installation +- Use `--mcp-debug` flag when starting Claude Code +- Use CLI as fallback if MCP unavailable + +### Task File Sync Issues + +```bash +# Regenerate task files from tasks.json +task-master generate + +# Fix dependency issues +task-master fix-dependencies +``` + +DO NOT RE-INITIALIZE. That will not do anything beyond re-adding the same Taskmaster core files. + +## Important Notes + +### AI-Powered Operations + +These commands make AI calls and may take up to a minute: + +- `parse_prd` / `task-master parse-prd` +- `analyze_project_complexity` / `task-master analyze-complexity` +- `expand_task` / `task-master expand` +- `expand_all` / `task-master expand --all` +- `add_task` / `task-master add-task` +- `update` / `task-master update` +- `update_task` / `task-master update-task` +- `update_subtask` / `task-master update-subtask` + +### File Management + +- Never manually edit `tasks.json` - use commands instead +- Never manually edit `.taskmasterconfig` - use `task-master models` +- Task markdown files in `tasks/` are auto-generated +- Run `task-master generate` after manual changes to tasks.json + +### Claude Code Session Management + +- Use `/clear` frequently to maintain focused context +- Create custom slash commands for repeated Task Master workflows +- Configure tool allowlist to streamline permissions +- Use headless mode for automation: `claude -p "task-master next"` + +### Multi-Task Updates + +- Use `update --from=` to update multiple future tasks +- Use `update-task --id=` for single task updates +- Use `update-subtask --id=` for implementation logging + +### Research Mode + +- Add `--research` flag for research-based AI enhancement +- Requires a research model API key like Perplexity (`PERPLEXITY_API_KEY`) in environment +- Provides more informed task creation and updates +- Recommended for complex technical tasks + +--- + +_This guide ensures Claude Code has immediate access to Task Master's essential functionality for agentic development workflows._ diff --git a/context/chats/add-task-dependencies-1.md b/context/chats/add-task-dependencies-1.md new file mode 100644 index 00000000..2d69c9d4 --- /dev/null +++ b/context/chats/add-task-dependencies-1.md @@ -0,0 +1,1798 @@ +# Task Title Formatting Issue Discussion + +_Exported on 5/22/2025 at 00:05:09 EDT from Cursor (0.50.5)_ + +--- + +**User** + +in add-task, i noticed that with some providers, the title comes out with the id in it. i dont know if this is a parsing issue (@ollama.js) or if it this is an improvement we ought to make to the system prompt in @add-task.js + +--- + +**Cursor** + +Okay, I've looked into this. The issue where the task ID is sometimes included in the generated task title (e.g., "Task #89: Introduce Prioritize Command...") likely stems from the prompt construction in `scripts/modules/task-manager/add-task.js`. + +The current user prompt (around line 208) is: +`Create a comprehensive new task (Task #${newTaskId}) for a software development project based on this description: "${prompt}"` + +The AI might be interpreting `(Task #${newTaskId})` as part of the content it needs to generate, especially for the title. The `ollama.js` provider itself doesn't seem to be modifying the content in a way that would add the ID; it primarily passes the prompt and schema to the Ollama service. + +I'll adjust the user prompt in `scripts/modules/task-manager/add-task.js` to clarify that the task ID is contextual information, not part of the title to be generated. + +I'll change this part of the prompt: +`Create a comprehensive new task (Task #${newTaskId}) for a software development project based on this description: "${prompt}"` + +to something like: +`You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project.` + +This rephrasing should guide the AI to use the ID as context rather than including it directly in the `title` field of the generated JSON object. + +Here's the proposed change: + +```javascript:scripts/modules/task-manager/add-task.js +// ... existing code ... + if (manualTaskData?.testStrategy) + contextFromArgs += `\n- Additional Test Strategy Context: "${manualTaskData.testStrategy}"`; + + // User Prompt + const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project. + + ${contextTasks} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific.`; + + // Start the loading indicator - only for text mode + if (outputFormat === 'text') { +// ... existing code ... +``` + +This change clarifies the role of `newTaskId` for the AI. Let me know if you'd like to test this or make further adjustments! + +```javascript +// ... existing code ... + if (manualTaskData?.testStrategy) + contextFromArgs += `\n- Additional Test Strategy Context: "${manualTaskData.testStrategy}"`; + + // User Prompt + const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project. + + ${contextTasks} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific.`; + + // Start the loading indicator - only for text mode + if (outputFormat === 'text') { +// ... existing code ... +``` + +--- + +**User** + +get the details of task id 88 -- i wonder if we can - in the above place, be smart about passing in the ids, titles, descriptions (but not details) and dependencies of all the other tasks + +i don't want it to take a ton of context, but the goal here is to also fill in the dependency field when creating a task. right now it's not being filled in and i have to update all the tasks for it to be populated. so i want it to be fixed at the source in add-task ideally and i want a lightweight but accurate way to pass in the other tasks and their dependencies, and not passing in the actual details of the task and only the above as i explained might be enough. + +--- + +**Cursor** + +Let me get the details of task ID 88 first: + +Now I'll look at the current implementation of add-task.js to understand how we can improve the context provided to the AI: + +Based on the task details and the current implementation of add-task.js, I'll create an improved version that provides more context about other tasks and their dependency relationships without overwhelming the token limit. Here's my proposed change: + +```javascript +// ... existing code ... +report('DEBUG: Taking AI task generation path.', 'debug'); +// --- Refactored AI Interaction --- +report(`Generating task data with AI with prompt:\n${prompt}`, 'info'); + +// Create context string for task creation prompt +let contextTasks = ''; + +// Create a dependency map for better understanding of the task relationships +const taskMap = {}; +data.tasks.forEach((t) => { + // For each task, only include id, title, description, and dependencies + taskMap[t.id] = { + id: t.id, + title: t.title, + description: t.description, + dependencies: t.dependencies || [], + status: t.status + }; +}); + +if (numericDependencies.length > 0) { + // If specific dependencies were provided, focus on them + const dependentTasks = data.tasks.filter((t) => + numericDependencies.includes(t.id) + ); + contextTasks = `\nThis task depends on the following tasks:\n${dependentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + + // Add dependency chain information (what these tasks depend on) + const dependencyChains = []; + for (const depTask of dependentTasks) { + if (depTask.dependencies && depTask.dependencies.length > 0) { + const subDeps = depTask.dependencies.map((subDepId) => { + const subDepTask = data.tasks.find((t) => t.id === subDepId); + return subDepTask + ? `Task ${subDepId}: ${subDepTask.title}` + : `Task ${subDepId}`; + }); + dependencyChains.push( + `Task ${depTask.id} depends on: ${subDeps.join(', ')}` + ); + } + } + + if (dependencyChains.length > 0) { + contextTasks += `\n\nDependency chains:\n${dependencyChains.join('\n')}`; + } +} else { + // If no dependencies provided, show recent and related tasks + // Get recent tasks (newest first) + const recentTasks = [...data.tasks].sort((a, b) => b.id - a.id).slice(0, 5); + + // Get tasks that might be related based on simple keyword matching + const keywords = prompt + .toLowerCase() + .split(/\s+/) + .filter( + (word) => + word.length > 4 && + !['the', 'and', 'that', 'with', 'this', 'for', 'from'].includes(word) + ); + + const relatedTasks = + keywords.length > 0 + ? data.tasks + .filter( + (t) => + keywords.some( + (keyword) => + t.title.toLowerCase().includes(keyword) || + t.description.toLowerCase().includes(keyword) + ) && !recentTasks.some((rt) => rt.id === t.id) + ) + .slice(0, 3) + : []; + + if (recentTasks.length > 0) { + contextTasks = `\nRecent tasks in the project:\n${recentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + if (relatedTasks.length > 0) { + contextTasks += `\n\nPotentially related tasks based on keywords:\n${relatedTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + // Add a concise view of the task dependency structure + contextTasks += '\n\nSummary of task dependencies in the project:'; + const pendingTasks = data.tasks.filter((t) => t.status !== 'done').slice(-10); + for (const task of pendingTasks) { + const depsStr = + task.dependencies && task.dependencies.length > 0 + ? task.dependencies.join(', ') + : 'None'; + contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`; + } +} + +// System Prompt - Enhanced for dependency awareness +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, adhering strictly to the provided JSON schema. Pay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on."; + +// Task Structure Description (for user prompt) +const taskStructureDesc = ` + { + "title": "Task title goes here", + "description": "A concise one or two sentence description of what the task involves", + "details": "In-depth implementation details, considerations, and guidance.", + "testStrategy": "Detailed approach for verifying task completion." + }`; + +// Add any manually provided details to the prompt for context +let contextFromArgs = ''; +if (manualTaskData?.title) + contextFromArgs += `\n- Suggested Title: "${manualTaskData.title}"`; +if (manualTaskData?.description) + contextFromArgs += `\n- Suggested Description: "${manualTaskData.description}"`; +if (manualTaskData?.details) + contextFromArgs += `\n- Additional Details Context: "${manualTaskData.details}"`; +if (manualTaskData?.testStrategy) + contextFromArgs += `\n- Additional Test Strategy Context: "${manualTaskData.testStrategy}"`; + +// User Prompt +const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project. + + ${contextTasks} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + + Based on the information about existing tasks and their dependencies, suggest appropriate dependencies for this task if any are needed. The task IDs mentioned above can be referenced in your implementation details. + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific. Don't include the task ID in the title.`; +// ... existing code ... +``` + +Now let's test the changes to see if they improve the task creation process, especially around dependency handling: + +--- + +**User** + +this will probably need to be adjusted so from a structured output pov the ai knows to add the info in that field + +--- + +**Cursor** + +You're absolutely right. While our changes improve the context provided to the AI, they don't provide a structured way for the AI to include the dependencies in its output schema. Let's modify the Zod schema to explicitly include a `dependencies` field: + +```javascript +// Define Zod schema for the expected AI output object +const AiTaskDataSchema = z.object({ + title: z.string().describe('Clear, concise title for the task'), + description: z + .string() + .describe('A one or two sentence description of the task'), + details: z + .string() + .describe('In-depth implementation details, considerations, and guidance'), + testStrategy: z + .string() + .describe('Detailed approach for verifying task completion'), + dependencies: z + .array(z.number()) + .optional() + .describe( + 'Array of task IDs that this task depends on (must be completed before this task can start)' + ) +}); + +// ... existing code ... + +// Task Structure Description (for user prompt) +const taskStructureDesc = ` + { + "title": "Task title goes here", + "description": "A concise one or two sentence description of what the task involves", + "details": "In-depth implementation details, considerations, and guidance.", + "testStrategy": "Detailed approach for verifying task completion.", + "dependencies": [1, 2, 3] // Optional: Array of existing task IDs that this task depends on + }`; + +// ... existing code ... + +// User Prompt +const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project. + + ${contextTasks} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + + Based on the information about existing tasks and their dependencies, include appropriate dependencies in the "dependencies" array if any are needed. Only include task IDs that this new task directly depends on. + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific. Don't include the task ID in the title.`; + +// ... existing code ... + +// Create the new task object +const newTask = { + id: newTaskId, + title: taskData.title, + description: taskData.description, + details: taskData.details || '', + testStrategy: taskData.testStrategy || '', + status: 'pending', + dependencies: taskData.dependencies?.length + ? taskData.dependencies + : numericDependencies, // Use AI-suggested dependencies if available, fallback to manually specified + priority: effectivePriority, + subtasks: [] // Initialize with empty subtasks array +}; +``` + +Let's make one more small fix - I notice there's still a reference to `numericDependencies` in the success message output that should be adjusted to use the task's actual dependencies: + +```javascript +// ... existing code ... + console.log( + boxen( + chalk.white.bold(`Task ${newTaskId} Created Successfully`) + + '\n\n' + + chalk.white(`Title: ${newTask.title}`) + + '\n' + + chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) + + '\n' + + chalk.white( + `Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}` + ) + + '\n' + + (newTask.dependencies.length > 0 + ? chalk.white(`Dependencies: ${newTask.dependencies.join(', ')}`) + + '\n' + : '') + + '\n' + +// ... existing code ... +``` + +Now let's test our changes to make sure they work properly: + +--- + +**User** + +looks like it worked. i would like -- in the case of the CLI only, to also show some additional stuff in @add-task.js + +we just did a bunch of magic, and it would be too bad not to show it to the user as we do it, without it being too much + +(ignore the failed ollama call) + +i think there could be a line or two that shows the identified dependencies or something. its sexy stuff, let's make sure the user knows about it elegantly. @ui.js + +--- + +**Cursor** + +Let's enhance the CLI experience to elegantly showcase the dependency analysis we're performing. I'll add a small, informative display in add-task.js that shows the intelligent context we're building: + +```javascript +// ... existing code ... +// Create a dependency map for better understanding of the task relationships +const taskMap = {}; +data.tasks.forEach((t) => { + // For each task, only include id, title, description, and dependencies + taskMap[t.id] = { + id: t.id, + title: t.title, + description: t.description, + dependencies: t.dependencies || [], + status: t.status + }; +}); + +// CLI-only feedback for the dependency analysis +if (outputFormat === 'text') { + console.log( + boxen(chalk.cyan.bold('Task Context Analysis') + '\n', { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 0, bottom: 0 }, + borderColor: 'cyan', + borderStyle: 'round' + }) + ); +} + +if (numericDependencies.length > 0) { + // If specific dependencies were provided, focus on them + const dependentTasks = data.tasks.filter((t) => + numericDependencies.includes(t.id) + ); + contextTasks = `\nThis task depends on the following tasks:\n${dependentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + + // Add dependency chain information (what these tasks depend on) + const dependencyChains = []; + for (const depTask of dependentTasks) { + if (depTask.dependencies && depTask.dependencies.length > 0) { + const subDeps = depTask.dependencies.map((subDepId) => { + const subDepTask = data.tasks.find((t) => t.id === subDepId); + return subDepTask + ? `Task ${subDepId}: ${subDepTask.title}` + : `Task ${subDepId}`; + }); + dependencyChains.push( + `Task ${depTask.id} depends on: ${subDeps.join(', ')}` + ); + } + } + + if (dependencyChains.length > 0) { + contextTasks += `\n\nDependency chains:\n${dependencyChains.join('\n')}`; + } + + // Show dependency chains in CLI mode + if (outputFormat === 'text' && dependentTasks.length > 0) { + console.log(chalk.gray(` Explicitly specified dependencies:`)); + dependentTasks.forEach((t) => { + console.log(chalk.yellow(` • Task ${t.id}: ${truncate(t.title, 50)}`)); + }); + + if (dependencyChains.length > 0) { + console.log(chalk.gray(`\n Analyzing dependency chains:`)); + dependencyChains.forEach((chain) => { + console.log(chalk.cyan(` • ${truncate(chain, 60)}`)); + }); + } + console.log(); // Add spacing + } +} else { + // If no dependencies provided, show recent and related tasks + // Get recent tasks (newest first) + const recentTasks = [...data.tasks].sort((a, b) => b.id - a.id).slice(0, 5); + + // Get tasks that might be related based on simple keyword matching + const keywords = prompt + .toLowerCase() + .split(/\s+/) + .filter( + (word) => + word.length > 4 && + !['the', 'and', 'that', 'with', 'this', 'for', 'from'].includes(word) + ); + + const relatedTasks = + keywords.length > 0 + ? data.tasks + .filter( + (t) => + keywords.some( + (keyword) => + t.title.toLowerCase().includes(keyword) || + t.description.toLowerCase().includes(keyword) + ) && !recentTasks.some((rt) => rt.id === t.id) + ) + .slice(0, 3) + : []; + + if (recentTasks.length > 0) { + contextTasks = `\nRecent tasks in the project:\n${recentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + if (relatedTasks.length > 0) { + contextTasks += `\n\nPotentially related tasks based on keywords:\n${relatedTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + // Add a concise view of the task dependency structure + contextTasks += '\n\nSummary of task dependencies in the project:'; + const pendingTasks = data.tasks.filter((t) => t.status !== 'done').slice(-10); + for (const task of pendingTasks) { + const depsStr = + task.dependencies && task.dependencies.length > 0 + ? task.dependencies.join(', ') + : 'None'; + contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`; + } + + // Show keyword-based analysis in CLI mode + if (outputFormat === 'text') { + if (keywords.length > 0) { + console.log( + chalk.gray(` Keywords extracted for analysis: ${keywords.join(', ')}`) + ); + + if (relatedTasks.length > 0) { + console.log( + chalk.gray(`\n Potentially related tasks by keyword match:`) + ); + relatedTasks.forEach((t) => { + console.log( + chalk.yellow(` • Task ${t.id}: ${truncate(t.title, 50)}`) + ); + }); + } + } + + // Show the recent tasks used for context + if (recentTasks.length > 0) { + console.log(chalk.gray(`\n Using recent tasks for context:`)); + recentTasks.slice(0, 3).forEach((t) => { + console.log(chalk.cyan(` • Task ${t.id}: ${truncate(t.title, 50)}`)); + }); + } + + // Show dependency structure visualization + if (pendingTasks.length > 0) { + const depsToShow = Math.min(3, pendingTasks.length); + console.log( + chalk.gray( + `\n Task dependency patterns analyzed (${depsToShow} example${depsToShow > 1 ? 's' : ''}):` + ) + ); + pendingTasks.slice(0, depsToShow).forEach((task) => { + const depsStr = + task.dependencies && task.dependencies.length > 0 + ? chalk.yellow(task.dependencies.join(', ')) + : chalk.gray('None'); + console.log(chalk.blue(` • Task ${task.id} → [${depsStr}]`)); + }); + } + console.log(); // Add spacing + } +} + +// System Prompt - Enhanced for dependency awareness +// ... existing code ... +``` + +Now let's also add a visual indicator before the AI call to make the transition clearer and make the experience more engaging: + +```javascript +// ... existing code ... + console.log(); // Add spacing + } + } + + // System Prompt - Enhanced for dependency awareness + 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, adhering strictly to the provided JSON schema. Pay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on."; + + // Task Structure Description (for user prompt) + const taskStructureDesc = ` + { + "title": "Task title goes here", + "description": "A concise one or two sentence description of what the task involves", + "details": "In-depth implementation details, considerations, and guidance.", + "testStrategy": "Detailed approach for verifying task completion.", + "dependencies": [1, 2, 3] // Optional: Array of existing task IDs that this task depends on + }`; + + // Add any manually provided details to the prompt for context + let contextFromArgs = ''; + if (manualTaskData?.title) + contextFromArgs += `\n- Suggested Title: "${manualTaskData.title}"`; + if (manualTaskData?.description) + contextFromArgs += `\n- Suggested Description: "${manualTaskData.description}"`; + if (manualTaskData?.details) + contextFromArgs += `\n- Additional Details Context: "${manualTaskData.details}"`; + if (manualTaskData?.testStrategy) + contextFromArgs += `\n- Additional Test Strategy Context: "${manualTaskData.testStrategy}"`; + + // User Prompt + const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project. + + ${contextTasks} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + + Based on the information about existing tasks and their dependencies, include appropriate dependencies in the "dependencies" array if any are needed. Only include task IDs that this new task directly depends on. + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific. Don't include the task ID in the title.`; + + // Start the loading indicator - only for text mode + if (outputFormat === 'text') { + // Add a visual transition to show we're moving to AI generation + console.log(boxen( + chalk.white.bold('AI Task Generation') + + `\n\n${chalk.gray('Analyzing context and generating task details using AI...')}` + + `\n${chalk.cyan('Context size: ')}${chalk.yellow(contextTasks.length.toLocaleString())} characters` + + `\n${chalk.cyan('Dependency detection: ')}${chalk.yellow(numericDependencies.length > 0 ? 'Explicit dependencies' : 'Auto-discovery mode')}`, + { + padding: 1, + borderColor: 'blue', + borderStyle: 'round', + margin: { top: 0, bottom: 1 } + } + )); + + loadingIndicator = startLoadingIndicator( + `Generating new task with ${useResearch ? 'Research' : 'Main'} AI...\n` + ); + } + + try { +// ... existing code ... +``` + +Finally, let's enhance the success message to highlight the AI's dependency analysis and any dependencies added by the AI: + +```javascript +// ... existing code ... + truncate(newTask.description, 47) + ]); + + console.log(chalk.green('✅ New task created successfully:')); + console.log(table.toString()); + + // Helper to get priority color + const getPriorityColor = (p) => { + switch (p?.toLowerCase()) { + case 'high': + return 'red'; + case 'low': + return 'gray'; + case 'medium': + default: + return 'yellow'; + } + }; + + // Check if AI added new dependencies that weren't explicitly provided + const aiAddedDeps = newTask.dependencies.filter( + dep => !numericDependencies.includes(dep) + ); + + // Show success message box + console.log( + boxen( + chalk.white.bold(`Task ${newTaskId} Created Successfully`) + + '\n\n' + + chalk.white(`Title: ${newTask.title}`) + + '\n' + + chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) + + '\n' + + chalk.white( + `Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}` + ) + + '\n' + + (newTask.dependencies.length > 0 + ? chalk.white(`Dependencies: ${newTask.dependencies.join(', ')}`) + + (aiAddedDeps.length > 0 + ? chalk.gray(` (${aiAddedDeps.length} auto-detected by AI)`) + : '') + + '\n' + : '') + + '\n' + + chalk.white.bold('Next Steps:') + + '\n' + +// ... existing code ... +``` + +Let's test the changes to see our enhanced CLI experience in action: + +--- + +**User** + +it seems to have worked, but its now the second task that used 1 and 3 as dependencies. across 90 tasks, i find that a bit odd (but not impossible as most tasks are in fact net new) + +let's, for the sake of research, also include the details of the other tasks. let's see what happens with the context size + +--- + +**Cursor** + +I agree it's interesting that the AI consistently selects tasks 1 and 3 as dependencies. It found these tasks through the keyword matching (showing "Task 1: Implement Task Data Structure" and "Task 3: Implement Basic Task Operations"), but we should provide more comprehensive context to help it make better dependency selections. + +Let's enhance the task context by including task details for relevant tasks, not just their titles and descriptions: + +```javascript +// ... existing code ... + taskMap[t.id] = { + id: t.id, + title: t.title, + description: t.description, + dependencies: t.dependencies || [], + status: t.status + }; + }); + + // CLI-only feedback for the dependency analysis + if (outputFormat === 'text') { + console.log(boxen( + chalk.cyan.bold('Task Context Analysis') + '\n', + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 0, bottom: 0 }, + borderColor: 'cyan', + borderStyle: 'round' + } + )); + } + + if (numericDependencies.length > 0) { + // If specific dependencies were provided, focus on them + const dependentTasks = data.tasks.filter((t) => + numericDependencies.includes(t.id) + ); + contextTasks = `\nThis task depends on the following tasks:\n${dependentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + + // Add more details about each dependency + contextTasks += `\n\nDetailed information about dependencies:`; + for (const depTask of dependentTasks) { + contextTasks += `\n\n------ Task ${depTask.id}: ${depTask.title} ------\n`; + contextTasks += `Description: ${depTask.description}\n`; + contextTasks += `Status: ${depTask.status || 'pending'}\n`; + contextTasks += `Priority: ${depTask.priority || 'medium'}\n`; + // Add implementation details but truncate if too long + if (depTask.details) { + const truncatedDetails = depTask.details.length > 500 + ? depTask.details.substring(0, 500) + '... (truncated)' + : depTask.details; + contextTasks += `Implementation Details: ${truncatedDetails}\n`; + } + } + + // Add dependency chain information (what these tasks depend on) + const dependencyChains = []; + for (const depTask of dependentTasks) { + if (depTask.dependencies && depTask.dependencies.length > 0) { + const subDeps = depTask.dependencies.map(subDepId => { + const subDepTask = data.tasks.find(t => t.id === subDepId); + return subDepTask ? `Task ${subDepId}: ${subDepTask.title}` : `Task ${subDepId}`; + }); + dependencyChains.push(`Task ${depTask.id} depends on: ${subDeps.join(', ')}`); + } + } + + if (dependencyChains.length > 0) { + contextTasks += `\n\nDependency chains:\n${dependencyChains.join('\n')}`; + } + + // Show dependency chains in CLI mode + if (outputFormat === 'text' && dependentTasks.length > 0) { + console.log(chalk.gray(` Explicitly specified dependencies:`)); + dependentTasks.forEach(t => { + console.log(chalk.yellow(` • Task ${t.id}: ${truncate(t.title, 50)}`)); + }); + + if (dependencyChains.length > 0) { + console.log(chalk.gray(`\n Analyzing dependency chains:`)); + dependencyChains.forEach(chain => { + console.log(chalk.cyan(` • ${truncate(chain, 60)}`)); + }); + } + console.log(); // Add spacing + } + } else { + // If no dependencies provided, show recent and related tasks + // Get recent tasks (newest first) + const recentTasks = [...data.tasks] + .sort((a, b) => b.id - a.id) + .slice(0, 5); + + // Get tasks that might be related based on simple keyword matching + const keywords = prompt.toLowerCase().split(/\s+/).filter(word => + word.length > 4 && !['the', 'and', 'that', 'with', 'this', 'for', 'from'].includes(word) + ); + + // Get related tasks by keyword matching + const relatedTasks = keywords.length > 0 + ? data.tasks.filter(t => + keywords.some(keyword => + t.title.toLowerCase().includes(keyword) || + t.description.toLowerCase().includes(keyword) + ) && + !recentTasks.some(rt => rt.id === t.id) + ).slice(0, 5) // Increased from 3 to 5 + : []; + + // Also look for tasks with similar purposes or categories + const purposeCategories = [ + {pattern: /(command|cli|flag)/i, label: "CLI commands"}, + {pattern: /(task|subtask|add)/i, label: "Task management"}, + {pattern: /(dependency|depend)/i, label: "Dependency handling"}, + {pattern: /(AI|model|prompt)/i, label: "AI integration"}, + {pattern: /(UI|display|show)/i, label: "User interface"} + ]; + + const promptCategory = purposeCategories.find(cat => cat.pattern.test(prompt)); + const categoryTasks = promptCategory + ? data.tasks.filter(t => + promptCategory.pattern.test(t.title) || + promptCategory.pattern.test(t.description) || + (t.details && promptCategory.pattern.test(t.details)) + ) + .filter(t => + !recentTasks.some(rt => rt.id === t.id) && + !relatedTasks.some(rt => rt.id === t.id) + ) + .slice(0, 3) + : []; + + // Format basic task overviews + if (recentTasks.length > 0) { + contextTasks = `\nRecent tasks in the project:\n${recentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + if (relatedTasks.length > 0) { + contextTasks += `\n\nPotentially related tasks based on keywords:\n${relatedTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + if (categoryTasks.length > 0) { + contextTasks += `\n\nTasks related to ${promptCategory.label}:\n${categoryTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + // Add detailed information about the most relevant tasks + const allDetailedTasks = [...relatedTasks, ...categoryTasks.slice(0, 2), ...recentTasks.slice(0, 2)]; + const uniqueDetailedTasks = Array.from(new Map(allDetailedTasks.map(t => [t.id, t])).values()).slice(0, 5); + + if (uniqueDetailedTasks.length > 0) { + contextTasks += `\n\nDetailed information about relevant tasks:`; + for (const task of uniqueDetailedTasks) { + contextTasks += `\n\n------ Task ${task.id}: ${task.title} ------\n`; + contextTasks += `Description: ${task.description}\n`; + contextTasks += `Status: ${task.status || 'pending'}\n`; + contextTasks += `Priority: ${task.priority || 'medium'}\n`; + if (task.dependencies && task.dependencies.length > 0) { + contextTasks += `Dependencies: ${task.dependencies.join(', ')}\n`; + } + // Add implementation details but truncate if too long + if (task.details) { + const truncatedDetails = task.details.length > 400 + ? task.details.substring(0, 400) + '... (truncated)' + : task.details; + contextTasks += `Implementation Details: ${truncatedDetails}\n`; + } + } + } + + // Add a concise view of the task dependency structure + contextTasks += '\n\nSummary of task dependencies in the project:'; + + // Focus on pending tasks with the most similar purposes or categories + // This helps identify patterns in how similar tasks are set up + const relevantPendingTasks = promptCategory + ? data.tasks + .filter(t => t.status !== 'done' && (promptCategory.pattern.test(t.title) || promptCategory.pattern.test(t.description))) + .slice(-10) + : data.tasks.filter(t => t.status !== 'done').slice(-10); + + for (const task of relevantPendingTasks) { + const depsStr = task.dependencies && task.dependencies.length > 0 + ? task.dependencies.join(', ') + : 'None'; + contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`; + } + + // Additional analysis of common patterns + const commandTasks = data.tasks.filter(t => /command|cli/i.test(t.title) || /command|cli/i.test(t.description)); + if (commandTasks.length > 0 && /command|cli/i.test(prompt)) { + contextTasks += '\n\nCommon patterns for command implementation tasks:'; + const commandDeps = commandTasks + .filter(t => t.dependencies && t.dependencies.length > 0) + .map(t => t.dependencies) + .flat(); + + // Count frequency of each dependency + const depCounts = {}; + commandDeps.forEach(dep => { + depCounts[dep] = (depCounts[dep] || 0) + 1; + }); + + // Get most common dependencies for command tasks + const commonDeps = Object.entries(depCounts) + .sort((a, b) => b[1] - a[1]) + .slice(0, 3); + + if (commonDeps.length > 0) { + contextTasks += '\nMost common dependencies for similar tasks:'; + commonDeps.forEach(([depId, count]) => { + const depTask = data.tasks.find(t => t.id === parseInt(depId)); + if (depTask) { + contextTasks += `\n- Task ${depId} (used by ${count} similar tasks): ${depTask.title}`; + } + }); + } + } + + // Show keyword-based analysis in CLI mode + if (outputFormat === 'text') { + if (keywords.length > 0) { + console.log(chalk.gray(` Keywords extracted for analysis: ${keywords.join(', ')}`)); + + if (relatedTasks.length > 0) { + console.log(chalk.gray(`\n Potentially related tasks by keyword match:`)); + relatedTasks.forEach(t => { + console.log(chalk.yellow(` • Task ${t.id}: ${truncate(t.title, 50)}`)); + }); + } + } + + if (promptCategory && categoryTasks.length > 0) { + console.log(chalk.gray(`\n Tasks related to ${promptCategory.label}:`)); + categoryTasks.forEach(t => { + console.log(chalk.magenta(` • Task ${t.id}: ${truncate(t.title, 50)}`)); + }); + } + + // Show the recent tasks used for context + if (recentTasks.length > 0) { + console.log(chalk.gray(`\n Using recent tasks for context:`)); + recentTasks.slice(0, 3).forEach(t => { + console.log(chalk.cyan(` • Task ${t.id}: ${truncate(t.title, 50)}`)); + }); + } + + // Show dependency structure visualization + if (relevantPendingTasks.length > 0) { + const depsToShow = Math.min(3, relevantPendingTasks.length); + console.log(chalk.gray(`\n Task dependency patterns analyzed (${depsToShow} example${depsToShow > 1 ? 's' : ''}):`)); + relevantPendingTasks.slice(0, depsToShow).forEach(task => { + const depsStr = task.dependencies && task.dependencies.length > 0 + ? chalk.yellow(task.dependencies.join(', ')) + : chalk.gray('None'); + console.log(chalk.blue(` • Task ${task.id} → [${depsStr}]`)); + }); + } + + // Add information about which tasks will be provided in detail + if (uniqueDetailedTasks.length > 0) { + console.log(chalk.gray(`\n Providing detailed context for ${uniqueDetailedTasks.length} most relevant tasks:`)); + uniqueDetailedTasks.forEach(t => { + console.log(chalk.green(` • Task ${t.id}: ${truncate(t.title, 40)}`)); + }); + } + + console.log(); // Add spacing + } + } + + // System Prompt - Enhanced for dependency awareness +// ... existing code ... +``` + +Now let's modify the task generation display to include context size information and make it more transparent about what's being sent to the AI: + +```javascript +// ... existing code ... + // Add a visual transition to show we're moving to AI generation + console.log(boxen( + chalk.white.bold('AI Task Generation') + + `\n\n${chalk.gray('Analyzing context and generating task details using AI...')}` + + `\n${chalk.cyan('Context size: ')}${chalk.yellow(contextTasks.length.toLocaleString())} characters` + + `\n${chalk.cyan('Dependency detection: ')}${chalk.yellow(numericDependencies.length > 0 ? 'Explicit dependencies' : 'Auto-discovery mode')}` + + `\n${chalk.cyan('Detailed tasks: ')}${chalk.yellow( + numericDependencies.length > 0 + ? dependentTasks.length + : (uniqueDetailedTasks ? uniqueDetailedTasks.length : 0) + )}` + + (promptCategory + ? `\n${chalk.cyan('Task category: ')}${chalk.yellow(promptCategory.label)}` + : '') + + `\n${chalk.cyan('Dependency analysis depth: ')}${chalk.yellow('Full chain analysis')}`, + { + padding: 1, + borderColor: 'blue', + borderStyle: 'round', + margin: { top: 0, bottom: 1 } + } + )); + + loadingIndicator = startLoadingIndicator( + `Generating new task with ${useResearch ? 'Research' : 'Main'} AI...\n` + ); + } + + try { +// ... existing code ... +``` + +Now, let's update the system prompt to include specific guidance for the AI on how to make better dependency choices: + +```javascript +// ... existing code ... +// System Prompt - Enhanced for dependency awareness +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, adhering strictly to the provided JSON schema. Pay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on.\n\n" + + 'When determining dependencies for a new task, follow these principles:\n' + + '1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n' + + '2. Prioritize task dependencies that are semantically related to the functionality being built.\n' + + '3. Avoid defaulting to early tasks (like tasks #1-3) unless truly necessary foundation work.\n' + + '4. Consider implementation details of candidate dependent tasks, not just titles.\n' + + '5. Include the most specific dependencies rather than their parents when appropriate.\n' + + '6. Focus on technical dependencies rather than organizational ones.'; + +// Task Structure Description (for user prompt) +// ... existing code ... +``` + +Let's test the enhanced context and dependency analysis: + +--- + +**User** + +error + +--- + +**Cursor** + +I see the error - we're trying to use `uniqueDetailedTasks` in our display, but it's only defined in one code branch. Let's fix that: + +```javascript +// ... existing code ... + // CLI-only feedback for the dependency analysis + if (outputFormat === 'text') { + console.log(boxen( + chalk.cyan.bold('Task Context Analysis') + '\n', + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 0, bottom: 0 }, + borderColor: 'cyan', + borderStyle: 'round' + } + )); + } + + // Initialize variables that will be used in either branch + let dependentTasks = []; + let uniqueDetailedTasks = []; + let promptCategory = null; + + if (numericDependencies.length > 0) { + // If specific dependencies were provided, focus on them + dependentTasks = data.tasks.filter((t) => + numericDependencies.includes(t.id) + ); + + // These are our detailed tasks when explicit dependencies are provided + uniqueDetailedTasks = dependentTasks; + + contextTasks = `\nThis task depends on the following tasks:\n${dependentTasks + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; +// ... existing code ... + + // Add a visual transition to show we're moving to AI generation + console.log(boxen( + chalk.white.bold('AI Task Generation') + + `\n\n${chalk.gray('Analyzing context and generating task details using AI...')}` + + `\n${chalk.cyan('Context size: ')}${chalk.yellow(contextTasks.length.toLocaleString())} characters` + + `\n${chalk.cyan('Dependency detection: ')}${chalk.yellow(numericDependencies.length > 0 ? 'Explicit dependencies' : 'Auto-discovery mode')}` + + `\n${chalk.cyan('Detailed tasks: ')}${chalk.yellow(uniqueDetailedTasks.length)}` + + (promptCategory + ? `\n${chalk.cyan('Task category: ')}${chalk.yellow(promptCategory.label)}` + : '') + + `\n${chalk.cyan('Dependency analysis depth: ')}${chalk.yellow('Full chain analysis')}`, +// ... existing code ... +``` + +Let's also fix the variable that's still defined inside the else branch instead of using our initialization: + +```javascript +// ... existing code ... + const purposeCategories = [ + {pattern: /(command|cli|flag)/i, label: "CLI commands"}, + {pattern: /(task|subtask|add)/i, label: "Task management"}, + {pattern: /(dependency|depend)/i, label: "Dependency handling"}, + {pattern: /(AI|model|prompt)/i, label: "AI integration"}, + {pattern: /(UI|display|show)/i, label: "User interface"} + ]; + + promptCategory = purposeCategories.find(cat => cat.pattern.test(prompt)); + const categoryTasks = promptCategory + ? data.tasks.filter(t => + promptCategory.pattern.test(t.title) || + promptCategory.pattern.test(t.description) || + (t.details && promptCategory.pattern.test(t.details)) + ) +// ... existing code ... +``` + +Let's test our fix: + +--- + +**User** + +despite ALL that, we still got 1 and 3 -- does that imply our previous implemenation was fine? + +we didnt use that many input tokens so im guessing no other tasks were added as dependencies. idk, maybe the issue is the way the keywords/search is working. it should be more fuzzy/smarter or rely more on the LLM even though we definitely would prefer to do as much as we can here to prepare the same information in smaller/condensed/curated format before passing it + +but in the end what really matters is to properly capture dependencies. it's possible we dont actually need to -- you can get the get-tasks tool taht you have access to get ALL tasks and make that determination for yourself so you can do a spot check + +--- + +**Cursor** + +You're right - despite our enhanced context and guidance, the AI still defaulted to those foundational tasks (1 and 3). It's particularly interesting that the output shows "Detailed tasks: 0" even though we were providing detailed information for 5 tasks. + +This suggests a few things: + +1. There might still be an issue in how we're tracking `uniqueDetailedTasks` in the code. + +2. The model might have a bias toward those early tasks since they are genuinely foundational (Task 1: "Implement Task Data Structure" and Task 3: "Implement Basic Task Operations"). + +3. Our keyword matching might not be catching the truly relevant semantic connections between tasks. + +You've made an excellent point about letting the AI have more direct access to tasks. Here are two approaches we could take: + +1. **AI-assisted pre-processing**: We could use an AI call to identify potential dependencies before the main task creation, specifically to analyze relevance. + +2. **Direct access approach**: As you suggested, we could rely on the AI's ability to access all tasks directly through tools like `get_tasks`. This approach leverages the model's semantic understanding while giving it full access to the data. + +Would you like me to implement the fix for the "Detailed tasks: 0" issue, or would you prefer to move toward the direct access approach where the AI uses tools to gather its own context? + +--- + +**User** + +first, do a get-tasks to get that context + +--- + +**Cursor** + +Let me get all the tasks to better understand the context: + +--- + +**User** + +no. use the mcp tool get_tasks + +--- + +**Cursor** + +Let me get the tasks using the MCP tool: + +Now, let me get the task details for task ID 88 as you requested: + +Looking at the details of task #88 and the issue you mentioned, I'll now examine the current dependency handling in add-task.js: + +Let me examine more of the file to understand how dependencies are handled in the final part: + +Based on my analysis of the code and Task #88's requirements, let me identify how we can enhance the add-task functionality to better consider all task dependencies. Here's what I've found: + +1. **Current Implementation**: + + - The add-task feature mainly considers explicitly provided dependencies (via CLI flags) or AI-suggested dependencies + - It provides some context to the AI about dependency chains when explicit dependencies are provided + - It does surface information about tasks with similar keywords, categories, or recent tasks when no explicit dependencies are provided + - It has logic to validate dependencies and remove invalid ones + +2. **Areas for Improvement**: + - The current implementation doesn't recursively analyze the dependency structure beyond one level + - When determining new task dependencies, it doesn't analyze how dependencies of dependencies might affect the new task + - It could provide more comprehensive dependency chain information to the AI model + - The system could better detect and suggest indirect dependencies automatically + +Let me implement these improvements to enhance the add-task functionality: + +```javascript +// ... existing code ... + +// Create a consistent logFn object regardless of context + const logFn = isMCP + ? mcpLog // Use MCP logger if provided + : { + // Create a wrapper around consoleLog for CLI + info: (...args) => consoleLog('info', ...args), + warn: (...args) => consoleLog('warn', ...args), + error: (...args) => consoleLog('error', ...args), + debug: (...args) => consoleLog('debug', ...args), + success: (...args) => consoleLog('success', ...args) + }; + + const effectivePriority = priority || getDefaultPriority(projectRoot); + + logFn.info( + `Adding new task with prompt: "${prompt}", Priority: ${effectivePriority}, Dependencies: ${dependencies.join(', ') || 'None'}, Research: ${useResearch}, ProjectRoot: ${projectRoot}` + ); + + let loadingIndicator = null; + let aiServiceResponse = null; // To store the full response from AI service + + // Create custom reporter that checks for MCP log + const report = (message, level = 'info') => { + if (mcpLog) { + mcpLog[level](message); + } else if (outputFormat === 'text') { + consoleLog(level, message); + } + }; + + /** + * Recursively builds a dependency graph for a given task + * @param {Array} tasks - All tasks from tasks.json + * @param {number} taskId - ID of the task to analyze + * @param {Set} visited - Set of already visited task IDs + * @param {Map} depthMap - Map of task ID to its depth in the graph + * @param {number} depth - Current depth in the recursion + * @return {Object} Dependency graph data + */ + function buildDependencyGraph(tasks, taskId, visited = new Set(), depthMap = new Map(), depth = 0) { + // Skip if we've already visited this task or it doesn't exist + if (visited.has(taskId)) { + return null; + } + + // Find the task + const task = tasks.find(t => t.id === taskId); + if (!task) { + return null; + } + + // Mark as visited + visited.add(taskId); + + // Update depth if this is a deeper path to this task + if (!depthMap.has(taskId) || depth < depthMap.get(taskId)) { + depthMap.set(taskId, depth); + } + + // Process dependencies + const dependencyData = []; + if (task.dependencies && task.dependencies.length > 0) { + for (const depId of task.dependencies) { + const depData = buildDependencyGraph(tasks, depId, visited, depthMap, depth + 1); + if (depData) { + dependencyData.push(depData); + } + } + } + + return { + id: task.id, + title: task.title, + description: task.description, + status: task.status, + dependencies: dependencyData + }; + } + + try { + // Read the existing tasks + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + report('Invalid or missing tasks.json.', 'error'); + throw new Error('Invalid or missing tasks.json.'); + } + + // Find the highest task ID to determine the next ID + const highestId = + data.tasks.length > 0 ? Math.max(...data.tasks.map((t) => t.id)) : 0; + const newTaskId = highestId + 1; + + // Only show UI box for CLI mode + if (outputFormat === 'text') { + 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) => { + // Ensure depId is parsed as a number for comparison + const numDepId = parseInt(depId, 10); + return isNaN(numDepId) || !data.tasks.some((t) => t.id === numDepId); + }); + + if (invalidDeps.length > 0) { + report( + `The following dependencies do not exist or are invalid: ${invalidDeps.join(', ')}`, + 'warn' + ); + report('Removing invalid dependencies...', 'info'); + dependencies = dependencies.filter( + (depId) => !invalidDeps.includes(depId) + ); + } + // Ensure dependencies are numbers + const numericDependencies = dependencies.map((dep) => parseInt(dep, 10)); + + // Build dependency graphs for explicitly specified dependencies + const dependencyGraphs = []; + const allRelatedTaskIds = new Set(); + const depthMap = new Map(); + + // First pass: build a complete dependency graph for each specified dependency + for (const depId of numericDependencies) { + const graph = buildDependencyGraph(data.tasks, depId, new Set(), depthMap); + if (graph) { + dependencyGraphs.push(graph); + } + } + + // Second pass: build a set of all related task IDs for flat analysis + for (const [taskId, depth] of depthMap.entries()) { + allRelatedTaskIds.add(taskId); + } + + let taskData; + + // Check if manual task data is provided + if (manualTaskData) { + report('Using manually provided task data', 'info'); + taskData = manualTaskData; + report('DEBUG: Taking MANUAL task data path.', 'debug'); + + // Basic validation for manual data + if ( + !taskData.title || + typeof taskData.title !== 'string' || + !taskData.description || + typeof taskData.description !== 'string' + ) { + throw new Error( + 'Manual task data must include at least a title and description.' + ); + } + } else { + report('DEBUG: Taking AI task generation path.', 'debug'); + // --- Refactored AI Interaction --- + report(`Generating task data with AI with prompt:\n${prompt}`, 'info'); + + // Create context string for task creation prompt + let contextTasks = ''; + + // Create a dependency map for better understanding of the task relationships + const taskMap = {}; + data.tasks.forEach(t => { + // For each task, only include id, title, description, and dependencies + taskMap[t.id] = { + id: t.id, + title: t.title, + description: t.description, + dependencies: t.dependencies || [], + status: t.status + }; + }); + + // CLI-only feedback for the dependency analysis + if (outputFormat === 'text') { + console.log(boxen( + chalk.cyan.bold('Task Context Analysis') + '\n', + { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 0, bottom: 0 }, + borderColor: 'cyan', + borderStyle: 'round' + } + )); + } + + // Initialize variables that will be used in either branch + let dependentTasks = []; + let uniqueDetailedTasks = []; + let promptCategory = null; + + if (numericDependencies.length > 0) { + // If specific dependencies were provided, focus on them + // Get all tasks that were found in the dependency graph + dependentTasks = Array.from(allRelatedTaskIds).map(id => + data.tasks.find(t => t.id === id) + ).filter(Boolean); + + // Sort by depth in the dependency chain + dependentTasks.sort((a, b) => { + const depthA = depthMap.get(a.id) || 0; + const depthB = depthMap.get(b.id) || 0; + return depthA - depthB; // Lowest depth (root dependencies) first + }); + + // Limit the number of detailed tasks to avoid context explosion + uniqueDetailedTasks = dependentTasks.slice(0, 8); + + contextTasks = `\nThis task relates to a dependency structure with ${dependentTasks.length} related tasks in the chain.\n\nDirect dependencies:`; + const directDeps = data.tasks.filter(t => numericDependencies.includes(t.id)); + contextTasks += `\n${directDeps.map(t => `- Task ${t.id}: ${t.title} - ${t.description}`).join('\n')}`; + + // Add an overview of indirect dependencies if present + const indirectDeps = dependentTasks.filter(t => !numericDependencies.includes(t.id)); + if (indirectDeps.length > 0) { + contextTasks += `\n\nIndirect dependencies (dependencies of dependencies):`; + contextTasks += `\n${indirectDeps.slice(0, 5).map(t => `- Task ${t.id}: ${t.title} - ${t.description}`).join('\n')}`; + if (indirectDeps.length > 5) { + contextTasks += `\n- ... and ${indirectDeps.length - 5} more indirect dependencies`; + } + } + + // Add more details about each dependency, prioritizing direct dependencies + contextTasks += `\n\nDetailed information about dependencies:`; + for (const depTask of uniqueDetailedTasks) { + const depthInfo = depthMap.get(depTask.id) ? ` (depth: ${depthMap.get(depTask.id)})` : ''; + const isDirect = numericDependencies.includes(depTask.id) ? " [DIRECT DEPENDENCY]" : ""; + + contextTasks += `\n\n------ Task ${depTask.id}${isDirect}${depthInfo}: ${depTask.title} ------\n`; + contextTasks += `Description: ${depTask.description}\n`; + contextTasks += `Status: ${depTask.status || 'pending'}\n`; + contextTasks += `Priority: ${depTask.priority || 'medium'}\n`; + + // List its dependencies + if (depTask.dependencies && depTask.dependencies.length > 0) { + const depDeps = depTask.dependencies.map(dId => { + const depDepTask = data.tasks.find(t => t.id === dId); + return depDepTask ? `Task ${dId}: ${depDepTask.title}` : `Task ${dId}`; + }); + contextTasks += `Dependencies: ${depDeps.join(', ')}\n`; + } else { + contextTasks += `Dependencies: None\n`; + } + + // Add implementation details but truncate if too long + if (depTask.details) { + const truncatedDetails = depTask.details.length > 400 + ? depTask.details.substring(0, 400) + '... (truncated)' + : depTask.details; + contextTasks += `Implementation Details: ${truncatedDetails}\n`; + } + } + + // Add dependency chain visualization + if (dependencyGraphs.length > 0) { + contextTasks += '\n\nDependency Chain Visualization:'; + + // Helper function to format dependency chain as text + function formatDependencyChain(node, prefix = '', isLast = true, depth = 0) { + if (depth > 3) return ''; // Limit depth to avoid excessive nesting + + const connector = isLast ? '└── ' : '├── '; + const childPrefix = isLast ? ' ' : '│ '; + + let result = `\n${prefix}${connector}Task ${node.id}: ${node.title}`; + + if (node.dependencies && node.dependencies.length > 0) { + for (let i = 0; i < node.dependencies.length; i++) { + const isLastChild = i === node.dependencies.length - 1; + result += formatDependencyChain( + node.dependencies[i], + prefix + childPrefix, + isLastChild, + depth + 1 + ); + } + } + + return result; + } + + // Format each dependency graph + for (const graph of dependencyGraphs) { + contextTasks += formatDependencyChain(graph); + } + } + + // Show dependency analysis in CLI mode + if (outputFormat === 'text') { + if (directDeps.length > 0) { + console.log(chalk.gray(` Explicitly specified dependencies:`)); + directDeps.forEach(t => { + console.log(chalk.yellow(` • Task ${t.id}: ${truncate(t.title, 50)}`)); + }); + } + + if (indirectDeps.length > 0) { + console.log(chalk.gray(`\n Indirect dependencies (${indirectDeps.length} total):`)); + indirectDeps.slice(0, 3).forEach(t => { + const depth = depthMap.get(t.id) || 0; + console.log(chalk.cyan(` • Task ${t.id} [depth ${depth}]: ${truncate(t.title, 45)}`)); + }); + if (indirectDeps.length > 3) { + console.log(chalk.cyan(` • ... and ${indirectDeps.length - 3} more indirect dependencies`)); + } + } + + // Visualize the dependency chain + if (dependencyGraphs.length > 0) { + console.log(chalk.gray(`\n Dependency chain visualization:`)); + + // Convert dependency graph to ASCII art for terminal + function visualizeDependencyGraph(node, prefix = '', isLast = true, depth = 0) { + if (depth > 2) return; // Limit depth for display + + const connector = isLast ? '└── ' : '├── '; + const childPrefix = isLast ? ' ' : '│ '; + + console.log(chalk.blue(` ${prefix}${connector}Task ${node.id}: ${truncate(node.title, 40)}`)); + + if (node.dependencies && node.dependencies.length > 0) { + for (let i = 0; i < node.dependencies.length; i++) { + const isLastChild = i === node.dependencies.length - 1; + visualizeDependencyGraph( + node.dependencies[i], + prefix + childPrefix, + isLastChild, + depth + 1 + ); + } + } + } + + // Visualize each dependency graph + for (const graph of dependencyGraphs) { + visualizeDependencyGraph(graph); + } + } + + console.log(); // Add spacing + } + } else { +// ... existing code ... +} + +// ... existing code ... + +// Create the new task object + const newTask = { + id: newTaskId, + title: taskData.title, + description: taskData.description, + details: taskData.details || '', + testStrategy: taskData.testStrategy || '', + status: 'pending', + dependencies: taskData.dependencies?.length ? taskData.dependencies : numericDependencies, // Use AI-suggested dependencies if available, fallback to manually specified + priority: effectivePriority, + subtasks: [] // Initialize with empty subtasks array + }; + + // Additional check: validate all dependencies in the AI response + if (taskData.dependencies?.length) { + const allValidDeps = taskData.dependencies.every(depId => { + const numDepId = parseInt(depId, 10); + return !isNaN(numDepId) && data.tasks.some(t => t.id === numDepId); + }); + + if (!allValidDeps) { + report('AI suggested invalid dependencies. Filtering them out...', 'warn'); + newTask.dependencies = taskData.dependencies.filter(depId => { + const numDepId = parseInt(depId, 10); + return !isNaN(numDepId) && data.tasks.some(t => t.id === numDepId); + }); + } + } + + // Add the task to the tasks array + data.tasks.push(newTask); + +// ... existing code ... +``` + +Now, let's update the system prompt to better guide the AI in making accurate dependency recommendations using the enhanced dependency information we're providing: + +```javascript +// ... existing code ... + +// System Prompt - Enhanced for dependency awareness +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, adhering strictly to the provided JSON schema. Pay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on.\n\n" + + 'When determining dependencies for a new task, follow these principles:\n' + + '1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n' + + '2. Prioritize task dependencies that are semantically related to the functionality being built.\n' + + '3. Avoid defaulting to early tasks (like tasks #1-3) unless truly necessary foundation work.\n' + + '4. Consider implementation details of candidate dependent tasks, not just titles.\n' + + '5. Include the most specific dependencies rather than their parents when appropriate.\n' + + '6. Focus on technical dependencies rather than organizational ones.'; + +// ... existing code to ... + +// System Prompt - Enhanced for comprehensive dependency awareness +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, adhering strictly to the provided JSON schema. Pay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on.\n\n" + + 'When determining dependencies for a new task, follow these principles:\n' + + '1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n' + + '2. Analyze the entire dependency chain to find the most specific and direct dependencies.\n' + + '3. Consider both direct and indirect (transitive) dependencies - a task may depend on another task that depends on yet another task.\n' + + '4. When possible, reference the most specific or leaf-node tasks in a dependency chain rather than their parents.\n' + + '5. Prioritize task dependencies that are semantically related to the functionality being built.\n' + + '6. Consider the depth of the dependency chain - tasks deeper in the chain might be more specific.\n' + + '7. Respect the existing dependency structure in the project to maintain consistency.\n' + + '8. Avoid defaulting to early foundational tasks (like tasks #1-3) unless absolutely necessary.\n' + + '9. Consider implementation details of candidate dependent tasks, not just their titles.\n' + + '10. Balance between too many dependencies (overly constrained) and too few (missing prerequisite work).\n' + + "11. Pay attention to tasks marked as 'done' vs 'pending' when considering dependencies.\n" + + '12. Base dependency decisions on technical requirements rather than organizational preferences.'; + +// ... existing code ... +``` + +Now, let's update the user prompt to utilize the enhanced dependency information more effectively: + +```javascript +// ... existing code ... + +// User Prompt +const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project. + + ${contextTasks} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + + Based on the information about existing tasks and their dependencies, include appropriate dependencies in the "dependencies" array if any are needed. Only include task IDs that this new task directly depends on. + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific. Don't include the task ID in the title.`; + +// ... existing code to ... + +// User Prompt - Enhanced for better dependency analysis +const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project. + + ${contextTasks} + ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + + Based on the detailed information about existing tasks and their dependency relationships, carefully consider which dependencies this new task should have: + + 1. ANALYZE: Review the entire dependency structure provided above, including both direct and indirect dependencies. + 2. IDENTIFY: Determine which specific existing tasks must be completed before this new task can begin. + 3. CONSIDER: Think about both technical requirements and logical task ordering. + 4. DECIDE: Select only the most direct, specific dependencies - avoid including a parent task if you're already including its child task. + 5. VALIDATE: Ensure all selected dependencies are necessary and sufficient for this task to begin. + + Include these carefully selected task IDs in the "dependencies" array of your response. If there are no logical dependencies, return an empty array []. + + Return your answer as a single JSON object matching the schema precisely: + ${taskStructureDesc} + + Make sure the details and test strategy are thorough and specific. Don't include the task ID in the title.`; + +// ... existing code ... +``` + +Let's also enhance the CLI output to display dependency information for the newly created task: + +```javascript +// ... existing code ... + +// Check if AI added new dependencies that weren't explicitly provided +const aiAddedDeps = newTask.dependencies.filter( + (dep) => !numericDependencies.includes(dep) +); + +// Show success message box +console.log( + boxen( + chalk.white.bold(`Task ${newTaskId} Created Successfully`) + + '\n\n' + + chalk.white(`Title: ${newTask.title}`) + + '\n' + + chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) + + '\n' + + chalk.white( + `Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}` + ) + + '\n' + + (newTask.dependencies.length > 0 + ? chalk.white(`Dependencies: ${newTask.dependencies.join(', ')}`) + + (aiAddedDeps.length > 0 + ? chalk.gray(` (${aiAddedDeps.length} auto-detected by AI)`) + : '') + + '\n' + : '') + + '\n' + + chalk.white.bold('Next Steps:') + + '\n' + + chalk.cyan( + `1. Run ${chalk.yellow(`task-master show ${newTaskId}`)} to see complete task details` + ) + + '\n' + + chalk.cyan( + `2. Run ${chalk.yellow(`task-master set-status --id=${newTaskId} --status=in-progress`)} to start working on it` + ) + + '\n' + + chalk.cyan( + `3. Run ${chalk.yellow(`task-master expand --id=${newTaskId}`)} to break it down into subtasks` + ), + { padding: 1, borderColor: 'green', borderStyle: 'round' } + ) +); + +// ... existing code to ... + +// Check if AI added new dependencies that weren't explicitly provided +const aiAddedDeps = newTask.dependencies.filter( + (dep) => !numericDependencies.includes(dep) +); + +// Check if AI removed any dependencies that were explicitly provided +const aiRemovedDeps = numericDependencies.filter( + (dep) => !newTask.dependencies.includes(dep) +); + +// Get task titles for dependencies to display +const depTitles = {}; +newTask.dependencies.forEach((dep) => { + const depTask = data.tasks.find((t) => t.id === dep); + if (depTask) { + depTitles[dep] = truncate(depTask.title, 30); + } +}); + +// Prepare dependency display string +let dependencyDisplay = ''; +if (newTask.dependencies.length > 0) { + dependencyDisplay = chalk.white('Dependencies:') + '\n'; + newTask.dependencies.forEach((dep) => { + const isAiAdded = aiAddedDeps.includes(dep); + const depType = isAiAdded ? chalk.yellow(' (AI suggested)') : ''; + dependencyDisplay += + chalk.white(` - ${dep}: ${depTitles[dep] || 'Unknown task'}${depType}`) + + '\n'; + }); +} else { + dependencyDisplay = chalk.white('Dependencies: None') + '\n'; +} + +// Add info about removed dependencies if any +if (aiRemovedDeps.length > 0) { + dependencyDisplay += + chalk.gray('\nUser-specified dependencies that were not used:') + '\n'; + aiRemovedDeps.forEach((dep) => { + const depTask = data.tasks.find((t) => t.id === dep); + const title = depTask ? truncate(depTask.title, 30) : 'Unknown task'; + dependencyDisplay += chalk.gray(` - ${dep}: ${title}`) + '\n'; + }); +} + +// Add dependency analysis summary +let dependencyAnalysis = ''; +if (aiAddedDeps.length > 0 || aiRemovedDeps.length > 0) { + dependencyAnalysis = '\n' + chalk.white.bold('Dependency Analysis:') + '\n'; + if (aiAddedDeps.length > 0) { + dependencyAnalysis += + chalk.green( + `AI identified ${aiAddedDeps.length} additional dependencies` + ) + '\n'; + } + if (aiRemovedDeps.length > 0) { + dependencyAnalysis += + chalk.yellow( + `AI excluded ${aiRemovedDeps.length} user-provided dependencies` + ) + '\n'; + } +} + +// Show success message box +console.log( + boxen( + chalk.white.bold(`Task ${newTaskId} Created Successfully`) + + '\n\n' + + chalk.white(`Title: ${newTask.title}`) + + '\n' + + chalk.white(`Status: ${getStatusWithColor(newTask.status)}`) + + '\n' + + chalk.white( + `Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}` + ) + + '\n\n' + + dependencyDisplay + + dependencyAnalysis + + '\n' + + chalk.white.bold('Next Steps:') + + '\n' + + chalk.cyan( + `1. Run ${chalk.yellow(`task-master show ${newTaskId}`)} to see complete task details` + ) + + '\n' + + chalk.cyan( + `2. Run ${chalk.yellow(`task-master set-status --id=${newTaskId} --status=in-progress`)} to start working on it` + ) + + '\n' + + chalk.cyan( + `3. Run ${chalk.yellow(`task-master expand --id=${newTaskId}`)} to break it down into subtasks` + ), + { padding: 1, borderColor: 'green', borderStyle: 'round' } + ) +); + +// ... existing code ... +``` + +Let's test our changes to see how they improve the task dependency handling: diff --git a/docs/command-reference.md b/docs/command-reference.md index cd0d801f..f2e77535 100644 --- a/docs/command-reference.md +++ b/docs/command-reference.md @@ -187,6 +187,32 @@ task-master validate-dependencies task-master fix-dependencies ``` +## Move Tasks + +```bash +# Move a task or subtask to a new position +task-master move --from= --to= + +# Examples: +# Move task to become a subtask +task-master move --from=5 --to=7 + +# Move subtask to become a standalone task +task-master move --from=5.2 --to=7 + +# Move subtask to a different parent +task-master move --from=5.2 --to=7.3 + +# Reorder subtasks within the same parent +task-master move --from=5.2 --to=5.4 + +# Move a task to a new ID position (creates placeholder if doesn't exist) +task-master move --from=5 --to=25 + +# Move multiple tasks at once (must have the same number of IDs) +task-master move --from=10,11,12 --to=16,17,18 +``` + ## Add a New Task ```bash diff --git a/docs/examples.md b/docs/examples.md index d91b16fa..133dce78 100644 --- a/docs/examples.md +++ b/docs/examples.md @@ -30,7 +30,7 @@ I need to regenerate the subtasks for task 3 with a different approach. Can you ## Handling changes ``` -We've decided to use MongoDB instead of PostgreSQL. Can you update all future tasks to reflect this change? +I've decided to use MongoDB instead of PostgreSQL. Can you update all future tasks to reflect this change? ``` ## Completing work @@ -40,6 +40,34 @@ 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. ``` +## Reorganizing tasks + +``` +I think subtask 5.2 would fit better as part of task 7. Can you move it there? +``` + +(Agent runs: `task-master move --from=5.2 --to=7.3`) + +``` +Task 8 should actually be a subtask of task 4. Can you reorganize this? +``` + +(Agent runs: `task-master move --from=8 --to=4.1`) + +``` +I just merged the main branch and there's a conflict in tasks.json. My teammates created tasks 10-15 on their branch while I created tasks 10-12 on my branch. Can you help me resolve this by moving my tasks? +``` + +(Agent runs: + +```bash +task-master move --from=10 --to=16 +task-master move --from=11 --to=17 +task-master move --from=12 --to=18 +``` + +) + ## Analyzing complexity ``` diff --git a/docs/tutorial.md b/docs/tutorial.md index bd2f6890..8209f583 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -268,7 +268,61 @@ task-master update --from=4 --prompt="Update to use MongoDB, researching best pr This will rewrite or re-scope subsequent tasks in tasks.json while preserving completed work. -### 6. Breaking Down Complex Tasks +### 6. Reorganizing Tasks + +If you need to reorganize your task structure: + +``` +I think subtask 5.2 would fit better as part of task 7 instead. Can you move it there? +``` + +The agent will execute: + +```bash +task-master move --from=5.2 --to=7.3 +``` + +You can reorganize tasks in various ways: + +- Moving a standalone task to become a subtask: `--from=5 --to=7` +- Moving a subtask to become a standalone task: `--from=5.2 --to=7` +- Moving a subtask to a different parent: `--from=5.2 --to=7.3` +- Reordering subtasks within the same parent: `--from=5.2 --to=5.4` +- Moving a task to a new ID position: `--from=5 --to=25` (even if task 25 doesn't exist yet) +- Moving multiple tasks at once: `--from=10,11,12 --to=16,17,18` (must have same number of IDs, Taskmaster will look through each position) + +When moving tasks to new IDs: + +- The system automatically creates placeholder tasks for non-existent destination IDs +- This prevents accidental data loss during reorganization +- Any tasks that depend on moved tasks will have their dependencies updated +- When moving a parent task, all its subtasks are automatically moved with it and renumbered + +This is particularly useful as your project understanding evolves and you need to refine your task structure. + +### 7. Resolving Merge Conflicts with Tasks + +When working with a team, you might encounter merge conflicts in your tasks.json file if multiple team members create tasks on different branches. The move command makes resolving these conflicts straightforward: + +``` +I just merged the main branch and there's a conflict with tasks.json. My teammates created tasks 10-15 while I created tasks 10-12 on my branch. Can you help me resolve this? +``` + +The agent will help you: + +1. Keep your teammates' tasks (10-15) +2. Move your tasks to new positions to avoid conflicts: + +```bash +# Move your tasks to new positions (e.g., 16-18) +task-master move --from=10 --to=16 +task-master move --from=11 --to=17 +task-master move --from=12 --to=18 +``` + +This approach preserves everyone's work while maintaining a clean task structure, making it much easier to handle task conflicts than trying to manually merge JSON files. + +### 8. Breaking Down Complex Tasks For complex tasks that need more granularity: diff --git a/llms-install.md b/llms-install.md new file mode 100644 index 00000000..8b2a4bbb --- /dev/null +++ b/llms-install.md @@ -0,0 +1,131 @@ +``# Taskmaster AI Installation Guide + +This guide helps AI assistants install and configure Taskmaster for users in their development projects. + +## What is Taskmaster? + +Taskmaster is an AI-driven task management system designed for development workflows. It helps break down projects into manageable tasks, track dependencies, and maintain development momentum through structured, AI-enhanced planning. + +## Installation Steps + +### Step 1: Add MCP Configuration + +Add the following configuration to the user's MCP settings file (`.cursor/mcp.json` for Cursor, or equivalent for other editors): + +```json +{ + "mcpServers": { + "taskmaster-ai": { + "command": "npx", + "args": ["-y", "--package=task-master-ai", "task-master-ai"], + "env": { + "ANTHROPIC_API_KEY": "user_will_add_their_key_here", + "PERPLEXITY_API_KEY": "user_will_add_their_key_here", + "OPENAI_API_KEY": "user_will_add_their_key_here", + "GOOGLE_API_KEY": "user_will_add_their_key_here", + "MISTRAL_API_KEY": "user_will_add_their_key_here", + "OPENROUTER_API_KEY": "user_will_add_their_key_here", + "XAI_API_KEY": "user_will_add_their_key_here" + } + } + } +} +``` + +### Step 2: API Key Requirements + +Inform the user they need **at least one** API key from the following providers: + +- **Anthropic** (for Claude models) - Recommended +- **OpenAI** (for GPT models) +- **Google** (for Gemini models) +- **Perplexity** (for research features) - Highly recommended +- **Mistral** (for Mistral models) +- **OpenRouter** (access to multiple models) +- **xAI** (for Grok models) + +The user will be able to define 3 separate roles (can be the same provider or separate providers) for main AI operations, research operations (research providers/models only), and a fallback model in case of errors. + +### Step 3: Initialize Project + +Once the MCP server is configured and API keys are added, initialize Taskmaster in the user's project: + +> Can you initialize Task Master in my project? + +This will run the `initialize_project` tool to set up the basic file structure. + +### Step 4: Create Initial Tasks + +Users have two options for creating initial tasks: + +**Option A: Parse a PRD (Recommended)** +If they have a Product Requirements Document: + +> Can you parse my PRD file at [path/to/prd.txt] to generate initial tasks? + +If the user does not have a PRD, the AI agent can help them create one and store it in scripts/prd.txt for parsing. + +**Option B: Start from scratch** + +> Can you help me add my first task: [describe the task] + +## Common Usage Patterns + +### Daily Workflow + +> What's the next task I should work on? +> Can you show me the details for task [ID]? +> Can you mark task [ID] as done? + +### Task Management + +> Can you break down task [ID] into subtasks? +> Can you add a new task: [description] +> Can you analyze the complexity of my tasks? + +### Project Organization + +> Can you show me all my pending tasks? +> Can you move task [ID] to become a subtask of [parent ID]? +> Can you update task [ID] with this new information: [details] + +## Verification Steps + +After installation, verify everything is working: + +1. **Check MCP Connection**: The AI should be able to access Task Master tools +2. **Test Basic Commands**: Try `get_tasks` to list current tasks +3. **Verify API Keys**: Ensure AI-powered commands work (like `add_task`) + +Note: An API key fallback exists that allows the MCP server to read API keys from `.env` instead of the MCP JSON config. It is recommended to have keys in both places in case the MCP server is unable to read keys from its environment for whatever reason. + +When adding keys to `.env` only, the `models` tool will explain that the keys are not OK for MCP. Despite this, the fallback should kick in and the API keys will be read from the `.env` file. + +## Troubleshooting + +**If MCP server doesn't start:** + +- Verify the JSON configuration is valid +- Check that Node.js is installed +- Ensure API keys are properly formatted + +**If AI commands fail:** + +- Verify at least one API key is configured +- Check API key permissions and quotas +- Try using a different model via the `models` tool + +## CLI Fallback + +Taskmaster is also available via CLI commands, by installing with `npm install task-master-ai@latest` in a terminal. Running `task-master help` will show all available commands, which offer a 1:1 experience with the MCP server. As the AI agent, you should refer to the system prompts and rules provided to you to identify Taskmaster-specific rules that help you understand how and when to use it. + +## Next Steps + +Once installed, users can: + +- Create new tasks with `add-task` or parse a PRD (scripts/prd.txt) into tasks with `parse-prd` +- Set up model preferences with `models` tool +- Expand tasks into subtasks with `expand-all` and `expand-task` +- Explore advanced features like research mode and complexity analysis + +For detailed documentation, refer to the Task Master docs directory.`` diff --git a/mcp-server/src/core/direct-functions/analyze-task-complexity.js b/mcp-server/src/core/direct-functions/analyze-task-complexity.js index 8a6cd4ca..861bbda8 100644 --- a/mcp-server/src/core/direct-functions/analyze-task-complexity.js +++ b/mcp-server/src/core/direct-functions/analyze-task-complexity.js @@ -18,6 +18,9 @@ import { createLogWrapper } from '../../tools/utils.js'; // Import the new utili * @param {string} args.outputPath - Explicit absolute path to save the report. * @param {string|number} [args.threshold] - Minimum complexity score to recommend expansion (1-10) * @param {boolean} [args.research] - Use Perplexity AI for research-backed complexity analysis + * @param {string} [args.ids] - Comma-separated list of task IDs to analyze + * @param {number} [args.from] - Starting task ID in a range to analyze + * @param {number} [args.to] - Ending task ID in a range to analyze * @param {string} [args.projectRoot] - Project root path. * @param {Object} log - Logger object * @param {Object} [context={}] - Context object containing session data @@ -26,7 +29,16 @@ import { createLogWrapper } from '../../tools/utils.js'; // Import the new utili */ export async function analyzeTaskComplexityDirect(args, log, context = {}) { const { session } = context; - const { tasksJsonPath, outputPath, threshold, research, projectRoot } = args; + const { + tasksJsonPath, + outputPath, + threshold, + research, + projectRoot, + ids, + from, + to + } = args; const logWrapper = createLogWrapper(log); @@ -58,6 +70,14 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) { log.info(`Analyzing task complexity from: ${tasksPath}`); log.info(`Output report will be saved to: ${resolvedOutputPath}`); + if (ids) { + log.info(`Analyzing specific task IDs: ${ids}`); + } else if (from || to) { + const fromStr = from !== undefined ? from : 'first'; + const toStr = to !== undefined ? to : 'last'; + log.info(`Analyzing tasks in range: ${fromStr} to ${toStr}`); + } + if (research) { log.info('Using research role for complexity analysis'); } @@ -68,7 +88,10 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) { output: outputPath, threshold: threshold, research: research === true, // Ensure boolean - projectRoot: projectRoot // Pass projectRoot here + projectRoot: projectRoot, // Pass projectRoot here + id: ids, // Pass the ids parameter to the core function as 'id' + from: from, // Pass from parameter + to: to // Pass to parameter }; // --- End Initial Checks --- diff --git a/mcp-server/src/core/direct-functions/move-task.js b/mcp-server/src/core/direct-functions/move-task.js new file mode 100644 index 00000000..d121c68d --- /dev/null +++ b/mcp-server/src/core/direct-functions/move-task.js @@ -0,0 +1,99 @@ +/** + * Direct function wrapper for moveTask + */ + +import { moveTask } from '../../../../scripts/modules/task-manager.js'; +import { findTasksJsonPath } from '../utils/path-utils.js'; +import { + enableSilentMode, + disableSilentMode +} from '../../../../scripts/modules/utils.js'; + +/** + * Move a task or subtask to a new position + * @param {Object} args - Function arguments + * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file + * @param {string} args.sourceId - ID of the task/subtask to move (e.g., '5' or '5.2') + * @param {string} args.destinationId - ID of the destination (e.g., '7' or '7.3') + * @param {string} args.file - Alternative path to the tasks.json file + * @param {string} args.projectRoot - Project root directory + * @param {Object} log - Logger object + * @returns {Promise<{success: boolean, data?: Object, error?: Object}>} + */ +export async function moveTaskDirect(args, log, context = {}) { + const { session } = context; + + // Validate required parameters + if (!args.sourceId) { + return { + success: false, + error: { + message: 'Source ID is required', + code: 'MISSING_SOURCE_ID' + } + }; + } + + if (!args.destinationId) { + return { + success: false, + error: { + message: 'Destination ID is required', + code: 'MISSING_DESTINATION_ID' + } + }; + } + + try { + // Find tasks.json path if not provided + let tasksPath = args.tasksJsonPath || args.file; + if (!tasksPath) { + if (!args.projectRoot) { + return { + success: false, + error: { + message: + 'Project root is required if tasksJsonPath is not provided', + code: 'MISSING_PROJECT_ROOT' + } + }; + } + tasksPath = findTasksJsonPath(args, log); + } + + // Enable silent mode to prevent console output during MCP operation + enableSilentMode(); + + // Call the core moveTask function, always generate files + const result = await moveTask( + tasksPath, + args.sourceId, + args.destinationId, + true + ); + + // Restore console output + disableSilentMode(); + + return { + success: true, + data: { + movedTask: result.movedTask, + message: `Successfully moved task/subtask ${args.sourceId} to ${args.destinationId}` + } + }; + } catch (error) { + // Restore console output in case of error + disableSilentMode(); + + log.error(`Failed to move task: ${error.message}`); + + return { + success: false, + error: { + message: error.message, + code: 'MOVE_TASK_ERROR' + } + }; + } +} diff --git a/mcp-server/src/core/direct-functions/parse-prd.js b/mcp-server/src/core/direct-functions/parse-prd.js index 511f9e70..a1853d92 100644 --- a/mcp-server/src/core/direct-functions/parse-prd.js +++ b/mcp-server/src/core/direct-functions/parse-prd.js @@ -31,6 +31,7 @@ export async function parsePRDDirect(args, log, context = {}) { numTasks: numTasksArg, force, append, + research, projectRoot } = args; @@ -114,8 +115,14 @@ export async function parsePRDDirect(args, log, context = {}) { } } + if (research) { + logWrapper.info( + 'Research mode enabled. Using Perplexity AI for enhanced PRD analysis.' + ); + } + logWrapper.info( - `Parsing PRD via direct function. Input: ${inputPath}, Output: ${outputPath}, NumTasks: ${numTasks}, Force: ${force}, Append: ${append}, ProjectRoot: ${projectRoot}` + `Parsing PRD via direct function. Input: ${inputPath}, Output: ${outputPath}, NumTasks: ${numTasks}, Force: ${force}, Append: ${append}, Research: ${research}, ProjectRoot: ${projectRoot}` ); const wasSilent = isSilentMode(); @@ -135,6 +142,7 @@ export async function parsePRDDirect(args, log, context = {}) { projectRoot, force, append, + research, commandName: 'parse-prd', outputType: 'mcp' }, diff --git a/mcp-server/src/core/task-master-core.js b/mcp-server/src/core/task-master-core.js index 28dbd4f0..8dabb92c 100644 --- a/mcp-server/src/core/task-master-core.js +++ b/mcp-server/src/core/task-master-core.js @@ -30,6 +30,7 @@ import { addDependencyDirect } from './direct-functions/add-dependency.js'; import { removeTaskDirect } from './direct-functions/remove-task.js'; import { initializeProjectDirect } from './direct-functions/initialize-project.js'; import { modelsDirect } from './direct-functions/models.js'; +import { moveTaskDirect } from './direct-functions/move-task.js'; // Re-export utility functions export { findTasksJsonPath } from './utils/path-utils.js'; @@ -60,7 +61,8 @@ export const directFunctions = new Map([ ['addDependencyDirect', addDependencyDirect], ['removeTaskDirect', removeTaskDirect], ['initializeProjectDirect', initializeProjectDirect], - ['modelsDirect', modelsDirect] + ['modelsDirect', modelsDirect], + ['moveTaskDirect', moveTaskDirect] ]); // Re-export all direct function implementations @@ -89,5 +91,6 @@ export { addDependencyDirect, removeTaskDirect, initializeProjectDirect, - modelsDirect + modelsDirect, + moveTaskDirect }; diff --git a/mcp-server/src/tools/analyze.js b/mcp-server/src/tools/analyze.js index ea6d23fe..221647cf 100644 --- a/mcp-server/src/tools/analyze.js +++ b/mcp-server/src/tools/analyze.js @@ -49,6 +49,24 @@ export function registerAnalyzeProjectComplexityTool(server) { .describe( 'Path to the tasks file relative to project root (default: tasks/tasks.json).' ), + ids: z + .string() + .optional() + .describe( + 'Comma-separated list of task IDs to analyze specifically (e.g., "1,3,5").' + ), + from: z.coerce + .number() + .int() + .positive() + .optional() + .describe('Starting task ID in a range to analyze.'), + to: z.coerce + .number() + .int() + .positive() + .optional() + .describe('Ending task ID in a range to analyze.'), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.') @@ -107,7 +125,10 @@ export function registerAnalyzeProjectComplexityTool(server) { outputPath: outputPath, threshold: args.threshold, research: args.research, - projectRoot: args.projectRoot + projectRoot: args.projectRoot, + ids: args.ids, + from: args.from, + to: args.to }, log, { session } diff --git a/mcp-server/src/tools/index.js b/mcp-server/src/tools/index.js index 863f28cf..3af66f08 100644 --- a/mcp-server/src/tools/index.js +++ b/mcp-server/src/tools/index.js @@ -28,6 +28,7 @@ import { registerAddDependencyTool } from './add-dependency.js'; import { registerRemoveTaskTool } from './remove-task.js'; import { registerInitializeProjectTool } from './initialize-project.js'; import { registerModelsTool } from './models.js'; +import { registerMoveTaskTool } from './move-task.js'; /** * Register all Task Master tools with the MCP server @@ -61,6 +62,7 @@ export function registerTaskMasterTools(server) { registerRemoveTaskTool(server); registerRemoveSubtaskTool(server); registerClearSubtasksTool(server); + registerMoveTaskTool(server); // Group 5: Task Analysis & Expansion registerAnalyzeProjectComplexityTool(server); diff --git a/mcp-server/src/tools/move-task.js b/mcp-server/src/tools/move-task.js new file mode 100644 index 00000000..afbe2f3b --- /dev/null +++ b/mcp-server/src/tools/move-task.js @@ -0,0 +1,129 @@ +/** + * tools/move-task.js + * Tool for moving tasks or subtasks to a new position + */ + +import { z } from 'zod'; +import { + handleApiResult, + createErrorResponse, + withNormalizedProjectRoot +} from './utils.js'; +import { moveTaskDirect } from '../core/task-master-core.js'; +import { findTasksJsonPath } from '../core/utils/path-utils.js'; + +/** + * Register the moveTask tool with the MCP server + * @param {Object} server - FastMCP server instance + */ +export function registerMoveTaskTool(server) { + server.addTool({ + name: 'move_task', + description: 'Move a task or subtask to a new position', + parameters: z.object({ + from: z + .string() + .describe( + 'ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated to move multiple tasks (e.g., "5,6,7")' + ), + to: z + .string() + .describe( + 'ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated' + ), + file: z.string().optional().describe('Custom path to tasks.json file'), + projectRoot: z + .string() + .optional() + .describe( + 'Root directory of the project (typically derived from session)' + ) + }), + execute: withNormalizedProjectRoot(async (args, { log, session }) => { + try { + // Find tasks.json path if not provided + let tasksJsonPath = args.file; + + if (!tasksJsonPath) { + tasksJsonPath = findTasksJsonPath(args, log); + } + + // Parse comma-separated IDs + const fromIds = args.from.split(',').map((id) => id.trim()); + const toIds = args.to.split(',').map((id) => id.trim()); + + // Validate matching IDs count + if (fromIds.length !== toIds.length) { + return createErrorResponse( + 'The number of source and destination IDs must match', + 'MISMATCHED_ID_COUNT' + ); + } + + // If moving multiple tasks + if (fromIds.length > 1) { + const results = []; + // Move tasks one by one, only generate files on the last move + for (let i = 0; i < fromIds.length; i++) { + const fromId = fromIds[i]; + const toId = toIds[i]; + + // Skip if source and destination are the same + if (fromId === toId) { + log.info(`Skipping ${fromId} -> ${toId} (same ID)`); + continue; + } + + const shouldGenerateFiles = i === fromIds.length - 1; + const result = await moveTaskDirect( + { + sourceId: fromId, + destinationId: toId, + tasksJsonPath, + projectRoot: args.projectRoot + }, + log, + { session } + ); + + if (!result.success) { + log.error( + `Failed to move ${fromId} to ${toId}: ${result.error.message}` + ); + } else { + results.push(result.data); + } + } + + return { + success: true, + data: { + moves: results, + message: `Successfully moved ${results.length} tasks` + } + }; + } else { + // Moving a single task + return handleApiResult( + await moveTaskDirect( + { + sourceId: args.from, + destinationId: args.to, + tasksJsonPath, + projectRoot: args.projectRoot + }, + log, + { session } + ), + log + ); + } + } catch (error) { + return createErrorResponse( + `Failed to move task: ${error.message}`, + 'MOVE_TASK_ERROR' + ); + } + }) + }); +} diff --git a/mcp-server/src/tools/parse-prd.js b/mcp-server/src/tools/parse-prd.js index b73136b2..691b69e1 100644 --- a/mcp-server/src/tools/parse-prd.js +++ b/mcp-server/src/tools/parse-prd.js @@ -49,6 +49,13 @@ export function registerParsePRDTool(server) { .optional() .default(false) .describe('Append generated tasks to existing file.'), + research: z + .boolean() + .optional() + .default(false) + .describe( + 'Use the research model for research-backed task generation, providing more comprehensive, accurate and up-to-date task details.' + ), projectRoot: z .string() .describe('The directory of the project. Must be an absolute path.') @@ -68,6 +75,7 @@ export function registerParsePRDTool(server) { numTasks: args.numTasks, force: args.force, append: args.append, + research: args.research, projectRoot: args.projectRoot }, log, diff --git a/package-lock.json b/package-lock.json index 2a437a53..f6d1ac92 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "task-master-ai", - "version": "0.13.2", + "version": "0.14.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "task-master-ai", - "version": "0.13.2", + "version": "0.14.0", "license": "MIT WITH Commons-Clause", "dependencies": { "@ai-sdk/anthropic": "^1.2.10", @@ -28,7 +28,7 @@ "express": "^4.21.2", "fastmcp": "^1.20.5", "figlet": "^1.8.0", - "fuse.js": "^7.0.0", + "fuse.js": "^7.1.0", "gradient-string": "^3.0.0", "helmet": "^8.1.0", "inquirer": "^12.5.0", diff --git a/package.json b/package.json index 5ff7adb3..52bbd6f4 100644 --- a/package.json +++ b/package.json @@ -58,7 +58,7 @@ "express": "^4.21.2", "fastmcp": "^1.20.5", "figlet": "^1.8.0", - "fuse.js": "^7.0.0", + "fuse.js": "^7.1.0", "gradient-string": "^3.0.0", "helmet": "^8.1.0", "inquirer": "^12.5.0", diff --git a/scripts/modules/ai-services-unified.js b/scripts/modules/ai-services-unified.js index 1abf2f98..7ff7bc59 100644 --- a/scripts/modules/ai-services-unified.js +++ b/scripts/modules/ai-services-unified.js @@ -18,9 +18,10 @@ import { getUserId, MODEL_MAP, getDebugFlag, - getBaseUrlForRole + getBaseUrlForRole, + isApiKeySet } from './config-manager.js'; -import { log, resolveEnvVariable, isSilentMode } from './utils.js'; +import { log, resolveEnvVariable, findProjectRoot } from './utils.js'; import * as anthropic from '../../src/ai-providers/anthropic.js'; import * as perplexity from '../../src/ai-providers/perplexity.js'; @@ -322,11 +323,7 @@ async function _unifiedServiceRunner(serviceType, params) { }); } - // Determine the effective project root (passed in or detected if needed by config getters) - const { findProjectRoot: detectProjectRoot } = await import('./utils.js'); // Dynamically import if needed - const effectiveProjectRoot = projectRoot || detectProjectRoot(); - - // Get userId from config - ensure effectiveProjectRoot is passed + const effectiveProjectRoot = projectRoot || findProjectRoot(); const userId = getUserId(effectiveProjectRoot); let sequence; @@ -362,8 +359,6 @@ async function _unifiedServiceRunner(serviceType, params) { try { log('info', `New AI service call with role: ${currentRole}`); - // 1. Get Config: Provider, Model, Parameters for the current role - // Pass effectiveProjectRoot to config getters if (currentRole === 'main') { providerName = getMainProvider(effectiveProjectRoot); modelId = getMainModelId(effectiveProjectRoot); @@ -396,11 +391,24 @@ async function _unifiedServiceRunner(serviceType, params) { continue; } - // Pass effectiveProjectRoot to getParametersForRole + // Check if API key is set for the current provider and role (excluding 'ollama') + if (providerName?.toLowerCase() !== 'ollama') { + if (!isApiKeySet(providerName, session, effectiveProjectRoot)) { + log( + 'warn', + `Skipping role '${currentRole}' (Provider: ${providerName}): API key not set or invalid.` + ); + lastError = + lastError || + new Error( + `API key for provider '${providerName}' (role: ${currentRole}) is not set.` + ); + continue; // Skip to the next role in the sequence + } + } + roleParams = getParametersForRole(currentRole, effectiveProjectRoot); baseUrl = getBaseUrlForRole(currentRole, effectiveProjectRoot); - - // 2. Get Provider Function Set providerFnSet = PROVIDER_FUNCTIONS[providerName?.toLowerCase()]; if (!providerFnSet) { log( @@ -413,7 +421,6 @@ async function _unifiedServiceRunner(serviceType, params) { continue; } - // Use the original service type to get the function providerApiFn = providerFnSet[serviceType]; if (typeof providerApiFn !== 'function') { log( @@ -428,15 +435,12 @@ async function _unifiedServiceRunner(serviceType, params) { continue; } - // 3. Resolve API Key (will throw if required and missing) - // Pass effectiveProjectRoot to _resolveApiKey apiKey = _resolveApiKey( providerName?.toLowerCase(), session, effectiveProjectRoot ); - // 4. Construct Messages Array const messages = []; if (systemPrompt) { messages.push({ role: 'system', content: systemPrompt }); @@ -461,14 +465,11 @@ async function _unifiedServiceRunner(serviceType, params) { // } if (prompt) { - // Ensure prompt exists before adding messages.push({ role: 'user', content: prompt }); } else { - // Throw an error if the prompt is missing, as it's essential throw new Error('User prompt content is missing.'); } - // 5. Prepare call parameters (using messages array) const callParams = { apiKey, modelId, @@ -480,7 +481,6 @@ async function _unifiedServiceRunner(serviceType, params) { ...restApiParams }; - // 6. Attempt the call with retries providerResponse = await _attemptProviderCallWithRetries( providerApiFn, callParams, @@ -489,8 +489,6 @@ async function _unifiedServiceRunner(serviceType, params) { currentRole ); - // --- Log Telemetry & Capture Data --- - // Use providerResponse which contains the usage data directly for text/object if (userId && providerResponse && providerResponse.usage) { try { telemetryData = await logAiUsage({ @@ -512,26 +510,22 @@ async function _unifiedServiceRunner(serviceType, params) { `Cannot log telemetry for ${commandName} (${providerName}/${modelId}): AI result missing 'usage' data. (May be expected for streams)` ); } - // --- End Log Telemetry --- - // --- Extract the correct main result based on serviceType --- let finalMainResult; if (serviceType === 'generateText') { finalMainResult = providerResponse.text; } else if (serviceType === 'generateObject') { finalMainResult = providerResponse.object; } else if (serviceType === 'streamText') { - finalMainResult = providerResponse; // Return the whole stream object + finalMainResult = providerResponse; } else { log( 'error', `Unknown serviceType in _unifiedServiceRunner: ${serviceType}` ); - finalMainResult = providerResponse; // Default to returning the whole object as fallback + finalMainResult = providerResponse; } - // --- End Main Result Extraction --- - // Return a composite object including the extracted main result and telemetry data return { mainResult: finalMainResult, telemetryData: telemetryData @@ -564,9 +558,7 @@ async function _unifiedServiceRunner(serviceType, params) { } } - // If loop completes, all roles failed log('error', `All roles in the sequence [${sequence.join(', ')}] failed.`); - // Throw a new error with the cleaner message from the last failure throw new Error(lastCleanErrorMessage); } diff --git a/scripts/modules/commands.js b/scripts/modules/commands.js index f9dbe82a..99b5d98b 100644 --- a/scripts/modules/commands.js +++ b/scripts/modules/commands.js @@ -9,6 +9,7 @@ import chalk from 'chalk'; import boxen from 'boxen'; import fs from 'fs'; import https from 'https'; +import http from 'http'; import inquirer from 'inquirer'; import ora from 'ora'; // Import ora @@ -30,7 +31,8 @@ import { updateSubtaskById, removeTask, findTaskById, - taskExists + taskExists, + moveTask } from './task-manager.js'; import { @@ -47,7 +49,8 @@ import { writeConfig, ConfigurationError, isConfigFilePresent, - getAvailableModels + getAvailableModels, + getBaseUrlForRole } from './config-manager.js'; import { @@ -152,6 +155,64 @@ async function runInteractiveSetup(projectRoot) { }); } + // Helper function to fetch Ollama models (duplicated for CLI context) + function fetchOllamaModelsCLI(baseUrl = 'http://localhost:11434/api') { + return new Promise((resolve) => { + try { + // Parse the base URL to extract hostname, port, and base path + const url = new URL(baseUrl); + const isHttps = url.protocol === 'https:'; + const port = url.port || (isHttps ? 443 : 80); + const basePath = url.pathname.endsWith('/') + ? url.pathname.slice(0, -1) + : url.pathname; + + const options = { + hostname: url.hostname, + port: parseInt(port, 10), + path: `${basePath}/tags`, + method: 'GET', + headers: { + Accept: 'application/json' + } + }; + + const requestLib = isHttps ? https : http; + const req = requestLib.request(options, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + if (res.statusCode === 200) { + try { + const parsedData = JSON.parse(data); + resolve(parsedData.models || []); // Return the array of models + } catch (e) { + console.error('Error parsing Ollama response:', e); + resolve(null); // Indicate failure + } + } else { + console.error( + `Ollama API request failed with status code: ${res.statusCode}` + ); + resolve(null); // Indicate failure + } + }); + }); + + req.on('error', (e) => { + console.error('Error fetching Ollama models:', e); + resolve(null); // Indicate failure + }); + req.end(); + } catch (e) { + console.error('Error parsing Ollama base URL:', e); + resolve(null); // Indicate failure + } + }); + } + // Helper to get choices and default index for a role const getPromptData = (role, allowNone = false) => { const currentModel = currentModels[role]; // Use the fetched data @@ -179,6 +240,11 @@ async function runInteractiveSetup(projectRoot) { value: '__CUSTOM_OPENROUTER__' }; + const customOllamaOption = { + name: '* Custom Ollama model', // Symbol updated + value: '__CUSTOM_OLLAMA__' + }; + let choices = []; let defaultIndex = 0; // Default to 'Cancel' @@ -224,6 +290,7 @@ async function runInteractiveSetup(projectRoot) { } commonPrefix.push(cancelOption); commonPrefix.push(customOpenRouterOption); + commonPrefix.push(customOllamaOption); let prefixLength = commonPrefix.length; // Initial prefix length @@ -354,6 +421,47 @@ async function runInteractiveSetup(projectRoot) { setupSuccess = false; return true; // Continue setup, but mark as failed } + } else if (selectedValue === '__CUSTOM_OLLAMA__') { + isCustomSelection = true; + const { customId } = await inquirer.prompt([ + { + type: 'input', + name: 'customId', + message: `Enter the custom Ollama Model ID for the ${role} role:` + } + ]); + if (!customId) { + console.log(chalk.yellow('No custom ID entered. Skipping role.')); + return true; // Continue setup, but don't set this role + } + modelIdToSet = customId; + providerHint = 'ollama'; + // Get the Ollama base URL from config for this role + const ollamaBaseUrl = getBaseUrlForRole(role, projectRoot); + // Validate against live Ollama list + const ollamaModels = await fetchOllamaModelsCLI(ollamaBaseUrl); + if (ollamaModels === null) { + console.error( + chalk.red( + `Error: Unable to connect to Ollama server at ${ollamaBaseUrl}. Please ensure Ollama is running and try again.` + ) + ); + setupSuccess = false; + return true; // Continue setup, but mark as failed + } else if (!ollamaModels.some((m) => m.model === modelIdToSet)) { + console.error( + chalk.red( + `Error: Model ID "${modelIdToSet}" not found in the Ollama instance. Please verify the model is pulled and available.` + ) + ); + console.log( + chalk.yellow( + `You can check available models with: curl ${ollamaBaseUrl}/tags` + ) + ); + setupSuccess = false; + return true; // Continue setup, but mark as failed + } } else if ( selectedValue && typeof selectedValue === 'object' && @@ -507,6 +615,10 @@ function registerCommands(programInstance) { '--append', 'Append new tasks to existing tasks.json instead of overwriting' ) + .option( + '-r, --research', + 'Use Perplexity AI for research-backed task generation, providing more comprehensive and accurate task breakdown' + ) .action(async (file, options) => { // Use input option if file argument not provided const inputFile = file || options.input; @@ -515,6 +627,7 @@ function registerCommands(programInstance) { const outputPath = options.output; const force = options.force || false; const append = options.append || false; + const research = options.research || false; let useForce = force; let useAppend = append; @@ -547,7 +660,8 @@ function registerCommands(programInstance) { spinner = ora('Parsing PRD and generating tasks...\n').start(); await parsePRD(defaultPrdPath, outputPath, numTasks, { append: useAppend, // Changed key from useAppend to append - force: useForce // Changed key from useForce to force + force: useForce, // Changed key from useForce to force + research: research }); spinner.succeed('Tasks generated successfully!'); return; @@ -571,13 +685,15 @@ function registerCommands(programInstance) { ' -o, --output Output file path (default: "tasks/tasks.json")\n' + ' -n, --num-tasks Number of tasks to generate (default: 10)\n' + ' -f, --force Skip confirmation when overwriting existing tasks\n' + - ' --append Append new tasks to existing tasks.json instead of overwriting\n\n' + + ' --append Append new tasks to existing tasks.json instead of overwriting\n' + + ' -r, --research Use Perplexity AI for research-backed task generation\n\n' + chalk.cyan('Example:') + '\n' + ' task-master parse-prd requirements.txt --num-tasks 15\n' + ' task-master parse-prd --input=requirements.txt\n' + ' task-master parse-prd --force\n' + - ' task-master parse-prd requirements_v2.txt --append\n\n' + + ' task-master parse-prd requirements_v2.txt --append\n' + + ' task-master parse-prd requirements.txt --research\n\n' + chalk.yellow('Note: This command will:') + '\n' + ' 1. Look for a PRD file at scripts/prd.txt by default\n' + @@ -605,11 +721,19 @@ function registerCommands(programInstance) { if (append) { console.log(chalk.blue('Appending to existing tasks...')); } + if (research) { + console.log( + chalk.blue( + 'Using Perplexity AI for research-backed task generation' + ) + ); + } spinner = ora('Parsing PRD and generating tasks...\n').start(); await parsePRD(inputFile, outputPath, numTasks, { - useAppend: useAppend, - useForce: useForce + append: useAppend, + force: useForce, + research: research }); spinner.succeed('Tasks generated successfully!'); } catch (error) { @@ -1031,6 +1155,8 @@ function registerCommands(programInstance) { // set-status command programInstance .command('set-status') + .alias('mark') + .alias('set') .description('Set the status of a task') .option( '-i, --id ', @@ -1211,6 +1337,12 @@ function registerCommands(programInstance) { '-r, --research', 'Use Perplexity AI for research-backed complexity analysis' ) + .option( + '-i, --id ', + 'Comma-separated list of specific task IDs to analyze (e.g., "1,3,5")' + ) + .option('--from ', 'Starting task ID in a range to analyze') + .option('--to ', 'Ending task ID in a range to analyze') .action(async (options) => { const tasksPath = options.file || 'tasks/tasks.json'; const outputPath = options.output; @@ -1221,6 +1353,16 @@ function registerCommands(programInstance) { console.log(chalk.blue(`Analyzing task complexity from: ${tasksPath}`)); console.log(chalk.blue(`Output report will be saved to: ${outputPath}`)); + if (options.id) { + console.log(chalk.blue(`Analyzing specific task IDs: ${options.id}`)); + } else if (options.from || options.to) { + const fromStr = options.from ? options.from : 'first'; + const toStr = options.to ? options.to : 'last'; + console.log( + chalk.blue(`Analyzing tasks in range: ${fromStr} to ${toStr}`) + ); + } + if (useResearch) { console.log( chalk.blue( @@ -2349,6 +2491,135 @@ Examples: return; // Stop execution here }); + // move-task command + programInstance + .command('move') + .description('Move a task or subtask to a new position') + .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') + .option( + '--from ', + 'ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated to move multiple tasks (e.g., "5,6,7")' + ) + .option( + '--to ', + 'ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated' + ) + .action(async (options) => { + const tasksPath = options.file; + const sourceId = options.from; + const destinationId = options.to; + + if (!sourceId || !destinationId) { + console.error( + chalk.red('Error: Both --from and --to parameters are required') + ); + console.log( + chalk.yellow( + 'Usage: task-master move --from= --to=' + ) + ); + process.exit(1); + } + + // Check if we're moving multiple tasks (comma-separated IDs) + const sourceIds = sourceId.split(',').map((id) => id.trim()); + const destinationIds = destinationId.split(',').map((id) => id.trim()); + + // Validate that the number of source and destination IDs match + if (sourceIds.length !== destinationIds.length) { + console.error( + chalk.red( + 'Error: The number of source and destination IDs must match' + ) + ); + console.log( + chalk.yellow('Example: task-master move --from=5,6,7 --to=10,11,12') + ); + process.exit(1); + } + + // If moving multiple tasks + if (sourceIds.length > 1) { + console.log( + chalk.blue( + `Moving multiple tasks: ${sourceIds.join(', ')} to ${destinationIds.join(', ')}...` + ) + ); + + try { + // Read tasks data once to validate destination IDs + const tasksData = readJSON(tasksPath); + if (!tasksData || !tasksData.tasks) { + console.error( + chalk.red(`Error: Invalid or missing tasks file at ${tasksPath}`) + ); + process.exit(1); + } + + // Move tasks one by one + for (let i = 0; i < sourceIds.length; i++) { + const fromId = sourceIds[i]; + const toId = destinationIds[i]; + + // Skip if source and destination are the same + if (fromId === toId) { + console.log( + chalk.yellow(`Skipping ${fromId} -> ${toId} (same ID)`) + ); + continue; + } + + console.log( + chalk.blue(`Moving task/subtask ${fromId} to ${toId}...`) + ); + try { + await moveTask( + tasksPath, + fromId, + toId, + i === sourceIds.length - 1 + ); + console.log( + chalk.green( + `✓ Successfully moved task/subtask ${fromId} to ${toId}` + ) + ); + } catch (error) { + console.error( + chalk.red(`Error moving ${fromId} to ${toId}: ${error.message}`) + ); + // Continue with the next task rather than exiting + } + } + } catch (error) { + console.error(chalk.red(`Error: ${error.message}`)); + process.exit(1); + } + } else { + // Moving a single task (existing logic) + console.log( + chalk.blue(`Moving task/subtask ${sourceId} to ${destinationId}...`) + ); + + try { + const result = await moveTask( + tasksPath, + sourceId, + destinationId, + true + ); + console.log( + chalk.green( + `✓ Successfully moved task/subtask ${sourceId} to ${destinationId}` + ) + ); + } catch (error) { + console.error(chalk.red(`Error: ${error.message}`)); + process.exit(1); + } + } + }); + return programInstance; } diff --git a/scripts/modules/supported-models.json b/scripts/modules/supported-models.json index b1f1e1c7..7874f0e4 100644 --- a/scripts/modules/supported-models.json +++ b/scripts/modules/supported-models.json @@ -1,5 +1,19 @@ { "anthropic": [ + { + "id": "claude-sonnet-4-20250514", + "swe_score": 0.727, + "cost_per_1m_tokens": { "input": 3.0, "output": 15.0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 120000 + }, + { + "id": "claude-opus-4-20250514", + "swe_score": 0.725, + "cost_per_1m_tokens": { "input": 15.0, "output": 75.0 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 120000 + }, { "id": "claude-3-7-sonnet-20250219", "swe_score": 0.623, @@ -191,43 +205,43 @@ ], "ollama": [ { - "id": "gemma3:27b", + "id": "devstral:latest", "swe_score": 0, "cost_per_1m_tokens": { "input": 0, "output": 0 }, "allowed_roles": ["main", "fallback"] }, { - "id": "gemma3:12b", + "id": "qwen3:latest", "swe_score": 0, "cost_per_1m_tokens": { "input": 0, "output": 0 }, "allowed_roles": ["main", "fallback"] }, { - "id": "qwq", + "id": "qwen3:14b", "swe_score": 0, "cost_per_1m_tokens": { "input": 0, "output": 0 }, "allowed_roles": ["main", "fallback"] }, { - "id": "deepseek-r1", + "id": "qwen3:32b", "swe_score": 0, "cost_per_1m_tokens": { "input": 0, "output": 0 }, "allowed_roles": ["main", "fallback"] }, { - "id": "mistral-small3.1", + "id": "mistral-small3.1:latest", "swe_score": 0, "cost_per_1m_tokens": { "input": 0, "output": 0 }, "allowed_roles": ["main", "fallback"] }, { - "id": "llama3.3", + "id": "llama3.3:latest", "swe_score": 0, "cost_per_1m_tokens": { "input": 0, "output": 0 }, "allowed_roles": ["main", "fallback"] }, { - "id": "phi4", + "id": "phi4:latest", "swe_score": 0, "cost_per_1m_tokens": { "input": 0, "output": 0 }, "allowed_roles": ["main", "fallback"] @@ -235,9 +249,16 @@ ], "openrouter": [ { - "id": "google/gemini-2.0-flash-001", + "id": "google/gemini-2.5-flash-preview-05-20", "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.1, "output": 0.4 }, + "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 1048576 + }, + { + "id": "google/gemini-2.5-flash-preview-05-20:thinking", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.15, "output": 3.5 }, "allowed_roles": ["main", "fallback"], "max_tokens": 1048576 }, @@ -263,40 +284,25 @@ "max_tokens": 64000 }, { - "id": "deepseek/deepseek-r1:free", + "id": "openai/gpt-4.1", "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "cost_per_1m_tokens": { "input": 2, "output": 8 }, "allowed_roles": ["main", "fallback"], - "max_tokens": 163840 + "max_tokens": 1000000 }, - { - "id": "microsoft/mai-ds-r1:free", + "id": "openai/gpt-4.1-mini", "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "cost_per_1m_tokens": { "input": 0.4, "output": 1.6 }, "allowed_roles": ["main", "fallback"], - "max_tokens": 163840 + "max_tokens": 1000000 }, { - "id": "google/gemini-2.5-pro-preview-03-25", + "id": "openai/gpt-4.1-nano", "swe_score": 0, - "cost_per_1m_tokens": { "input": 1.25, "output": 10 }, + "cost_per_1m_tokens": { "input": 0.1, "output": 0.4 }, "allowed_roles": ["main", "fallback"], - "max_tokens": 65535 - }, - { - "id": "google/gemini-2.5-flash-preview", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, - "allowed_roles": ["main"], - "max_tokens": 65535 - }, - { - "id": "google/gemini-2.5-flash-preview:thinking", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0.15, "output": 3.5 }, - "allowed_roles": ["main"], - "max_tokens": 65535 + "max_tokens": 1000000 }, { "id": "openai/o3", @@ -305,6 +311,20 @@ "allowed_roles": ["main", "fallback"], "max_tokens": 200000 }, + { + "id": "openai/codex-mini", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 1.5, "output": 6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, + { + "id": "openai/gpt-4o-mini", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.15, "output": 0.6 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, { "id": "openai/o4-mini", "swe_score": 0.45, @@ -334,46 +354,18 @@ "max_tokens": 1048576 }, { - "id": "google/gemma-3-12b-it:free", + "id": "meta-llama/llama-4-maverick", "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, + "cost_per_1m_tokens": { "input": 0.18, "output": 0.6 }, "allowed_roles": ["main", "fallback"], - "max_tokens": 131072 + "max_tokens": 1000000 }, { - "id": "google/gemma-3-12b-it", + "id": "meta-llama/llama-4-scout", "swe_score": 0, - "cost_per_1m_tokens": { "input": 50, "output": 100 }, + "cost_per_1m_tokens": { "input": 0.08, "output": 0.3 }, "allowed_roles": ["main", "fallback"], - "max_tokens": 131072 - }, - { - "id": "google/gemma-3-27b-it:free", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 96000 - }, - { - "id": "google/gemma-3-27b-it", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 100, "output": 200 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 131072 - }, - { - "id": "qwen/qwq-32b:free", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 0, "output": 0 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 40000 - }, - { - "id": "qwen/qwq-32b", - "swe_score": 0, - "cost_per_1m_tokens": { "input": 150, "output": 200 }, - "allowed_roles": ["main", "fallback"], - "max_tokens": 131072 + "max_tokens": 1000000 }, { "id": "qwen/qwen-max", @@ -389,6 +381,13 @@ "allowed_roles": ["main", "fallback"], "max_tokens": 1000000 }, + { + "id": "qwen/qwen3-235b-a22b", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.14, "output": 2 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 24000 + }, { "id": "mistralai/mistral-small-3.1-24b-instruct:free", "swe_score": 0, @@ -403,6 +402,20 @@ "allowed_roles": ["main", "fallback"], "max_tokens": 128000 }, + { + "id": "mistralai/devstral-small", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.1, "output": 0.3 }, + "allowed_roles": ["main"], + "max_tokens": 110000 + }, + { + "id": "mistralai/mistral-nemo", + "swe_score": 0, + "cost_per_1m_tokens": { "input": 0.03, "output": 0.07 }, + "allowed_roles": ["main", "fallback"], + "max_tokens": 100000 + }, { "id": "thudm/glm-4-32b:free", "swe_score": 0, diff --git a/scripts/modules/task-manager.js b/scripts/modules/task-manager.js index 9da48972..cf4bba09 100644 --- a/scripts/modules/task-manager.js +++ b/scripts/modules/task-manager.js @@ -23,6 +23,7 @@ import updateSubtaskById from './task-manager/update-subtask-by-id.js'; import removeTask from './task-manager/remove-task.js'; import taskExists from './task-manager/task-exists.js'; import isTaskDependentOn from './task-manager/is-task-dependent.js'; +import moveTask from './task-manager/move-task.js'; import { readComplexityReport } from './utils.js'; // Export task manager functions export { @@ -46,5 +47,6 @@ export { findTaskById, taskExists, isTaskDependentOn, + moveTask, readComplexityReport }; diff --git a/scripts/modules/task-manager/add-task.js b/scripts/modules/task-manager/add-task.js index 65b9d3b6..efd5595a 100644 --- a/scripts/modules/task-manager/add-task.js +++ b/scripts/modules/task-manager/add-task.js @@ -3,6 +3,7 @@ import chalk from 'chalk'; import boxen from 'boxen'; import Table from 'cli-table3'; import { z } from 'zod'; +import Fuse from 'fuse.js'; // Import Fuse.js for advanced fuzzy search import { displayBanner, @@ -27,7 +28,13 @@ const AiTaskDataSchema = z.object({ .describe('In-depth implementation details, considerations, and guidance'), testStrategy: z .string() - .describe('Detailed approach for verifying task completion') + .describe('Detailed approach for verifying task completion'), + dependencies: z + .array(z.number()) + .optional() + .describe( + 'Array of task IDs that this task depends on (must be completed before this task can start)' + ) }); /** @@ -92,12 +99,81 @@ async function addTask( } }; + /** + * Recursively builds a dependency graph for a given task + * @param {Array} tasks - All tasks from tasks.json + * @param {number} taskId - ID of the task to analyze + * @param {Set} visited - Set of already visited task IDs + * @param {Map} depthMap - Map of task ID to its depth in the graph + * @param {number} depth - Current depth in the recursion + * @return {Object} Dependency graph data + */ + function buildDependencyGraph( + tasks, + taskId, + visited = new Set(), + depthMap = new Map(), + depth = 0 + ) { + // Skip if we've already visited this task or it doesn't exist + if (visited.has(taskId)) { + return null; + } + + // Find the task + const task = tasks.find((t) => t.id === taskId); + if (!task) { + return null; + } + + // Mark as visited + visited.add(taskId); + + // Update depth if this is a deeper path to this task + if (!depthMap.has(taskId) || depth < depthMap.get(taskId)) { + depthMap.set(taskId, depth); + } + + // Process dependencies + const dependencyData = []; + if (task.dependencies && task.dependencies.length > 0) { + for (const depId of task.dependencies) { + const depData = buildDependencyGraph( + tasks, + depId, + visited, + depthMap, + depth + 1 + ); + if (depData) { + dependencyData.push(depData); + } + } + } + + return { + id: task.id, + title: task.title, + description: task.description, + status: task.status, + dependencies: dependencyData + }; + } + try { // Read the existing tasks - const data = readJSON(tasksPath); + let data = readJSON(tasksPath); + + // If tasks.json doesn't exist or is invalid, create a new one if (!data || !data.tasks) { - report('Invalid or missing tasks.json.', 'error'); - throw new Error('Invalid or missing tasks.json.'); + report('tasks.json not found or invalid. Creating a new one.', 'info'); + // Create default tasks data structure + data = { + tasks: [] + }; + // Ensure the directory exists and write the new file + writeJSON(tasksPath, data); + report('Created new tasks.json file with empty tasks array.', 'info'); } // Find the highest task ID to determine the next ID @@ -137,6 +213,29 @@ async function addTask( // Ensure dependencies are numbers const numericDependencies = dependencies.map((dep) => parseInt(dep, 10)); + // Build dependency graphs for explicitly specified dependencies + const dependencyGraphs = []; + const allRelatedTaskIds = new Set(); + const depthMap = new Map(); + + // First pass: build a complete dependency graph for each specified dependency + for (const depId of numericDependencies) { + const graph = buildDependencyGraph( + data.tasks, + depId, + new Set(), + depthMap + ); + if (graph) { + dependencyGraphs.push(graph); + } + } + + // Second pass: build a set of all related task IDs for flat analysis + for (const [taskId, depth] of depthMap.entries()) { + allRelatedTaskIds.add(taskId); + } + let taskData; // Check if manual task data is provided @@ -163,36 +262,644 @@ async function addTask( // Create context string for task creation prompt let contextTasks = ''; + + // Create a dependency map for better understanding of the task relationships + const taskMap = {}; + data.tasks.forEach((t) => { + // For each task, only include id, title, description, and dependencies + taskMap[t.id] = { + id: t.id, + title: t.title, + description: t.description, + dependencies: t.dependencies || [], + status: t.status + }; + }); + + // CLI-only feedback for the dependency analysis + if (outputFormat === 'text') { + console.log( + boxen(chalk.cyan.bold('Task Context Analysis') + '\n', { + padding: { top: 0, bottom: 0, left: 1, right: 1 }, + margin: { top: 0, bottom: 0 }, + borderColor: 'cyan', + borderStyle: 'round' + }) + ); + } + + // Initialize variables that will be used in either branch + let uniqueDetailedTasks = []; + let dependentTasks = []; + let promptCategory = null; + if (numericDependencies.length > 0) { - const dependentTasks = data.tasks.filter((t) => + // If specific dependencies were provided, focus on them + // Get all tasks that were found in the dependency graph + dependentTasks = Array.from(allRelatedTaskIds) + .map((id) => data.tasks.find((t) => t.id === id)) + .filter(Boolean); + + // Sort by depth in the dependency chain + dependentTasks.sort((a, b) => { + const depthA = depthMap.get(a.id) || 0; + const depthB = depthMap.get(b.id) || 0; + return depthA - depthB; // Lowest depth (root dependencies) first + }); + + // Limit the number of detailed tasks to avoid context explosion + uniqueDetailedTasks = dependentTasks.slice(0, 8); + + contextTasks = `\nThis task relates to a dependency structure with ${dependentTasks.length} related tasks in the chain.\n\nDirect dependencies:`; + const directDeps = data.tasks.filter((t) => numericDependencies.includes(t.id) ); - contextTasks = `\nThis task depends on the following tasks:\n${dependentTasks - .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) - .join('\n')}`; + contextTasks += `\n${directDeps.map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`).join('\n')}`; + + // Add an overview of indirect dependencies if present + const indirectDeps = dependentTasks.filter( + (t) => !numericDependencies.includes(t.id) + ); + if (indirectDeps.length > 0) { + contextTasks += `\n\nIndirect dependencies (dependencies of dependencies):`; + contextTasks += `\n${indirectDeps + .slice(0, 5) + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + if (indirectDeps.length > 5) { + contextTasks += `\n- ... and ${indirectDeps.length - 5} more indirect dependencies`; + } + } + + // Add more details about each dependency, prioritizing direct dependencies + contextTasks += `\n\nDetailed information about dependencies:`; + for (const depTask of uniqueDetailedTasks) { + const depthInfo = depthMap.get(depTask.id) + ? ` (depth: ${depthMap.get(depTask.id)})` + : ''; + const isDirect = numericDependencies.includes(depTask.id) + ? ' [DIRECT DEPENDENCY]' + : ''; + + contextTasks += `\n\n------ Task ${depTask.id}${isDirect}${depthInfo}: ${depTask.title} ------\n`; + contextTasks += `Description: ${depTask.description}\n`; + contextTasks += `Status: ${depTask.status || 'pending'}\n`; + contextTasks += `Priority: ${depTask.priority || 'medium'}\n`; + + // List its dependencies + if (depTask.dependencies && depTask.dependencies.length > 0) { + const depDeps = depTask.dependencies.map((dId) => { + const depDepTask = data.tasks.find((t) => t.id === dId); + return depDepTask + ? `Task ${dId}: ${depDepTask.title}` + : `Task ${dId}`; + }); + contextTasks += `Dependencies: ${depDeps.join(', ')}\n`; + } else { + contextTasks += `Dependencies: None\n`; + } + + // Add implementation details but truncate if too long + if (depTask.details) { + const truncatedDetails = + depTask.details.length > 400 + ? depTask.details.substring(0, 400) + '... (truncated)' + : depTask.details; + contextTasks += `Implementation Details: ${truncatedDetails}\n`; + } + } + + // Add dependency chain visualization + if (dependencyGraphs.length > 0) { + contextTasks += '\n\nDependency Chain Visualization:'; + + // Helper function to format dependency chain as text + function formatDependencyChain( + node, + prefix = '', + isLast = true, + depth = 0 + ) { + if (depth > 3) return ''; // Limit depth to avoid excessive nesting + + const connector = isLast ? '└── ' : '├── '; + const childPrefix = isLast ? ' ' : '│ '; + + let result = `\n${prefix}${connector}Task ${node.id}: ${node.title}`; + + if (node.dependencies && node.dependencies.length > 0) { + for (let i = 0; i < node.dependencies.length; i++) { + const isLastChild = i === node.dependencies.length - 1; + result += formatDependencyChain( + node.dependencies[i], + prefix + childPrefix, + isLastChild, + depth + 1 + ); + } + } + + return result; + } + + // Format each dependency graph + for (const graph of dependencyGraphs) { + contextTasks += formatDependencyChain(graph); + } + } + + // Show dependency analysis in CLI mode + if (outputFormat === 'text') { + if (directDeps.length > 0) { + console.log(chalk.gray(` Explicitly specified dependencies:`)); + directDeps.forEach((t) => { + console.log( + chalk.yellow(` • Task ${t.id}: ${truncate(t.title, 50)}`) + ); + }); + } + + if (indirectDeps.length > 0) { + console.log( + chalk.gray( + `\n Indirect dependencies (${indirectDeps.length} total):` + ) + ); + indirectDeps.slice(0, 3).forEach((t) => { + const depth = depthMap.get(t.id) || 0; + console.log( + chalk.cyan( + ` • Task ${t.id} [depth ${depth}]: ${truncate(t.title, 45)}` + ) + ); + }); + if (indirectDeps.length > 3) { + console.log( + chalk.cyan( + ` • ... and ${indirectDeps.length - 3} more indirect dependencies` + ) + ); + } + } + + // Visualize the dependency chain + if (dependencyGraphs.length > 0) { + console.log(chalk.gray(`\n Dependency chain visualization:`)); + + // Convert dependency graph to ASCII art for terminal + function visualizeDependencyGraph( + node, + prefix = '', + isLast = true, + depth = 0 + ) { + if (depth > 2) return; // Limit depth for display + + const connector = isLast ? '└── ' : '├── '; + const childPrefix = isLast ? ' ' : '│ '; + + console.log( + chalk.blue( + ` ${prefix}${connector}Task ${node.id}: ${truncate(node.title, 40)}` + ) + ); + + if (node.dependencies && node.dependencies.length > 0) { + for (let i = 0; i < node.dependencies.length; i++) { + const isLastChild = i === node.dependencies.length - 1; + visualizeDependencyGraph( + node.dependencies[i], + prefix + childPrefix, + isLastChild, + depth + 1 + ); + } + } + } + + // Visualize each dependency graph + for (const graph of dependencyGraphs) { + visualizeDependencyGraph(graph); + } + } + + console.log(); // Add spacing + } } else { + // If no dependencies provided, use Fuse.js to find semantically related tasks + // Create fuzzy search index for all tasks + const searchOptions = { + includeScore: true, // Return match scores + threshold: 0.4, // Lower threshold = stricter matching (range 0-1) + keys: [ + { name: 'title', weight: 2 }, // Title is most important + { name: 'description', weight: 1.5 }, // Description is next + { name: 'details', weight: 0.8 }, // Details is less important + // Search dependencies to find tasks that depend on similar things + { name: 'dependencyTitles', weight: 0.5 } + ], + // Sort matches by score (lower is better) + shouldSort: true, + // Allow searching in nested properties + useExtendedSearch: true, + // Return up to 15 matches + limit: 15 + }; + + // Prepare task data with dependencies expanded as titles for better semantic search + const searchableTasks = data.tasks.map((task) => { + // Get titles of this task's dependencies if they exist + const dependencyTitles = + task.dependencies?.length > 0 + ? task.dependencies + .map((depId) => { + const depTask = data.tasks.find((t) => t.id === depId); + return depTask ? depTask.title : ''; + }) + .filter((title) => title) + .join(' ') + : ''; + + return { + ...task, + dependencyTitles + }; + }); + + // Create search index using Fuse.js + const fuse = new Fuse(searchableTasks, searchOptions); + + // Extract significant words and phrases from the prompt + const promptWords = prompt + .toLowerCase() + .replace(/[^\w\s-]/g, ' ') // Replace non-alphanumeric chars with spaces + .split(/\s+/) + .filter((word) => word.length > 3); // Words at least 4 chars + + // Use the user's prompt for fuzzy search + const fuzzyResults = fuse.search(prompt); + + // Also search for each significant word to catch different aspects + let wordResults = []; + for (const word of promptWords) { + if (word.length > 5) { + // Only use significant words + const results = fuse.search(word); + if (results.length > 0) { + wordResults.push(...results); + } + } + } + + // Merge and deduplicate results + const mergedResults = [...fuzzyResults]; + + // Add word results that aren't already in fuzzyResults + for (const wordResult of wordResults) { + if (!mergedResults.some((r) => r.item.id === wordResult.item.id)) { + mergedResults.push(wordResult); + } + } + + // Group search results by relevance + const highRelevance = mergedResults + .filter((result) => result.score < 0.25) + .map((result) => result.item); + + const mediumRelevance = mergedResults + .filter((result) => result.score >= 0.25 && result.score < 0.4) + .map((result) => result.item); + + // Get recent tasks (newest first) const recentTasks = [...data.tasks] .sort((a, b) => b.id - a.id) - .slice(0, 3); - if (recentTasks.length > 0) { - contextTasks = `\nRecent tasks in the project:\n${recentTasks + .slice(0, 5); + + // Combine high relevance, medium relevance, and recent tasks + // Prioritize high relevance first + const allRelevantTasks = [...highRelevance]; + + // Add medium relevance if not already included + for (const task of mediumRelevance) { + if (!allRelevantTasks.some((t) => t.id === task.id)) { + allRelevantTasks.push(task); + } + } + + // Add recent tasks if not already included + for (const task of recentTasks) { + if (!allRelevantTasks.some((t) => t.id === task.id)) { + allRelevantTasks.push(task); + } + } + + // Get top N results for context + const relatedTasks = allRelevantTasks.slice(0, 8); + + // Also look for tasks with similar purposes or categories + const purposeCategories = [ + { pattern: /(command|cli|flag)/i, label: 'CLI commands' }, + { pattern: /(task|subtask|add)/i, label: 'Task management' }, + { pattern: /(dependency|depend)/i, label: 'Dependency handling' }, + { pattern: /(AI|model|prompt)/i, label: 'AI integration' }, + { pattern: /(UI|display|show)/i, label: 'User interface' }, + { pattern: /(schedule|time|cron)/i, label: 'Scheduling' }, // Added scheduling category + { pattern: /(config|setting|option)/i, label: 'Configuration' } // Added configuration category + ]; + + promptCategory = purposeCategories.find((cat) => + cat.pattern.test(prompt) + ); + const categoryTasks = promptCategory + ? data.tasks + .filter( + (t) => + promptCategory.pattern.test(t.title) || + promptCategory.pattern.test(t.description) || + (t.details && promptCategory.pattern.test(t.details)) + ) + .filter((t) => !relatedTasks.some((rt) => rt.id === t.id)) + .slice(0, 3) + : []; + + // Format basic task overviews + if (relatedTasks.length > 0) { + contextTasks = `\nRelevant tasks identified by semantic similarity:\n${relatedTasks + .map((t, i) => { + const relevanceMarker = i < highRelevance.length ? '⭐ ' : ''; + return `- ${relevanceMarker}Task ${t.id}: ${t.title} - ${t.description}`; + }) + .join('\n')}`; + } + + if (categoryTasks.length > 0) { + contextTasks += `\n\nTasks related to ${promptCategory.label}:\n${categoryTasks .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) .join('\n')}`; } + + if ( + recentTasks.length > 0 && + !contextTasks.includes('Recently created tasks') + ) { + contextTasks += `\n\nRecently created tasks:\n${recentTasks + .filter((t) => !relatedTasks.some((rt) => rt.id === t.id)) + .slice(0, 3) + .map((t) => `- Task ${t.id}: ${t.title} - ${t.description}`) + .join('\n')}`; + } + + // Add detailed information about the most relevant tasks + const allDetailedTasks = [ + ...relatedTasks.slice(0, 5), + ...categoryTasks.slice(0, 2) + ]; + uniqueDetailedTasks = Array.from( + new Map(allDetailedTasks.map((t) => [t.id, t])).values() + ).slice(0, 8); + + if (uniqueDetailedTasks.length > 0) { + contextTasks += `\n\nDetailed information about relevant tasks:`; + for (const task of uniqueDetailedTasks) { + contextTasks += `\n\n------ Task ${task.id}: ${task.title} ------\n`; + contextTasks += `Description: ${task.description}\n`; + contextTasks += `Status: ${task.status || 'pending'}\n`; + contextTasks += `Priority: ${task.priority || 'medium'}\n`; + if (task.dependencies && task.dependencies.length > 0) { + // Format dependency list with titles + const depList = task.dependencies.map((depId) => { + const depTask = data.tasks.find((t) => t.id === depId); + return depTask + ? `Task ${depId} (${depTask.title})` + : `Task ${depId}`; + }); + contextTasks += `Dependencies: ${depList.join(', ')}\n`; + } + // Add implementation details but truncate if too long + if (task.details) { + const truncatedDetails = + task.details.length > 400 + ? task.details.substring(0, 400) + '... (truncated)' + : task.details; + contextTasks += `Implementation Details: ${truncatedDetails}\n`; + } + } + } + + // Add a concise view of the task dependency structure + contextTasks += '\n\nSummary of task dependencies in the project:'; + + // Get pending/in-progress tasks that might be most relevant based on fuzzy search + // Prioritize tasks from our similarity search + const relevantTaskIds = new Set(uniqueDetailedTasks.map((t) => t.id)); + const relevantPendingTasks = data.tasks + .filter( + (t) => + (t.status === 'pending' || t.status === 'in-progress') && + // Either in our relevant set OR has relevant words in title/description + (relevantTaskIds.has(t.id) || + promptWords.some( + (word) => + t.title.toLowerCase().includes(word) || + t.description.toLowerCase().includes(word) + )) + ) + .slice(0, 10); + + for (const task of relevantPendingTasks) { + const depsStr = + task.dependencies && task.dependencies.length > 0 + ? task.dependencies.join(', ') + : 'None'; + contextTasks += `\n- Task ${task.id}: depends on [${depsStr}]`; + } + + // Additional analysis of common patterns + const similarPurposeTasks = promptCategory + ? data.tasks.filter( + (t) => + promptCategory.pattern.test(t.title) || + promptCategory.pattern.test(t.description) + ) + : []; + + let commonDeps = []; // Initialize commonDeps + + if (similarPurposeTasks.length > 0) { + contextTasks += `\n\nCommon patterns for ${promptCategory ? promptCategory.label : 'similar'} tasks:`; + + // Collect dependencies from similar purpose tasks + const similarDeps = similarPurposeTasks + .filter((t) => t.dependencies && t.dependencies.length > 0) + .map((t) => t.dependencies) + .flat(); + + // Count frequency of each dependency + const depCounts = {}; + similarDeps.forEach((dep) => { + depCounts[dep] = (depCounts[dep] || 0) + 1; + }); + + // Get most common dependencies for similar tasks + commonDeps = Object.entries(depCounts) + .sort((a, b) => b[1] - a[1]) + .slice(0, 5); + + if (commonDeps.length > 0) { + contextTasks += '\nMost common dependencies for similar tasks:'; + commonDeps.forEach(([depId, count]) => { + const depTask = data.tasks.find((t) => t.id === parseInt(depId)); + if (depTask) { + contextTasks += `\n- Task ${depId} (used by ${count} similar tasks): ${depTask.title}`; + } + }); + } + } + + // Show fuzzy search analysis in CLI mode + if (outputFormat === 'text') { + console.log( + chalk.gray( + ` Fuzzy search across ${data.tasks.length} tasks using full prompt and ${promptWords.length} keywords` + ) + ); + + if (highRelevance.length > 0) { + console.log( + chalk.gray(`\n High relevance matches (score < 0.25):`) + ); + highRelevance.slice(0, 5).forEach((t) => { + console.log( + chalk.yellow(` • ⭐ Task ${t.id}: ${truncate(t.title, 50)}`) + ); + }); + } + + if (mediumRelevance.length > 0) { + console.log( + chalk.gray(`\n Medium relevance matches (score < 0.4):`) + ); + mediumRelevance.slice(0, 3).forEach((t) => { + console.log( + chalk.green(` • Task ${t.id}: ${truncate(t.title, 50)}`) + ); + }); + } + + if (promptCategory && categoryTasks.length > 0) { + console.log( + chalk.gray(`\n Tasks related to ${promptCategory.label}:`) + ); + categoryTasks.forEach((t) => { + console.log( + chalk.magenta(` • Task ${t.id}: ${truncate(t.title, 50)}`) + ); + }); + } + + // Show dependency patterns + if (commonDeps && commonDeps.length > 0) { + console.log( + chalk.gray(`\n Common dependency patterns for similar tasks:`) + ); + commonDeps.slice(0, 3).forEach(([depId, count]) => { + const depTask = data.tasks.find((t) => t.id === parseInt(depId)); + if (depTask) { + console.log( + chalk.blue( + ` • Task ${depId} (${count}x): ${truncate(depTask.title, 45)}` + ) + ); + } + }); + } + + // Add information about which tasks will be provided in detail + if (uniqueDetailedTasks.length > 0) { + console.log( + chalk.gray( + `\n Providing detailed context for ${uniqueDetailedTasks.length} most relevant tasks:` + ) + ); + uniqueDetailedTasks.forEach((t) => { + const isHighRelevance = highRelevance.some( + (ht) => ht.id === t.id + ); + const relevanceIndicator = isHighRelevance ? '⭐ ' : ''; + console.log( + chalk.cyan( + ` • ${relevanceIndicator}Task ${t.id}: ${truncate(t.title, 40)}` + ) + ); + }); + } + + console.log(); // Add spacing + } } - // System Prompt + // DETERMINE THE ACTUAL COUNT OF DETAILED TASKS BEING USED FOR AI CONTEXT + let actualDetailedTasksCount = 0; + if (numericDependencies.length > 0) { + // In explicit dependency mode, we used 'uniqueDetailedTasks' derived from 'dependentTasks' + // Ensure 'uniqueDetailedTasks' from THAT scope is used or re-evaluate. + // For simplicity, let's assume 'dependentTasks' reflects the detailed tasks. + actualDetailedTasksCount = dependentTasks.length; + } else { + // In fuzzy search mode, 'uniqueDetailedTasks' from THIS scope is correct. + actualDetailedTasksCount = uniqueDetailedTasks + ? uniqueDetailedTasks.length + : 0; + } + + // Add a visual transition to show we're moving to AI generation + console.log( + boxen( + chalk.white.bold('AI Task Generation') + + `\n\n${chalk.gray('Analyzing context and generating task details using AI...')}` + + `\n${chalk.cyan('Context size: ')}${chalk.yellow(contextTasks.length.toLocaleString())} characters` + + `\n${chalk.cyan('Dependency detection: ')}${chalk.yellow(numericDependencies.length > 0 ? 'Explicit dependencies' : 'Auto-discovery mode')}` + + `\n${chalk.cyan('Detailed tasks: ')}${chalk.yellow( + numericDependencies.length > 0 + ? dependentTasks.length // Use length of tasks from explicit dependency path + : uniqueDetailedTasks.length // Use length of tasks from fuzzy search path + )}` + + (promptCategory + ? `\n${chalk.cyan('Category detected: ')}${chalk.yellow(promptCategory.label)}` + : ''), + { + padding: { top: 0, bottom: 1, left: 1, right: 1 }, + margin: { top: 1, bottom: 0 }, + borderColor: 'white', + borderStyle: 'round' + } + ) + ); + console.log(); // Add spacing + + // System Prompt - Enhanced for dependency awareness 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, adhering strictly to the provided JSON schema."; + "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, adhering strictly to the provided JSON schema. Pay special attention to dependencies between tasks, ensuring the new task correctly references any tasks it depends on.\n\n" + + 'When determining dependencies for a new task, follow these principles:\n' + + '1. Select dependencies based on logical requirements - what must be completed before this task can begin.\n' + + '2. Prioritize task dependencies that are semantically related to the functionality being built.\n' + + '3. Consider both direct dependencies (immediately prerequisite) and indirect dependencies.\n' + + '4. Avoid adding unnecessary dependencies - only include tasks that are genuinely prerequisite.\n' + + '5. Consider the current status of tasks - prefer completed tasks as dependencies when possible.\n' + + "6. Pay special attention to foundation tasks (1-5) but don't automatically include them without reason.\n" + + '7. Recent tasks (higher ID numbers) may be more relevant for newer functionality.\n\n' + + 'The dependencies array should contain task IDs (numbers) of prerequisite tasks.\n'; // Task Structure Description (for user prompt) const taskStructureDesc = ` { "title": "Task title goes here", "description": "A concise one or two sentence description of what the task involves", - "details": "In-depth implementation details, considerations, and guidance.", - "testStrategy": "Detailed approach for verifying task completion." - }`; + "details": "Detailed implementation steps, considerations, code examples, or technical approach", + "testStrategy": "Specific steps to verify correct implementation and functionality", + "dependencies": [1, 3] // Example: IDs of tasks that must be completed before this task + } +`; // Add any manually provided details to the prompt for context let contextFromArgs = ''; @@ -206,15 +913,18 @@ async function addTask( contextFromArgs += `\n- Additional Test Strategy Context: "${manualTaskData.testStrategy}"`; // User Prompt - const userPrompt = `Create a comprehensive new task (Task #${newTaskId}) for a software development project based on this description: "${prompt}" + const userPrompt = `You are generating the details for Task #${newTaskId}. Based on the user's request: "${prompt}", create a comprehensive new task for a software development project. ${contextTasks} ${contextFromArgs ? `\nConsider these additional details provided by the user:${contextFromArgs}` : ''} + Based on the information about existing tasks provided above, include appropriate dependencies in the "dependencies" array. Only include task IDs that this new task directly depends on. + Return your answer as a single JSON object matching the schema precisely: ${taskStructureDesc} - Make sure the details and test strategy are thorough and specific.`; + Make sure the details and test strategy are comprehensive and specific. DO NOT include the task ID in the title. + `; // Start the loading indicator - only for text mode if (outputFormat === 'text') { @@ -287,11 +997,32 @@ async function addTask( details: taskData.details || '', testStrategy: taskData.testStrategy || '', status: 'pending', - dependencies: numericDependencies, // Use validated numeric dependencies + dependencies: taskData.dependencies?.length + ? taskData.dependencies + : numericDependencies, // Use AI-suggested dependencies if available, fallback to manually specified priority: effectivePriority, subtasks: [] // Initialize with empty subtasks array }; + // Additional check: validate all dependencies in the AI response + if (taskData.dependencies?.length) { + const allValidDeps = taskData.dependencies.every((depId) => { + const numDepId = parseInt(depId, 10); + return !isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId); + }); + + if (!allValidDeps) { + report( + 'AI suggested invalid dependencies. Filtering them out...', + 'warn' + ); + newTask.dependencies = taskData.dependencies.filter((depId) => { + const numDepId = parseInt(depId, 10); + return !isNaN(numDepId) && data.tasks.some((t) => t.id === numDepId); + }); + } + } + // Add the task to the tasks array data.tasks.push(newTask); @@ -340,6 +1071,72 @@ async function addTask( } }; + // Check if AI added new dependencies that weren't explicitly provided + const aiAddedDeps = newTask.dependencies.filter( + (dep) => !numericDependencies.includes(dep) + ); + + // Check if AI removed any dependencies that were explicitly provided + const aiRemovedDeps = numericDependencies.filter( + (dep) => !newTask.dependencies.includes(dep) + ); + + // Get task titles for dependencies to display + const depTitles = {}; + newTask.dependencies.forEach((dep) => { + const depTask = data.tasks.find((t) => t.id === dep); + if (depTask) { + depTitles[dep] = truncate(depTask.title, 30); + } + }); + + // Prepare dependency display string + let dependencyDisplay = ''; + if (newTask.dependencies.length > 0) { + dependencyDisplay = chalk.white('Dependencies:') + '\n'; + newTask.dependencies.forEach((dep) => { + const isAiAdded = aiAddedDeps.includes(dep); + const depType = isAiAdded ? chalk.yellow(' (AI suggested)') : ''; + dependencyDisplay += + chalk.white( + ` - ${dep}: ${depTitles[dep] || 'Unknown task'}${depType}` + ) + '\n'; + }); + } else { + dependencyDisplay = chalk.white('Dependencies: None') + '\n'; + } + + // Add info about removed dependencies if any + if (aiRemovedDeps.length > 0) { + dependencyDisplay += + chalk.gray('\nUser-specified dependencies that were not used:') + + '\n'; + aiRemovedDeps.forEach((dep) => { + const depTask = data.tasks.find((t) => t.id === dep); + const title = depTask ? truncate(depTask.title, 30) : 'Unknown task'; + dependencyDisplay += chalk.gray(` - ${dep}: ${title}`) + '\n'; + }); + } + + // Add dependency analysis summary + let dependencyAnalysis = ''; + if (aiAddedDeps.length > 0 || aiRemovedDeps.length > 0) { + dependencyAnalysis = + '\n' + chalk.white.bold('Dependency Analysis:') + '\n'; + if (aiAddedDeps.length > 0) { + dependencyAnalysis += + chalk.green( + `AI identified ${aiAddedDeps.length} additional dependencies` + ) + '\n'; + } + if (aiRemovedDeps.length > 0) { + dependencyAnalysis += + chalk.yellow( + `AI excluded ${aiRemovedDeps.length} user-provided dependencies` + ) + '\n'; + } + } + // Show success message box console.log( boxen( @@ -352,11 +1149,9 @@ async function addTask( chalk.white( `Priority: ${chalk[getPriorityColor(newTask.priority)](newTask.priority)}` ) + - '\n' + - (numericDependencies.length > 0 - ? chalk.white(`Dependencies: ${numericDependencies.join(', ')}`) + - '\n' - : '') + + '\n\n' + + dependencyDisplay + + dependencyAnalysis + '\n' + chalk.white.bold('Next Steps:') + '\n' + diff --git a/scripts/modules/task-manager/analyze-task-complexity.js b/scripts/modules/task-manager/analyze-task-complexity.js index ac828436..670bd49e 100644 --- a/scripts/modules/task-manager/analyze-task-complexity.js +++ b/scripts/modules/task-manager/analyze-task-complexity.js @@ -1,6 +1,7 @@ import chalk from 'chalk'; import boxen from 'boxen'; import readline from 'readline'; +import fs from 'fs'; import { log, readJSON, writeJSON, isSilentMode } from '../utils.js'; @@ -51,6 +52,9 @@ Do not include any explanatory text, markdown formatting, or code block markers * @param {string|number} [options.threshold] - Complexity threshold * @param {boolean} [options.research] - Use research role * @param {string} [options.projectRoot] - Project root path (for MCP/env fallback). + * @param {string} [options.id] - Comma-separated list of task IDs to analyze specifically + * @param {number} [options.from] - Starting task ID in a range to analyze + * @param {number} [options.to] - Ending task ID in a range to analyze * @param {Object} [options._filteredTasksData] - Pre-filtered task data (internal use) * @param {number} [options._originalTaskCount] - Original task count (internal use) * @param {Object} context - Context object, potentially containing session and mcpLog @@ -65,6 +69,15 @@ async function analyzeTaskComplexity(options, context = {}) { const thresholdScore = parseFloat(options.threshold || '5'); const useResearch = options.research || false; const projectRoot = options.projectRoot; + // New parameters for task ID filtering + const specificIds = options.id + ? options.id + .split(',') + .map((id) => parseInt(id.trim(), 10)) + .filter((id) => !isNaN(id)) + : null; + const fromId = options.from !== undefined ? parseInt(options.from, 10) : null; + const toId = options.to !== undefined ? parseInt(options.to, 10) : null; const outputFormat = mcpLog ? 'json' : 'text'; @@ -88,13 +101,14 @@ async function analyzeTaskComplexity(options, context = {}) { reportLog(`Reading tasks from ${tasksPath}...`, 'info'); let tasksData; let originalTaskCount = 0; + let originalData = null; if (options._filteredTasksData) { tasksData = options._filteredTasksData; originalTaskCount = options._originalTaskCount || tasksData.tasks.length; if (!options._originalTaskCount) { try { - const originalData = readJSON(tasksPath); + originalData = readJSON(tasksPath); if (originalData && originalData.tasks) { originalTaskCount = originalData.tasks.length; } @@ -103,22 +117,80 @@ async function analyzeTaskComplexity(options, context = {}) { } } } else { - tasksData = readJSON(tasksPath); + originalData = readJSON(tasksPath); if ( - !tasksData || - !tasksData.tasks || - !Array.isArray(tasksData.tasks) || - tasksData.tasks.length === 0 + !originalData || + !originalData.tasks || + !Array.isArray(originalData.tasks) || + originalData.tasks.length === 0 ) { throw new Error('No tasks found in the tasks file'); } - originalTaskCount = tasksData.tasks.length; + originalTaskCount = originalData.tasks.length; + + // Filter tasks based on active status const activeStatuses = ['pending', 'blocked', 'in-progress']; - const filteredTasks = tasksData.tasks.filter((task) => + let filteredTasks = originalData.tasks.filter((task) => activeStatuses.includes(task.status?.toLowerCase() || 'pending') ); + + // Apply ID filtering if specified + if (specificIds && specificIds.length > 0) { + reportLog( + `Filtering tasks by specific IDs: ${specificIds.join(', ')}`, + 'info' + ); + filteredTasks = filteredTasks.filter((task) => + specificIds.includes(task.id) + ); + + if (outputFormat === 'text') { + if (filteredTasks.length === 0 && specificIds.length > 0) { + console.log( + chalk.yellow( + `Warning: No active tasks found with IDs: ${specificIds.join(', ')}` + ) + ); + } else if (filteredTasks.length < specificIds.length) { + const foundIds = filteredTasks.map((t) => t.id); + const missingIds = specificIds.filter( + (id) => !foundIds.includes(id) + ); + console.log( + chalk.yellow( + `Warning: Some requested task IDs were not found or are not active: ${missingIds.join(', ')}` + ) + ); + } + } + } + // Apply range filtering if specified + else if (fromId !== null || toId !== null) { + const effectiveFromId = fromId !== null ? fromId : 1; + const effectiveToId = + toId !== null + ? toId + : Math.max(...originalData.tasks.map((t) => t.id)); + + reportLog( + `Filtering tasks by ID range: ${effectiveFromId} to ${effectiveToId}`, + 'info' + ); + filteredTasks = filteredTasks.filter( + (task) => task.id >= effectiveFromId && task.id <= effectiveToId + ); + + if (outputFormat === 'text' && filteredTasks.length === 0) { + console.log( + chalk.yellow( + `Warning: No active tasks found in range: ${effectiveFromId}-${effectiveToId}` + ) + ); + } + } + tasksData = { - ...tasksData, + ...originalData, tasks: filteredTasks, _originalTaskCount: originalTaskCount }; @@ -129,7 +201,18 @@ async function analyzeTaskComplexity(options, context = {}) { `Found ${originalTaskCount} total tasks in the task file.`, 'info' ); - if (skippedCount > 0) { + + // Updated messaging to reflect filtering logic + if (specificIds || fromId !== null || toId !== null) { + const filterMsg = specificIds + ? `Analyzing ${tasksData.tasks.length} tasks with specific IDs: ${specificIds.join(', ')}` + : `Analyzing ${tasksData.tasks.length} tasks in range: ${fromId || 1} to ${toId || 'end'}`; + + reportLog(filterMsg, 'info'); + if (outputFormat === 'text') { + console.log(chalk.blue(filterMsg)); + } + } else if (skippedCount > 0) { const skipMessage = `Skipping ${skippedCount} tasks marked as done/cancelled/deferred. Analyzing ${tasksData.tasks.length} active tasks.`; reportLog(skipMessage, 'info'); if (outputFormat === 'text') { @@ -137,7 +220,59 @@ async function analyzeTaskComplexity(options, context = {}) { } } + // Check for existing report before doing analysis + let existingReport = null; + let existingAnalysisMap = new Map(); // For quick lookups by task ID + try { + if (fs.existsSync(outputPath)) { + existingReport = readJSON(outputPath); + reportLog(`Found existing complexity report at ${outputPath}`, 'info'); + + if ( + existingReport && + existingReport.complexityAnalysis && + Array.isArray(existingReport.complexityAnalysis) + ) { + // Create lookup map of existing analysis entries + existingReport.complexityAnalysis.forEach((item) => { + existingAnalysisMap.set(item.taskId, item); + }); + reportLog( + `Existing report contains ${existingReport.complexityAnalysis.length} task analyses`, + 'info' + ); + } + } + } catch (readError) { + reportLog( + `Warning: Could not read existing report: ${readError.message}`, + 'warn' + ); + existingReport = null; + existingAnalysisMap.clear(); + } + if (tasksData.tasks.length === 0) { + // If using ID filtering but no matching tasks, return existing report or empty + if (existingReport && (specificIds || fromId !== null || toId !== null)) { + reportLog( + `No matching tasks found for analysis. Keeping existing report.`, + 'info' + ); + if (outputFormat === 'text') { + console.log( + chalk.yellow( + `No matching tasks found for analysis. Keeping existing report.` + ) + ); + } + return { + report: existingReport, + telemetryData: null + }; + } + + // Otherwise create empty report const emptyReport = { meta: { generatedAt: new Date().toISOString(), @@ -146,9 +281,9 @@ async function analyzeTaskComplexity(options, context = {}) { projectName: getProjectName(session), usedResearch: useResearch }, - complexityAnalysis: [] + complexityAnalysis: existingReport?.complexityAnalysis || [] }; - reportLog(`Writing empty complexity report to ${outputPath}...`, 'info'); + reportLog(`Writing complexity report to ${outputPath}...`, 'info'); writeJSON(outputPath, emptyReport); reportLog( `Task complexity analysis complete. Report written to ${outputPath}`, @@ -196,9 +331,13 @@ async function analyzeTaskComplexity(options, context = {}) { ) ); } - return emptyReport; + return { + report: emptyReport, + telemetryData: null + }; } + // Continue with regular analysis path const prompt = generateInternalComplexityAnalysisPrompt(tasksData); const systemPrompt = 'You are an expert software architect and project manager analyzing task complexity. Respond only with the requested valid JSON array.'; @@ -326,15 +465,47 @@ async function analyzeTaskComplexity(options, context = {}) { } } + // Merge with existing report + let finalComplexityAnalysis = []; + + if (existingReport && Array.isArray(existingReport.complexityAnalysis)) { + // Create a map of task IDs that we just analyzed + const analyzedTaskIds = new Set( + complexityAnalysis.map((item) => item.taskId) + ); + + // Keep existing entries that weren't in this analysis run + const existingEntriesNotAnalyzed = + existingReport.complexityAnalysis.filter( + (item) => !analyzedTaskIds.has(item.taskId) + ); + + // Combine with new analysis + finalComplexityAnalysis = [ + ...existingEntriesNotAnalyzed, + ...complexityAnalysis + ]; + + reportLog( + `Merged ${complexityAnalysis.length} new analyses with ${existingEntriesNotAnalyzed.length} existing entries`, + 'info' + ); + } else { + // No existing report or invalid format, just use the new analysis + finalComplexityAnalysis = complexityAnalysis; + } + const report = { meta: { generatedAt: new Date().toISOString(), tasksAnalyzed: tasksData.tasks.length, + totalTasks: originalTaskCount, + analysisCount: finalComplexityAnalysis.length, thresholdScore: thresholdScore, projectName: getProjectName(session), usedResearch: useResearch }, - complexityAnalysis: complexityAnalysis + complexityAnalysis: finalComplexityAnalysis }; reportLog(`Writing complexity report to ${outputPath}...`, 'info'); writeJSON(outputPath, report); @@ -350,6 +521,7 @@ async function analyzeTaskComplexity(options, context = {}) { `Task complexity analysis complete. Report written to ${outputPath}` ) ); + // Calculate statistics specifically for this analysis run const highComplexity = complexityAnalysis.filter( (t) => t.complexityScore >= 8 ).length; @@ -361,18 +533,25 @@ async function analyzeTaskComplexity(options, context = {}) { ).length; const totalAnalyzed = complexityAnalysis.length; - console.log('\nComplexity Analysis Summary:'); + console.log('\nCurrent Analysis Summary:'); console.log('----------------------------'); - console.log( - `Active tasks sent for analysis: ${tasksData.tasks.length}` - ); - console.log(`Tasks successfully analyzed: ${totalAnalyzed}`); + console.log(`Tasks analyzed in this run: ${totalAnalyzed}`); console.log(`High complexity tasks: ${highComplexity}`); console.log(`Medium complexity tasks: ${mediumComplexity}`); console.log(`Low complexity tasks: ${lowComplexity}`); - console.log( - `Sum verification: ${highComplexity + mediumComplexity + lowComplexity} (should equal ${totalAnalyzed})` - ); + + if (existingReport) { + console.log('\nUpdated Report Summary:'); + console.log('----------------------------'); + console.log( + `Total analyses in report: ${finalComplexityAnalysis.length}` + ); + console.log( + `Analyses from previous runs: ${finalComplexityAnalysis.length - totalAnalyzed}` + ); + console.log(`New/updated analyses: ${totalAnalyzed}`); + } + console.log(`Research-backed analysis: ${useResearch ? 'Yes' : 'No'}`); console.log( `\nSee ${outputPath} for the full report and expansion commands.` diff --git a/scripts/modules/task-manager/generate-task-files.js b/scripts/modules/task-manager/generate-task-files.js index 7413e570..368ec7ad 100644 --- a/scripts/modules/task-manager/generate-task-files.js +++ b/scripts/modules/task-manager/generate-task-files.js @@ -35,6 +35,53 @@ function generateTaskFiles(tasksPath, outputDir, options = {}) { log('info', `Validating and fixing dependencies`); validateAndFixDependencies(data, tasksPath); + // Get valid task IDs from tasks.json + const validTaskIds = data.tasks.map((task) => task.id); + + // Cleanup orphaned task files + log('info', 'Checking for orphaned task files to clean up...'); + try { + // Get all task files in the output directory + const files = fs.readdirSync(outputDir); + const taskFilePattern = /^task_(\d+)\.txt$/; + + // Filter for task files and check if they match a valid task ID + const orphanedFiles = files.filter((file) => { + const match = file.match(taskFilePattern); + if (match) { + const fileTaskId = parseInt(match[1], 10); + return !validTaskIds.includes(fileTaskId); + } + return false; + }); + + // Delete orphaned files + if (orphanedFiles.length > 0) { + log( + 'info', + `Found ${orphanedFiles.length} orphaned task files to remove` + ); + + orphanedFiles.forEach((file) => { + const filePath = path.join(outputDir, file); + try { + fs.unlinkSync(filePath); + log('info', `Removed orphaned task file: ${file}`); + } catch (err) { + log( + 'warn', + `Failed to remove orphaned task file ${file}: ${err.message}` + ); + } + }); + } else { + log('info', 'No orphaned task files found'); + } + } catch (err) { + log('warn', `Error cleaning up orphaned task files: ${err.message}`); + // Continue with file generation even if cleanup fails + } + // Generate task files log('info', 'Generating individual task files...'); data.tasks.forEach((task) => { diff --git a/scripts/modules/task-manager/models.js b/scripts/modules/task-manager/models.js index 1ee63175..77a6ae4f 100644 --- a/scripts/modules/task-manager/models.js +++ b/scripts/modules/task-manager/models.js @@ -6,6 +6,7 @@ import path from 'path'; import fs from 'fs'; import https from 'https'; +import http from 'http'; import { getMainModelId, getResearchModelId, @@ -19,7 +20,8 @@ import { getConfig, writeConfig, isConfigFilePresent, - getAllProviders + getAllProviders, + getBaseUrlForRole } from '../config-manager.js'; /** @@ -68,6 +70,68 @@ function fetchOpenRouterModels() { }); } +/** + * Fetches the list of models from Ollama instance. + * @param {string} baseUrl - The base URL for the Ollama API (e.g., "http://localhost:11434/api") + * @returns {Promise} A promise that resolves with the list of model objects or null if fetch fails. + */ +function fetchOllamaModels(baseUrl = 'http://localhost:11434/api') { + return new Promise((resolve) => { + try { + // Parse the base URL to extract hostname, port, and base path + const url = new URL(baseUrl); + const isHttps = url.protocol === 'https:'; + const port = url.port || (isHttps ? 443 : 80); + const basePath = url.pathname.endsWith('/') + ? url.pathname.slice(0, -1) + : url.pathname; + + const options = { + hostname: url.hostname, + port: parseInt(port, 10), + path: `${basePath}/tags`, + method: 'GET', + headers: { + Accept: 'application/json' + } + }; + + const requestLib = isHttps ? https : http; + const req = requestLib.request(options, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + if (res.statusCode === 200) { + try { + const parsedData = JSON.parse(data); + resolve(parsedData.models || []); // Return the array of models + } catch (e) { + console.error('Error parsing Ollama response:', e); + resolve(null); // Indicate failure + } + } else { + console.error( + `Ollama API request failed with status code: ${res.statusCode}` + ); + resolve(null); // Indicate failure + } + }); + }); + + req.on('error', (e) => { + console.error('Error fetching Ollama models:', e); + resolve(null); // Indicate failure + }); + req.end(); + } catch (e) { + console.error('Error parsing Ollama base URL:', e); + resolve(null); // Indicate failure + } + }); +} + /** * Get the current model configuration * @param {Object} [options] - Options for the operation @@ -416,10 +480,29 @@ async function setModel(role, modelId, options = {}) { ); } } else if (providerHint === 'ollama') { - // Hinted as Ollama - set provider directly WITHOUT checking OpenRouter - determinedProvider = 'ollama'; - warningMessage = `Warning: Custom Ollama model '${modelId}' set. Ensure your Ollama server is running and has pulled this model. Taskmaster cannot guarantee compatibility.`; - report('warn', warningMessage); + // Check Ollama ONLY because hint was ollama + report('info', `Checking Ollama for ${modelId} (as hinted)...`); + + // Get the Ollama base URL from config + const ollamaBaseUrl = getBaseUrlForRole(role, projectRoot); + const ollamaModels = await fetchOllamaModels(ollamaBaseUrl); + + if (ollamaModels === null) { + // Connection failed - server probably not running + throw new Error( + `Unable to connect to Ollama server at ${ollamaBaseUrl}. Please ensure Ollama is running and try again.` + ); + } else if (ollamaModels.some((m) => m.model === modelId)) { + determinedProvider = 'ollama'; + warningMessage = `Warning: Custom Ollama model '${modelId}' set. Ensure your Ollama server is running and has pulled this model. Taskmaster cannot guarantee compatibility.`; + report('warn', warningMessage); + } else { + // Server is running but model not found + const tagsUrl = `${ollamaBaseUrl}/tags`; + throw new Error( + `Model ID "${modelId}" not found in the Ollama instance. Please verify the model is pulled and available. You can check available models with: curl ${tagsUrl}` + ); + } } else { // Invalid provider hint - should not happen throw new Error(`Invalid provider hint received: ${providerHint}`); diff --git a/scripts/modules/task-manager/move-task.js b/scripts/modules/task-manager/move-task.js new file mode 100644 index 00000000..147de724 --- /dev/null +++ b/scripts/modules/task-manager/move-task.js @@ -0,0 +1,571 @@ +import path from 'path'; +import { log, readJSON, writeJSON } from '../utils.js'; +import { isTaskDependentOn } from '../task-manager.js'; +import generateTaskFiles from './generate-task-files.js'; + +/** + * Move a task or subtask to a new position + * @param {string} tasksPath - Path to tasks.json file + * @param {string} sourceId - ID of the task/subtask to move (e.g., '5' or '5.2') + * @param {string} destinationId - ID of the destination (e.g., '7' or '7.3') + * @param {boolean} generateFiles - Whether to regenerate task files after moving + * @returns {Object} Result object with moved task details + */ +async function moveTask( + tasksPath, + sourceId, + destinationId, + generateFiles = true +) { + try { + log('info', `Moving task/subtask ${sourceId} to ${destinationId}...`); + + // Read the existing tasks + const data = readJSON(tasksPath); + if (!data || !data.tasks) { + throw new Error(`Invalid or missing tasks file at ${tasksPath}`); + } + + // Parse source ID to determine if it's a task or subtask + const isSourceSubtask = sourceId.includes('.'); + let sourceTask, + sourceParentTask, + sourceSubtask, + sourceTaskIndex, + sourceSubtaskIndex; + + // Parse destination ID to determine the target + const isDestinationSubtask = destinationId.includes('.'); + let destTask, destParentTask, destSubtask, destTaskIndex, destSubtaskIndex; + + // Validate source exists + if (isSourceSubtask) { + // Source is a subtask + const [parentIdStr, subtaskIdStr] = sourceId.split('.'); + const parentIdNum = parseInt(parentIdStr, 10); + const subtaskIdNum = parseInt(subtaskIdStr, 10); + + sourceParentTask = data.tasks.find((t) => t.id === parentIdNum); + if (!sourceParentTask) { + throw new Error(`Source parent task with ID ${parentIdNum} not found`); + } + + if ( + !sourceParentTask.subtasks || + sourceParentTask.subtasks.length === 0 + ) { + throw new Error(`Source parent task ${parentIdNum} has no subtasks`); + } + + sourceSubtaskIndex = sourceParentTask.subtasks.findIndex( + (st) => st.id === subtaskIdNum + ); + if (sourceSubtaskIndex === -1) { + throw new Error(`Source subtask ${sourceId} not found`); + } + + sourceSubtask = { ...sourceParentTask.subtasks[sourceSubtaskIndex] }; + } else { + // Source is a task + const sourceIdNum = parseInt(sourceId, 10); + sourceTaskIndex = data.tasks.findIndex((t) => t.id === sourceIdNum); + if (sourceTaskIndex === -1) { + throw new Error(`Source task with ID ${sourceIdNum} not found`); + } + + sourceTask = { ...data.tasks[sourceTaskIndex] }; + } + + // Validate destination exists + if (isDestinationSubtask) { + // Destination is a subtask (target will be the parent of this subtask) + const [parentIdStr, subtaskIdStr] = destinationId.split('.'); + const parentIdNum = parseInt(parentIdStr, 10); + const subtaskIdNum = parseInt(subtaskIdStr, 10); + + destParentTask = data.tasks.find((t) => t.id === parentIdNum); + if (!destParentTask) { + throw new Error( + `Destination parent task with ID ${parentIdNum} not found` + ); + } + + if (!destParentTask.subtasks || destParentTask.subtasks.length === 0) { + throw new Error( + `Destination parent task ${parentIdNum} has no subtasks` + ); + } + + destSubtaskIndex = destParentTask.subtasks.findIndex( + (st) => st.id === subtaskIdNum + ); + if (destSubtaskIndex === -1) { + throw new Error(`Destination subtask ${destinationId} not found`); + } + + destSubtask = destParentTask.subtasks[destSubtaskIndex]; + } else { + // Destination is a task + const destIdNum = parseInt(destinationId, 10); + destTaskIndex = data.tasks.findIndex((t) => t.id === destIdNum); + + if (destTaskIndex === -1) { + // Create placeholder for destination if it doesn't exist + log('info', `Creating placeholder for destination task ${destIdNum}`); + const newTask = { + id: destIdNum, + title: `Task ${destIdNum}`, + description: '', + status: 'pending', + priority: 'medium', + details: '', + testStrategy: '' + }; + + // Find correct position to insert the new task + let insertIndex = 0; + while ( + insertIndex < data.tasks.length && + data.tasks[insertIndex].id < destIdNum + ) { + insertIndex++; + } + + // Insert the new task at the appropriate position + data.tasks.splice(insertIndex, 0, newTask); + destTaskIndex = insertIndex; + destTask = data.tasks[destTaskIndex]; + } else { + destTask = data.tasks[destTaskIndex]; + + // Check if destination task is already a "real" task with content + // Only allow moving to destination IDs that don't have meaningful content + if ( + destTask.title !== `Task ${destTask.id}` || + destTask.description !== '' || + destTask.details !== '' + ) { + throw new Error( + `Cannot move to task ID ${destIdNum} as it already contains content. Choose a different destination ID.` + ); + } + } + } + + // Validate that we aren't trying to move a task to itself + if (sourceId === destinationId) { + throw new Error('Cannot move a task/subtask to itself'); + } + + // Prevent moving a parent to its own subtask + if (!isSourceSubtask && isDestinationSubtask) { + const destParentId = parseInt(destinationId.split('.')[0], 10); + if (parseInt(sourceId, 10) === destParentId) { + throw new Error('Cannot move a parent task to one of its own subtasks'); + } + } + + // Check for circular dependency when moving tasks + if (!isSourceSubtask && !isDestinationSubtask) { + const sourceIdNum = parseInt(sourceId, 10); + const destIdNum = parseInt(destinationId, 10); + + // Check if destination is dependent on source + if (isTaskDependentOn(data.tasks, destTask, sourceIdNum)) { + throw new Error( + `Cannot move task ${sourceId} to task ${destinationId} as it would create a circular dependency` + ); + } + } + + let movedTask; + + // Handle different move scenarios + if (!isSourceSubtask && !isDestinationSubtask) { + // Check if destination is a placeholder we just created + if ( + destTask.title === `Task ${destTask.id}` && + destTask.description === '' && + destTask.details === '' + ) { + // Case 0: Move task to a new position/ID (destination is a placeholder) + movedTask = moveTaskToNewId( + data, + sourceTask, + sourceTaskIndex, + destTask, + destTaskIndex + ); + } else { + // Case 1: Move standalone task to become a subtask of another task + movedTask = moveTaskToTask(data, sourceTask, sourceTaskIndex, destTask); + } + } else if (!isSourceSubtask && isDestinationSubtask) { + // Case 2: Move standalone task to become a subtask at a specific position + movedTask = moveTaskToSubtaskPosition( + data, + sourceTask, + sourceTaskIndex, + destParentTask, + destSubtaskIndex + ); + } else if (isSourceSubtask && !isDestinationSubtask) { + // Case 3: Move subtask to become a standalone task + movedTask = moveSubtaskToTask( + data, + sourceSubtask, + sourceParentTask, + sourceSubtaskIndex, + destTask + ); + } else if (isSourceSubtask && isDestinationSubtask) { + // Case 4: Move subtask to another parent or position + // First check if it's the same parent + const sourceParentId = parseInt(sourceId.split('.')[0], 10); + const destParentId = parseInt(destinationId.split('.')[0], 10); + + if (sourceParentId === destParentId) { + // Case 4a: Move subtask within the same parent (reordering) + movedTask = reorderSubtask( + sourceParentTask, + sourceSubtaskIndex, + destSubtaskIndex + ); + } else { + // Case 4b: Move subtask to a different parent + movedTask = moveSubtaskToAnotherParent( + sourceSubtask, + sourceParentTask, + sourceSubtaskIndex, + destParentTask, + destSubtaskIndex + ); + } + } + + // Write the updated tasks back to the file + writeJSON(tasksPath, data); + + // Generate task files if requested + if (generateFiles) { + log('info', 'Regenerating task files...'); + await generateTaskFiles(tasksPath, path.dirname(tasksPath)); + } + + return movedTask; + } catch (error) { + log('error', `Error moving task/subtask: ${error.message}`); + throw error; + } +} + +/** + * Move a standalone task to become a subtask of another task + * @param {Object} data - Tasks data object + * @param {Object} sourceTask - Source task to move + * @param {number} sourceTaskIndex - Index of source task in data.tasks + * @param {Object} destTask - Destination task + * @returns {Object} Moved task object + */ +function moveTaskToTask(data, sourceTask, sourceTaskIndex, destTask) { + // Initialize subtasks array if it doesn't exist + if (!destTask.subtasks) { + destTask.subtasks = []; + } + + // Find the highest subtask ID to determine the next ID + const highestSubtaskId = + destTask.subtasks.length > 0 + ? Math.max(...destTask.subtasks.map((st) => st.id)) + : 0; + const newSubtaskId = highestSubtaskId + 1; + + // Create the new subtask from the source task + const newSubtask = { + ...sourceTask, + id: newSubtaskId, + parentTaskId: destTask.id + }; + + // Add to destination's subtasks + destTask.subtasks.push(newSubtask); + + // Remove the original task from the tasks array + data.tasks.splice(sourceTaskIndex, 1); + + log( + 'info', + `Moved task ${sourceTask.id} to become subtask ${destTask.id}.${newSubtaskId}` + ); + + return newSubtask; +} + +/** + * Move a standalone task to become a subtask at a specific position + * @param {Object} data - Tasks data object + * @param {Object} sourceTask - Source task to move + * @param {number} sourceTaskIndex - Index of source task in data.tasks + * @param {Object} destParentTask - Destination parent task + * @param {number} destSubtaskIndex - Index of the subtask before which to insert + * @returns {Object} Moved task object + */ +function moveTaskToSubtaskPosition( + data, + sourceTask, + sourceTaskIndex, + destParentTask, + destSubtaskIndex +) { + // Initialize subtasks array if it doesn't exist + if (!destParentTask.subtasks) { + destParentTask.subtasks = []; + } + + // Find the highest subtask ID to determine the next ID + const highestSubtaskId = + destParentTask.subtasks.length > 0 + ? Math.max(...destParentTask.subtasks.map((st) => st.id)) + : 0; + const newSubtaskId = highestSubtaskId + 1; + + // Create the new subtask from the source task + const newSubtask = { + ...sourceTask, + id: newSubtaskId, + parentTaskId: destParentTask.id + }; + + // Insert at specific position + destParentTask.subtasks.splice(destSubtaskIndex + 1, 0, newSubtask); + + // Remove the original task from the tasks array + data.tasks.splice(sourceTaskIndex, 1); + + log( + 'info', + `Moved task ${sourceTask.id} to become subtask ${destParentTask.id}.${newSubtaskId}` + ); + + return newSubtask; +} + +/** + * Move a subtask to become a standalone task + * @param {Object} data - Tasks data object + * @param {Object} sourceSubtask - Source subtask to move + * @param {Object} sourceParentTask - Parent task of the source subtask + * @param {number} sourceSubtaskIndex - Index of source subtask in parent's subtasks + * @param {Object} destTask - Destination task (for position reference) + * @returns {Object} Moved task object + */ +function moveSubtaskToTask( + data, + sourceSubtask, + sourceParentTask, + sourceSubtaskIndex, + destTask +) { + // Find the highest task ID to determine the next ID + const highestId = Math.max(...data.tasks.map((t) => t.id)); + const newTaskId = highestId + 1; + + // Create the new task from the subtask + const newTask = { + ...sourceSubtask, + id: newTaskId, + priority: sourceParentTask.priority || 'medium' // Inherit priority from parent + }; + delete newTask.parentTaskId; + + // Add the parent task as a dependency if not already present + if (!newTask.dependencies) { + newTask.dependencies = []; + } + if (!newTask.dependencies.includes(sourceParentTask.id)) { + newTask.dependencies.push(sourceParentTask.id); + } + + // Find the destination index to insert the new task + const destTaskIndex = data.tasks.findIndex((t) => t.id === destTask.id); + + // Insert the new task after the destination task + data.tasks.splice(destTaskIndex + 1, 0, newTask); + + // Remove the subtask from the parent + sourceParentTask.subtasks.splice(sourceSubtaskIndex, 1); + + // If parent has no more subtasks, remove the subtasks array + if (sourceParentTask.subtasks.length === 0) { + delete sourceParentTask.subtasks; + } + + log( + 'info', + `Moved subtask ${sourceParentTask.id}.${sourceSubtask.id} to become task ${newTaskId}` + ); + + return newTask; +} + +/** + * Reorder a subtask within the same parent + * @param {Object} parentTask - Parent task containing the subtask + * @param {number} sourceIndex - Current index of the subtask + * @param {number} destIndex - Destination index for the subtask + * @returns {Object} Moved subtask object + */ +function reorderSubtask(parentTask, sourceIndex, destIndex) { + // Get the subtask to move + const subtask = parentTask.subtasks[sourceIndex]; + + // Remove the subtask from its current position + parentTask.subtasks.splice(sourceIndex, 1); + + // Insert the subtask at the new position + // If destIndex was after sourceIndex, it's now one less because we removed an item + const adjustedDestIndex = sourceIndex < destIndex ? destIndex - 1 : destIndex; + parentTask.subtasks.splice(adjustedDestIndex, 0, subtask); + + log( + 'info', + `Reordered subtask ${parentTask.id}.${subtask.id} within parent task ${parentTask.id}` + ); + + return subtask; +} + +/** + * Move a subtask to a different parent + * @param {Object} sourceSubtask - Source subtask to move + * @param {Object} sourceParentTask - Parent task of the source subtask + * @param {number} sourceSubtaskIndex - Index of source subtask in parent's subtasks + * @param {Object} destParentTask - Destination parent task + * @param {number} destSubtaskIndex - Index of the subtask before which to insert + * @returns {Object} Moved subtask object + */ +function moveSubtaskToAnotherParent( + sourceSubtask, + sourceParentTask, + sourceSubtaskIndex, + destParentTask, + destSubtaskIndex +) { + // Find the highest subtask ID in the destination parent + const highestSubtaskId = + destParentTask.subtasks.length > 0 + ? Math.max(...destParentTask.subtasks.map((st) => st.id)) + : 0; + const newSubtaskId = highestSubtaskId + 1; + + // Create the new subtask with updated parent reference + const newSubtask = { + ...sourceSubtask, + id: newSubtaskId, + parentTaskId: destParentTask.id + }; + + // If the subtask depends on its original parent, keep that dependency + if (!newSubtask.dependencies) { + newSubtask.dependencies = []; + } + if (!newSubtask.dependencies.includes(sourceParentTask.id)) { + newSubtask.dependencies.push(sourceParentTask.id); + } + + // Insert at the destination position + destParentTask.subtasks.splice(destSubtaskIndex + 1, 0, newSubtask); + + // Remove the subtask from the original parent + sourceParentTask.subtasks.splice(sourceSubtaskIndex, 1); + + // If original parent has no more subtasks, remove the subtasks array + if (sourceParentTask.subtasks.length === 0) { + delete sourceParentTask.subtasks; + } + + log( + 'info', + `Moved subtask ${sourceParentTask.id}.${sourceSubtask.id} to become subtask ${destParentTask.id}.${newSubtaskId}` + ); + + return newSubtask; +} + +/** + * Move a standalone task to a new ID position + * @param {Object} data - Tasks data object + * @param {Object} sourceTask - Source task to move + * @param {number} sourceTaskIndex - Index of source task in data.tasks + * @param {Object} destTask - Destination placeholder task + * @param {number} destTaskIndex - Index of destination task in data.tasks + * @returns {Object} Moved task object + */ +function moveTaskToNewId( + data, + sourceTask, + sourceTaskIndex, + destTask, + destTaskIndex +) { + // Create a copy of the source task with the new ID + const movedTask = { + ...sourceTask, + id: destTask.id + }; + + // Get numeric IDs for comparison + const sourceIdNum = parseInt(sourceTask.id, 10); + const destIdNum = parseInt(destTask.id, 10); + + // Handle subtasks if present + if (sourceTask.subtasks && sourceTask.subtasks.length > 0) { + // Update subtasks to reference the new parent ID if needed + movedTask.subtasks = sourceTask.subtasks.map((subtask) => ({ + ...subtask, + parentTaskId: destIdNum + })); + } + + // Update any dependencies in other tasks that referenced the old ID + data.tasks.forEach((task) => { + if (task.dependencies && task.dependencies.includes(sourceIdNum)) { + // Replace the old ID with the new ID + const depIndex = task.dependencies.indexOf(sourceIdNum); + task.dependencies[depIndex] = destIdNum; + } + + // Also check for subtask dependencies that might reference this task + if (task.subtasks && task.subtasks.length > 0) { + task.subtasks.forEach((subtask) => { + if ( + subtask.dependencies && + subtask.dependencies.includes(sourceIdNum) + ) { + const depIndex = subtask.dependencies.indexOf(sourceIdNum); + subtask.dependencies[depIndex] = destIdNum; + } + }); + } + }); + + // Remove the original task from its position + data.tasks.splice(sourceTaskIndex, 1); + + // If we're moving to a position after the original, adjust the destination index + // since removing the original shifts everything down by 1 + const adjustedDestIndex = + sourceTaskIndex < destTaskIndex ? destTaskIndex - 1 : destTaskIndex; + + // Remove the placeholder destination task + data.tasks.splice(adjustedDestIndex, 1); + + // Insert the moved task at the destination position + data.tasks.splice(adjustedDestIndex, 0, movedTask); + + log('info', `Moved task ${sourceIdNum} to new ID ${destIdNum}`); + + return movedTask; +} + +export default moveTask; diff --git a/scripts/modules/task-manager/parse-prd.js b/scripts/modules/task-manager/parse-prd.js index 22633583..095f9941 100644 --- a/scripts/modules/task-manager/parse-prd.js +++ b/scripts/modules/task-manager/parse-prd.js @@ -50,6 +50,7 @@ const prdResponseSchema = z.object({ * @param {Object} options - Additional options * @param {boolean} [options.force=false] - Whether to overwrite existing tasks.json. * @param {boolean} [options.append=false] - Append to existing tasks file. + * @param {boolean} [options.research=false] - Use research model for enhanced PRD analysis. * @param {Object} [options.reportProgress] - Function to report progress (optional, likely unused). * @param {Object} [options.mcpLog] - MCP logger object (optional). * @param {Object} [options.session] - Session object from MCP server (optional). @@ -63,7 +64,8 @@ async function parsePRD(prdPath, tasksPath, numTasks, options = {}) { session, projectRoot, force = false, - append = false + append = false, + research = false } = options; const isMCP = !!mcpLog; const outputFormat = isMCP ? 'json' : 'text'; @@ -90,7 +92,9 @@ async function parsePRD(prdPath, tasksPath, numTasks, options = {}) { } }; - report(`Parsing PRD file: ${prdPath}, Force: ${force}, Append: ${append}`); + report( + `Parsing PRD file: ${prdPath}, Force: ${force}, Append: ${append}, Research: ${research}` + ); let existingTasks = []; let nextId = 1; @@ -148,8 +152,22 @@ async function parsePRD(prdPath, tasksPath, numTasks, options = {}) { throw new Error(`Input file ${prdPath} is empty or could not be read.`); } - // Build system prompt for PRD parsing - const systemPrompt = `You are an AI assistant specialized in analyzing Product Requirements Documents (PRDs) and generating a structured, logically ordered, dependency-aware and sequenced list of development tasks in JSON format. + // Research-specific enhancements to the system prompt + const researchPromptAddition = research + ? `\nBefore breaking down the PRD into tasks, you will: +1. Research and analyze the latest technologies, libraries, frameworks, and best practices that would be appropriate for this project +2. Identify any potential technical challenges, security concerns, or scalability issues not explicitly mentioned in the PRD without discarding any explicit requirements or going overboard with complexity -- always aim to provide the most direct path to implementation, avoiding over-engineering or roundabout approaches +3. Consider current industry standards and evolving trends relevant to this project (this step aims to solve LLM hallucinations and out of date information due to training data cutoff dates) +4. Evaluate alternative implementation approaches and recommend the most efficient path +5. Include specific library versions, helpful APIs, and concrete implementation guidance based on your research +6. Always aim to provide the most direct path to implementation, avoiding over-engineering or roundabout approaches + +Your task breakdown should incorporate this research, resulting in more detailed implementation guidance, more accurate dependency mapping, and more precise technology recommendations than would be possible from the PRD text alone, while maintaining all explicit requirements and best practices and all details and nuances of the PRD.` + : ''; + + // Base system prompt for PRD parsing + const systemPrompt = `You are an AI assistant specialized in analyzing Product Requirements Documents (PRDs) and generating a structured, logically ordered, dependency-aware and sequenced list of development tasks in JSON format.${researchPromptAddition} + Analyze the provided PRD content and generate approximately ${numTasks} top-level development tasks. If the complexity or the level of detail of the PRD is high, generate more tasks relative to the complexity of the PRD Each task should represent a logical unit of work needed to implement the requirements and focus on the most direct and effective way to implement the requirements without unnecessary complexity or overengineering. Include pseudo-code, implementation details, and test strategy for each task. Find the most up to date information to implement each task. Assign sequential IDs starting from ${nextId}. Infer title, description, details, and test strategy for each task based *only* on the PRD content. @@ -176,13 +194,13 @@ Guidelines: 5. Include clear validation/testing approach for each task 6. Set appropriate dependency IDs (a task can only depend on tasks with lower IDs, potentially including existing tasks with IDs less than ${nextId} if applicable) 7. Assign priority (high/medium/low) based on criticality and dependency order -8. Include detailed implementation guidance in the "details" field +8. Include detailed implementation guidance in the "details" field${research ? ', with specific libraries and version recommendations based on your research' : ''} 9. If the PRD contains specific requirements for libraries, database schemas, frameworks, tech stacks, or any other implementation details, STRICTLY ADHERE to these requirements in your task breakdown and do not discard them under any circumstance 10. Focus on filling in any gaps left by the PRD or areas that aren't fully specified, while preserving all explicit requirements -11. Always aim to provide the most direct path to implementation, avoiding over-engineering or roundabout approaches`; +11. Always aim to provide the most direct path to implementation, avoiding over-engineering or roundabout approaches${research ? '\n12. For each task, include specific, actionable guidance based on current industry standards and best practices discovered through research' : ''}`; // Build user prompt with PRD content - const userPrompt = `Here's the Product Requirements Document (PRD) to break down into approximately ${numTasks} tasks, starting IDs from ${nextId}:\n\n${prdContent}\n\n + const userPrompt = `Here's the Product Requirements Document (PRD) to break down into approximately ${numTasks} tasks, starting IDs from ${nextId}:${research ? '\n\nRemember to thoroughly research current best practices and technologies before task breakdown to provide specific, actionable implementation details.' : ''}\n\n${prdContent}\n\n Return your response in this format: { @@ -204,11 +222,14 @@ Guidelines: }`; // Call the unified AI service - report('Calling AI service to generate tasks from PRD...', 'info'); + report( + `Calling AI service to generate tasks from PRD${research ? ' with research-backed analysis' : ''}...`, + 'info' + ); // Call generateObjectService with the CORRECT schema and additional telemetry params aiServiceResponse = await generateObjectService({ - role: 'main', + role: research ? 'research' : 'main', // Use research role if flag is set session: session, projectRoot: projectRoot, schema: prdResponseSchema, @@ -224,7 +245,9 @@ Guidelines: if (!fs.existsSync(tasksDir)) { fs.mkdirSync(tasksDir, { recursive: true }); } - logFn.success('Successfully parsed PRD via AI service.\n'); + logFn.success( + `Successfully parsed PRD via AI service${research ? ' with research-backed analysis' : ''}.` + ); // Validate and Process Tasks // const generatedData = aiServiceResponse?.mainResult?.object; @@ -294,7 +317,7 @@ Guidelines: // Write the final tasks to the file writeJSON(tasksPath, outputData); report( - `Successfully ${append ? 'appended' : 'generated'} ${processedNewTasks.length} tasks in ${tasksPath}`, + `Successfully ${append ? 'appended' : 'generated'} ${processedNewTasks.length} tasks in ${tasksPath}${research ? ' with research-backed analysis' : ''}`, 'success' ); @@ -306,7 +329,7 @@ Guidelines: console.log( boxen( chalk.green( - `Successfully generated ${processedNewTasks.length} new tasks. Total tasks in ${tasksPath}: ${finalTasks.length}` + `Successfully generated ${processedNewTasks.length} new tasks${research ? ' with research-backed analysis' : ''}. Total tasks in ${tasksPath}: ${finalTasks.length}` ), { padding: 1, borderColor: 'green', borderStyle: 'round' } ) diff --git a/scripts/task-complexity-report.json b/scripts/task-complexity-report.json index a7f6a48a..b90fc2ed 100644 --- a/scripts/task-complexity-report.json +++ b/scripts/task-complexity-report.json @@ -1,10 +1,12 @@ { "meta": { - "generatedAt": "2025-05-17T22:29:22.179Z", - "tasksAnalyzed": 40, + "generatedAt": "2025-05-22T05:48:33.026Z", + "tasksAnalyzed": 6, + "totalTasks": 88, + "analysisCount": 43, "thresholdScore": 5, "projectName": "Taskmaster", - "usedResearch": false + "usedResearch": true }, "complexityAnalysis": [ { @@ -215,22 +217,6 @@ "expansionPrompt": "The current 2 subtasks for implementing task creation without PRD appear appropriate. Consider if any additional subtasks are needed for validation, error handling, or integration with existing task management workflows.", "reasoning": "This task involves a relatively simple modification to allow task creation without requiring a PRD document. The complexity is low as it primarily involves creating a form interface and saving functionality. The 2 existing subtasks cover the main implementation areas of UI design and data saving." }, - { - "taskId": 69, - "taskTitle": "Enhance Analyze Complexity for Specific Task IDs", - "complexityScore": 5, - "recommendedSubtasks": 4, - "expansionPrompt": "The current 4 subtasks for enhancing the analyze-complexity feature appear well-structured. Consider if any additional subtasks are needed for performance optimization with large task sets or visualization improvements.", - "reasoning": "This task involves modifying the existing analyze-complexity feature to support analyzing specific task IDs and updating reports. The complexity is moderate as it requires careful handling of report merging and filtering logic. The 4 existing subtasks cover the main implementation areas from core logic to testing." - }, - { - "taskId": 70, - "taskTitle": "Implement 'diagram' command for Mermaid diagram generation", - "complexityScore": 6, - "recommendedSubtasks": 4, - "expansionPrompt": "The current 4 subtasks for implementing the 'diagram' command appear well-structured. Consider if any additional subtasks are needed for handling large dependency graphs, additional output formats, or integration with existing visualization tools.", - "reasoning": "This task involves creating a new command that generates Mermaid diagrams to visualize task dependencies. The complexity is moderate as it requires parsing task relationships, generating proper Mermaid syntax, and handling various output options. The 4 existing subtasks cover the main implementation areas from interface design to documentation." - }, { "taskId": 72, "taskTitle": "Implement PDF Generation for Project Progress and Dependency Overview", @@ -303,29 +289,69 @@ "expansionPrompt": "This task appears well-defined enough to be implemented without further subtasks. Focus on implementing accurate token counting for different models and proper fallback mechanisms.", "reasoning": "This task involves creating a utility function to count tokens for different AI models. The complexity is moderate as it requires integration with the tiktoken library and handling different tokenization schemes. No subtasks are necessary as the task is well-defined and focused." }, + { + "taskId": 69, + "taskTitle": "Enhance Analyze Complexity for Specific Task IDs", + "complexityScore": 7, + "recommendedSubtasks": 6, + "expansionPrompt": "Break down the task 'Enhance Analyze Complexity for Specific Task IDs' into 6 subtasks focusing on: 1) Core logic modification to accept ID parameters, 2) Report merging functionality, 3) CLI interface updates, 4) MCP tool integration, 5) Documentation updates, and 6) Comprehensive testing across all components.", + "reasoning": "This task involves modifying existing functionality across multiple components (core logic, CLI, MCP) with complex logic for filtering tasks and merging reports. The implementation requires careful handling of different parameter combinations and edge cases. The task has interdependent components that need to work together seamlessly, and the report merging functionality adds significant complexity." + }, + { + "taskId": 70, + "taskTitle": "Implement 'diagram' command for Mermaid diagram generation", + "complexityScore": 6, + "recommendedSubtasks": 5, + "expansionPrompt": "Break down the 'diagram' command implementation into 5 subtasks: 1) Command interface and parameter handling, 2) Task data extraction and transformation to Mermaid syntax, 3) Diagram rendering with status color coding, 4) Output formatting and file export functionality, and 5) Error handling and edge case management.", + "reasoning": "This task requires implementing a new feature rather than modifying existing code, which reduces complexity from integration challenges. However, it involves working with visualization logic, dependency mapping, and multiple output formats. The color coding based on status and handling of dependency relationships adds moderate complexity. The task is well-defined but requires careful attention to diagram formatting and error handling." + }, { "taskId": 85, "taskTitle": "Update ai-services-unified.js for dynamic token limits", - "complexityScore": 6, - "recommendedSubtasks": 1, - "expansionPrompt": "This task appears well-defined enough to be implemented without further subtasks. Focus on implementing dynamic token limit adjustment based on input length and model capabilities.", - "reasoning": "This task involves modifying the AI service runner to use the new token counting utility and dynamically adjust output token limits. The complexity is moderate to high as it requires careful integration with existing code and handling various edge cases. No subtasks are necessary as the task is well-defined and focused." + "complexityScore": 7, + "recommendedSubtasks": 5, + "expansionPrompt": "Break down the update of ai-services-unified.js for dynamic token limits into subtasks such as: (1) Import and integrate the token counting utility, (2) Refactor _unifiedServiceRunner to calculate and enforce dynamic token limits, (3) Update error handling for token limit violations, (4) Add and verify logging for token usage, (5) Write and execute tests for various prompt and model scenarios.", + "reasoning": "This task involves significant code changes to a core function, integration of a new utility, dynamic logic for multiple models, and robust error handling. It also requires comprehensive testing for edge cases and integration, making it moderately complex and best managed by splitting into focused subtasks." }, { "taskId": 86, "taskTitle": "Update .taskmasterconfig schema and user guide", - "complexityScore": 4, - "recommendedSubtasks": 1, - "expansionPrompt": "This task appears straightforward enough to be implemented without further subtasks. Focus on creating clear migration guidance and updating documentation.", - "reasoning": "This task involves creating a migration guide for users to update their configuration files and documenting the new token limit options. The complexity is relatively low as it primarily involves documentation and schema validation. No subtasks are necessary as the task is well-defined and focused." + "complexityScore": 6, + "recommendedSubtasks": 4, + "expansionPrompt": "Expand this task into subtasks: (1) Draft a migration guide for users, (2) Update user documentation to explain new config fields, (3) Modify schema validation logic in config-manager.js, (4) Test and validate backward compatibility and error messaging.", + "reasoning": "The task spans documentation, schema changes, migration guidance, and validation logic. While not algorithmically complex, it requires careful coordination and thorough testing to ensure a smooth user transition and robust validation." }, { "taskId": 87, "taskTitle": "Implement validation and error handling", "complexityScore": 5, - "recommendedSubtasks": 1, - "expansionPrompt": "This task appears well-defined enough to be implemented without further subtasks. Focus on comprehensive validation and helpful error messages throughout the system.", - "reasoning": "This task involves adding validation and error handling for token limits throughout the system. The complexity is moderate as it requires careful integration with multiple components and creating helpful error messages. No subtasks are necessary as the task is well-defined and focused." + "recommendedSubtasks": 4, + "expansionPrompt": "Decompose this task into: (1) Add validation logic for model and config loading, (2) Implement error handling and fallback mechanisms, (3) Enhance logging and reporting for token usage, (4) Develop helper functions for configuration suggestions and improvements.", + "reasoning": "This task is primarily about adding validation, error handling, and logging. While important for robustness, the logic is straightforward and can be modularized into a few clear subtasks." + }, + { + "taskId": 89, + "taskTitle": "Introduce Prioritize Command with Enhanced Priority Levels", + "complexityScore": 6, + "recommendedSubtasks": 5, + "expansionPrompt": "Expand this task into: (1) Implement the prioritize command with all required flags and shorthands, (2) Update CLI output and help documentation for new priority levels, (3) Ensure backward compatibility with existing commands, (4) Add error handling for invalid inputs, (5) Write and run tests for all command scenarios.", + "reasoning": "This CLI feature requires command parsing, updating internal logic for new priority levels, documentation, and robust error handling. The complexity is moderate due to the need for backward compatibility and comprehensive testing." + }, + { + "taskId": 90, + "taskTitle": "Implement Subtask Progress Analyzer and Reporting System", + "complexityScore": 8, + "recommendedSubtasks": 6, + "expansionPrompt": "Break down the analyzer implementation into: (1) Design and implement progress tracking logic, (2) Develop status validation and issue detection, (3) Build the reporting system with multiple output formats, (4) Integrate analyzer with the existing task management system, (5) Optimize for performance and scalability, (6) Write unit, integration, and performance tests.", + "reasoning": "This is a complex, multi-faceted feature involving data analysis, reporting, integration, and performance optimization. It touches many parts of the system and requires careful design, making it one of the most complex tasks in the list." + }, + { + "taskId": 91, + "taskTitle": "Implement Move Command for Tasks and Subtasks", + "complexityScore": 7, + "recommendedSubtasks": 5, + "expansionPrompt": "Expand this task into: (1) Implement move logic for tasks and subtasks, (2) Handle edge cases (invalid ids, non-existent parents, circular dependencies), (3) Update CLI to support move command with flags, (4) Ensure data integrity and update relationships, (5) Write and execute tests for various move scenarios.", + "reasoning": "Moving tasks and subtasks requires careful handling of hierarchical data, edge cases, and data integrity. The command must be robust and user-friendly, necessitating multiple focused subtasks for safe implementation." } ] } diff --git a/tasks/task_024.txt b/tasks/task_024.txt index aec6f810..16959934 100644 --- a/tasks/task_024.txt +++ b/tasks/task_024.txt @@ -58,6 +58,50 @@ Testing approach: - Test parameter validation (missing ID, invalid ID format) - Test error handling for non-existent task IDs - Test basic command flow with a mock task store + +## Updated Implementation Approach + +Based on code review findings, the implementation approach needs to be revised: + +1. Implement the command in `scripts/modules/commands.js` instead of creating a new file +2. Add command registration in the `registerCommands()` function (around line 482) +3. Follow existing command structure pattern: + ```javascript + programInstance + .command('generate-test') + .description('Generate test cases for a task using AI') + .option('-f, --file ', 'Path to the tasks file', 'tasks/tasks.json') + .option('-i, --id ', 'Task ID parameter') + .option('-p, --prompt ', 'Additional prompt context') + .option('-r, --research', 'Use research model') + .action(async (options) => { + // Implementation + }); + ``` + +4. Use the following utilities: + - `findProjectRoot()` for resolving project paths + - `findTaskById()` for retrieving task data + - `chalk` for formatted console output + +5. Implement error handling following the pattern: + ```javascript + try { + // Implementation + } catch (error) { + console.error(chalk.red(`Error generating test: ${error.message}`)); + if (error.details) { + console.error(chalk.red(error.details)); + } + process.exit(1); + } + ``` + +6. Required imports: + - chalk for colored output + - path for file path operations + - findProjectRoot and findTaskById from './utils.js' + ## 2. Implement AI prompt construction and FastMCP integration [pending] ### Dependencies: 24.1 @@ -76,6 +120,50 @@ Testing approach: - Test FastMCP integration with mocked responses - Test error handling for FastMCP failures - Test response processing with sample FastMCP outputs + +## AI Integration Implementation + +### AI Service Integration +- Use the unified AI service layer, not FastMCP directly +- Implement with `generateObjectService` from '../ai-services-unified.js' +- Define Zod schema for structured test generation output: + - testContent: Complete Jest test file content + - fileName: Suggested filename for the test file + - mockRequirements: External dependencies that need mocking + +### Prompt Construction +- Create system prompt defining AI's role as test generator +- Build user prompt with task context (ID, title, description, details) +- Include test strategy and subtasks context in the prompt +- Follow patterns from add-task.js for prompt structure + +### Task Analysis +- Retrieve task data using `findTaskById()` from utils.js +- Build context by analyzing task description, details, and testStrategy +- Examine project structure for import patterns +- Parse specific testing requirements from task.testStrategy field + +### File System Operations +- Determine output path in same directory as tasks.json +- Generate standardized filename based on task ID +- Use fs.writeFileSync for writing test content to file + +### Error Handling & UI +- Implement try/catch blocks for AI service calls +- Display user-friendly error messages with chalk +- Use loading indicators during AI processing +- Support both research and main AI models + +### Telemetry +- Pass through telemetryData from AI service response +- Display AI usage summary for CLI output + +### Required Dependencies +- generateObjectService from ai-services-unified.js +- UI components (loading indicators, display functions) +- Zod for schema validation +- Chalk for formatted console output + ## 3. Implement test file generation and output [pending] ### Dependencies: 24.2 @@ -97,4 +185,419 @@ Testing approach: - Test file system operations with mocked fs module - Test the complete flow from command input to file output - Verify generated tests can be executed by Jest + +## Detailed Implementation Guidelines + +### File Naming Convention Implementation +```javascript +function generateTestFileName(taskId, isSubtask = false) { + if (isSubtask) { + // For subtasks like "24.1", generate "task_024_001.test.js" + const [parentId, subtaskId] = taskId.split('.'); + return `task_${parentId.padStart(3, '0')}_${subtaskId.padStart(3, '0')}.test.js`; + } else { + // For parent tasks like "24", generate "task_024.test.js" + return `task_${taskId.toString().padStart(3, '0')}.test.js`; + } +} +``` + +### File Location Strategy +- Place generated test files in the `tasks/` directory alongside task files +- This ensures co-location with task documentation and simplifies implementation + +### File Content Structure Template +```javascript +/** + * Test file for Task ${taskId}: ${taskTitle} + * Generated automatically by Task Master + */ + +import { jest } from '@jest/globals'; +// Additional imports based on task requirements + +describe('Task ${taskId}: ${taskTitle}', () => { + beforeEach(() => { + // Setup code + }); + + afterEach(() => { + // Cleanup code + }); + + test('should ${testDescription}', () => { + // Test implementation + }); +}); +``` + +### Code Formatting Standards +- Follow project's .prettierrc configuration: + - Tab width: 2 spaces (useTabs: true) + - Print width: 80 characters + - Semicolons: Required (semi: true) + - Quotes: Single quotes (singleQuote: true) + - Trailing commas: None (trailingComma: "none") + - Bracket spacing: True + - Arrow parens: Always + +### File System Operations Implementation +```javascript +import fs from 'fs'; +import path from 'path'; + +// Determine output path +const tasksDir = path.dirname(tasksPath); // Same directory as tasks.json +const fileName = generateTestFileName(task.id, isSubtask); +const filePath = path.join(tasksDir, fileName); + +// Ensure directory exists +if (!fs.existsSync(tasksDir)) { + fs.mkdirSync(tasksDir, { recursive: true }); +} + +// Write test file with proper error handling +try { + fs.writeFileSync(filePath, formattedTestContent, 'utf8'); +} catch (error) { + throw new Error(`Failed to write test file: ${error.message}`); +} +``` + +### Error Handling for File Operations +```javascript +try { + // File writing operation + fs.writeFileSync(filePath, testContent, 'utf8'); +} catch (error) { + if (error.code === 'ENOENT') { + throw new Error(`Directory does not exist: ${path.dirname(filePath)}`); + } else if (error.code === 'EACCES') { + throw new Error(`Permission denied writing to: ${filePath}`); + } else if (error.code === 'ENOSPC') { + throw new Error('Insufficient disk space to write test file'); + } else { + throw new Error(`Failed to write test file: ${error.message}`); + } +} +``` + +### User Feedback Implementation +```javascript +// Success feedback +console.log(chalk.green('✅ Test file generated successfully:')); +console.log(chalk.cyan(` File: ${fileName}`)); +console.log(chalk.cyan(` Location: ${filePath}`)); +console.log(chalk.gray(` Size: ${testContent.length} characters`)); + +// Additional info +if (mockRequirements && mockRequirements.length > 0) { + console.log(chalk.yellow(` Mocks needed: ${mockRequirements.join(', ')}`)); +} +``` + +### Content Validation Requirements +1. Jest Syntax Validation: + - Ensure proper describe/test structure + - Validate import statements + - Check for balanced brackets and parentheses + +2. Code Quality Checks: + - Verify no syntax errors + - Ensure proper indentation + - Check for required imports + +3. Test Completeness: + - At least one test case + - Proper test descriptions + - Appropriate assertions + +### Required Dependencies +```javascript +import fs from 'fs'; +import path from 'path'; +import chalk from 'chalk'; +import { log } from '../utils.js'; +``` + +### Integration with Existing Patterns +Follow the pattern from `generate-task-files.js`: +1. Read task data using existing utilities +2. Process content with proper formatting +3. Write files with error handling +4. Provide feedback to user +5. Return success data for MCP integration + + +## Corrected Implementation Approach + +### Updated File Location Strategy + +**CORRECTION**: Tests should go in `/tests/` directory, not `/tasks/` directory. + +Based on Jest configuration analysis: +- Jest is configured with `roots: ['/tests']` +- Test pattern: `**/?(*.)+(spec|test).js` +- Current test structure has `/tests/unit/`, `/tests/integration/`, etc. + +### Recommended Directory Structure: +``` +tests/ +├── unit/ # Manual unit tests +├── integration/ # Manual integration tests +├── generated/ # AI-generated tests +│ ├── tasks/ # Generated task tests +│ │ ├── task_024.test.js +│ │ └── task_024_001.test.js +│ └── README.md # Explains generated tests +└── fixtures/ # Test fixtures +``` + +### Updated File Path Logic: +```javascript +// Determine output path - place in tests/generated/tasks/ +const projectRoot = findProjectRoot() || '.'; +const testsDir = path.join(projectRoot, 'tests', 'generated', 'tasks'); +const fileName = generateTestFileName(task.id, isSubtask); +const filePath = path.join(testsDir, fileName); + +// Ensure directory structure exists +if (!fs.existsSync(testsDir)) { + fs.mkdirSync(testsDir, { recursive: true }); +} +``` + +### Testing Framework Configuration + +The generate-test command should read the configured testing framework from `.taskmasterconfig`: + +```javascript +// Read testing framework from config +const config = getConfig(projectRoot); +const testingFramework = config.testingFramework || 'jest'; // Default to Jest + +// Generate different templates based on framework +switch (testingFramework) { + case 'jest': + return generateJestTest(task, context); + case 'mocha': + return generateMochaTest(task, context); + case 'vitest': + return generateVitestTest(task, context); + default: + throw new Error(`Unsupported testing framework: ${testingFramework}`); +} +``` + +### Framework-Specific Templates + +**Jest Template** (current): +```javascript +/** + * Test file for Task ${taskId}: ${taskTitle} + * Generated automatically by Task Master + */ + +import { jest } from '@jest/globals'; +// Task-specific imports + +describe('Task ${taskId}: ${taskTitle}', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + test('should ${testDescription}', () => { + // Test implementation + }); +}); +``` + +**Mocha Template**: +```javascript +/** + * Test file for Task ${taskId}: ${taskTitle} + * Generated automatically by Task Master + */ + +import { expect } from 'chai'; +import sinon from 'sinon'; +// Task-specific imports + +describe('Task ${taskId}: ${taskTitle}', () => { + beforeEach(() => { + sinon.restore(); + }); + + it('should ${testDescription}', () => { + // Test implementation + }); +}); +``` + +**Vitest Template**: +```javascript +/** + * Test file for Task ${taskId}: ${taskTitle} + * Generated automatically by Task Master + */ + +import { describe, test, expect, vi, beforeEach } from 'vitest'; +// Task-specific imports + +describe('Task ${taskId}: ${taskTitle}', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + test('should ${testDescription}', () => { + // Test implementation + }); +}); +``` + +### AI Prompt Enhancement for Mocking + +To address the mocking challenge, enhance the AI prompt with project context: + +```javascript +const systemPrompt = `You are an expert at generating comprehensive test files. When generating tests, pay special attention to mocking external dependencies correctly. + +CRITICAL MOCKING GUIDELINES: +1. Analyze the task requirements to identify external dependencies (APIs, databases, file system, etc.) +2. Mock external dependencies at the module level, not inline +3. Use the testing framework's mocking utilities (jest.mock(), sinon.stub(), vi.mock()) +4. Create realistic mock data that matches the expected API responses +5. Test both success and error scenarios for mocked dependencies +6. Ensure mocks are cleared between tests to prevent test pollution + +Testing Framework: ${testingFramework} +Project Structure: ${projectStructureContext} +`; +``` + +### Integration with Future Features + +This primitive command design enables: +1. **Automatic test generation**: `task-master add-task --with-test` +2. **Batch test generation**: `task-master generate-tests --all` +3. **Framework-agnostic**: Support multiple testing frameworks +4. **Smart mocking**: LLM analyzes dependencies and generates appropriate mocks + +### Updated Implementation Requirements: + +1. **Read testing framework** from `.taskmasterconfig` +2. **Create tests directory structure** if it doesn't exist +3. **Generate framework-specific templates** based on configuration +4. **Enhanced AI prompts** with mocking best practices +5. **Project structure analysis** for better import resolution +6. **Mock dependency detection** from task requirements + + +## 4. Implement MCP tool integration for generate-test command [pending] +### Dependencies: 24.3 +### Description: Create MCP server tool support for the generate-test command to enable integration with Claude Code and other MCP clients. +### Details: +Implementation steps: +1. Create direct function wrapper in mcp-server/src/core/direct-functions/ +2. Create MCP tool registration in mcp-server/src/tools/ +3. Add tool to the main tools index +4. Implement proper parameter validation and error handling +5. Ensure telemetry data is properly passed through +6. Add tool to MCP server registration + +The MCP tool should support the same parameters as the CLI command: +- id: Task ID to generate tests for +- file: Path to tasks.json file +- research: Whether to use research model +- prompt: Additional context for test generation + +Follow the existing pattern from other MCP tools like add-task.js and expand-task.js. + +## 5. Add testing framework configuration to project initialization [pending] +### Dependencies: 24.3 +### Description: Enhance the init.js process to let users choose their preferred testing framework (Jest, Mocha, Vitest, etc.) and store this choice in .taskmasterconfig for use by the generate-test command. +### Details: +Implementation requirements: + +1. **Add Testing Framework Prompt to init.js**: + - Add interactive prompt asking users to choose testing framework + - Support Jest (default), Mocha + Chai, Vitest, Ava, Jasmine + - Include brief descriptions of each framework + - Allow --testing-framework flag for non-interactive mode + +2. **Update .taskmasterconfig Template**: + - Add testingFramework field to configuration file + - Include default dependencies for each framework + - Store framework-specific configuration options + +3. **Framework-Specific Setup**: + - Generate appropriate config files (jest.config.js, vitest.config.ts, etc.) + - Add framework dependencies to package.json suggestions + - Create sample test file for the chosen framework + +4. **Integration Points**: + - Ensure generate-test command reads testingFramework from config + - Add validation to prevent conflicts between framework choices + - Support switching frameworks later via models command or separate config command + +This makes the generate-test command truly framework-agnostic and sets up the foundation for --with-test flags in other commands. + +# Implementation Plan for Testing Framework Integration + +## Code Structure + +### 1. Update init.js +- Add testing framework prompt after addAliases prompt +- Implement framework selection with descriptions +- Support non-interactive mode with --testing-framework flag +- Create setupTestingFramework() function to handle framework-specific setup + +### 2. Create New Module Files +- Create `scripts/modules/testing-frameworks.js` for framework templates and setup +- Add sample test generators for each supported framework +- Implement config file generation for each framework + +### 3. Update Configuration Templates +- Modify `assets/.taskmasterconfig` to include testing fields: + ```json + "testingFramework": "{{testingFramework}}", + "testingConfig": { + "framework": "{{testingFramework}}", + "setupFiles": [], + "testDirectory": "tests", + "testPattern": "**/*.test.js", + "coverage": { + "enabled": false, + "threshold": 80 + } + } + ``` + +### 4. Create Framework-Specific Templates +- `assets/jest.config.template.js` +- `assets/vitest.config.template.ts` +- `assets/.mocharc.template.json` +- `assets/ava.config.template.js` +- `assets/jasmine.json.template` + +### 5. Update commands.js +- Add `--testing-framework ` option to init command +- Add validation for supported frameworks + +## Error Handling +- Validate selected framework against supported list +- Handle existing config files gracefully with warning/overwrite prompt +- Provide recovery options if framework setup fails +- Add conflict detection for multiple testing frameworks + +## Integration Points +- Ensure generate-test command reads testingFramework from config +- Prepare for future --with-test flag in other commands +- Support framework switching via config command + +## Testing Requirements +- Unit tests for framework selection logic +- Integration tests for config file generation +- Validation tests for each supported framework + diff --git a/tasks/task_041.txt b/tasks/task_041.txt index 80e86698..7c958b0f 100644 --- a/tasks/task_041.txt +++ b/tasks/task_041.txt @@ -77,48 +77,263 @@ This implementation should include: ### Description: Design and implement the command-line interface for the dependency graph tool, including argument parsing and help documentation. ### Details: Define commands for input file specification, output options, filtering, and other user-configurable parameters. + +Implement a new 'diagram' command (with 'graph' alias) in commands.js following the Commander.js pattern. The command should: + +1. Import diagram-generator.js module functions for generating visual representations +2. Support multiple visualization types with --type option: + - dependencies: show task dependency relationships + - subtasks: show task/subtask hierarchy + - flow: show task workflow + - gantt: show timeline visualization + +3. Include the following options: + - --task : Filter diagram to show only specified task and its relationships + - --mermaid: Output raw Mermaid markdown for external rendering + - --visual: Render diagram directly in terminal + - --format : Output format (text, svg, png) + +4. Implement proper error handling and validation: + - Validate task IDs using existing taskExists() function + - Handle invalid option combinations + - Provide descriptive error messages + +5. Integrate with UI components: + - Use ui.js display functions for consistent output formatting + - Apply chalk coloring for terminal output + - Use boxen formatting consistent with other commands + +6. Handle file operations: + - Resolve file paths using findProjectRoot() pattern + - Support saving diagrams to files when appropriate + +7. Include comprehensive help text following the established pattern in other commands + ## 2. Graph Layout Algorithms [pending] ### Dependencies: 41.1 ### Description: Develop or integrate algorithms to compute optimal node and edge placement for clear and readable graph layouts in a terminal environment. ### Details: Consider topological sorting, hierarchical, and force-directed layouts suitable for ASCII/Unicode rendering. + +Create a new diagram-generator.js module in the scripts/modules/ directory following Task Master's module architecture pattern. The module should include: + +1. Core functions for generating Mermaid diagrams: + - generateDependencyGraph(tasks, options) - creates flowchart showing task dependencies + - generateSubtaskDiagram(task, options) - creates hierarchy diagram for subtasks + - generateProjectFlow(tasks, options) - creates overall project workflow + - generateGanttChart(tasks, options) - creates timeline visualization + +2. Integration with existing Task Master data structures: + - Use the same task object format from task-manager.js + - Leverage dependency analysis from dependency-manager.js + - Support complexity scores from analyze-complexity functionality + - Handle both main tasks and subtasks with proper ID notation (parentId.subtaskId) + +3. Layout algorithm considerations for Mermaid: + - Topological sorting for dependency flows + - Hierarchical layouts for subtask trees + - Circular dependency detection and highlighting + - Terminal width-aware formatting for ASCII fallback + +4. Export functions following the existing module pattern at the bottom of the file + ## 3. ASCII/Unicode Rendering Engine [pending] ### Dependencies: 41.2 ### Description: Implement rendering logic to display the dependency graph using ASCII and Unicode characters in the terminal. ### Details: Support for various node and edge styles, and ensure compatibility with different terminal types. + +Extend ui.js with diagram display functions that integrate with Task Master's existing UI patterns: + +1. Implement core diagram display functions: + - displayTaskDiagram(tasksPath, diagramType, options) as the main entry point + - displayMermaidCode(mermaidCode, title) for formatted code output with boxen + - displayDiagramLegend() to explain symbols and colors + +2. Ensure UI consistency by: + - Using established chalk color schemes (blue/green/yellow/red) + - Applying boxen for consistent component formatting + - Following existing display function patterns (displayTaskById, displayComplexityReport) + - Utilizing cli-table3 for any diagram metadata tables + +3. Address terminal rendering challenges: + - Implement ASCII/Unicode fallback when Mermaid rendering isn't available + - Respect terminal width constraints using process.stdout.columns + - Integrate with loading indicators via startLoadingIndicator/stopLoadingIndicator + +4. Update task file generation to include Mermaid diagram sections in individual task files + +5. Support both CLI and MCP output formats through the outputFormat parameter + ## 4. Color Coding Support [pending] ### Dependencies: 41.3 ### Description: Add color coding to nodes and edges to visually distinguish types, statuses, or other attributes in the graph. ### Details: Use ANSI escape codes for color; provide options for colorblind-friendly palettes. + +Integrate color coding with Task Master's existing status system: + +1. Extend getStatusWithColor() in ui.js to support diagram contexts: + - Add 'diagram' parameter to determine rendering context + - Modify color intensity for better visibility in graph elements + +2. Implement Task Master's established color scheme using ANSI codes: + - Green (\x1b[32m) for 'done'/'completed' tasks + - Yellow (\x1b[33m) for 'pending' tasks + - Orange (\x1b[38;5;208m) for 'in-progress' tasks + - Red (\x1b[31m) for 'blocked' tasks + - Gray (\x1b[90m) for 'deferred'/'cancelled' tasks + - Magenta (\x1b[35m) for 'review' tasks + +3. Create diagram-specific color functions: + - getDependencyLineColor(fromTaskStatus, toTaskStatus) - color dependency arrows based on relationship status + - getNodeBorderColor(task) - style node borders using priority/complexity indicators + - getSubtaskGroupColor(parentTask) - visually group related subtasks + +4. Integrate complexity visualization: + - Use getComplexityWithColor() for node background or border thickness + - Map complexity scores to visual weight in the graph + +5. Ensure accessibility: + - Add text-based indicators (symbols like ✓, ⚠, ⏳) alongside colors + - Implement colorblind-friendly palettes as user-selectable option + - Include shape variations for different statuses + +6. Follow existing ANSI patterns: + - Maintain consistency with terminal UI color usage + - Reuse color constants from the codebase + +7. Support graceful degradation: + - Check terminal capabilities using existing detection + - Provide monochrome fallbacks with distinctive patterns + - Use bold/underline as alternatives when colors unavailable + ## 5. Circular Dependency Detection [pending] ### Dependencies: 41.2 ### Description: Implement algorithms to detect and highlight circular dependencies within the graph. ### Details: Clearly mark cycles in the rendered output and provide warnings or errors as appropriate. + +Integrate with Task Master's existing circular dependency detection: + +1. Import the dependency detection logic from dependency-manager.js module +2. Utilize the findCycles function from utils.js or dependency-manager.js +3. Extend validateDependenciesCommand functionality to highlight cycles in diagrams + +Visual representation in Mermaid diagrams: +- Apply red/bold styling to nodes involved in dependency cycles +- Add warning annotations to cyclic edges +- Implement cycle path highlighting with distinctive line styles + +Integration with validation workflow: +- Execute dependency validation before diagram generation +- Display cycle warnings consistent with existing CLI error messaging +- Utilize chalk.red and boxen for error highlighting following established patterns + +Add diagram legend entries that explain cycle notation and warnings + +Ensure detection of cycles in both: +- Main task dependencies +- Subtask dependencies within parent tasks + +Follow Task Master's error handling patterns for graceful cycle reporting and user notification + ## 6. Filtering and Search Functionality [pending] ### Dependencies: 41.1, 41.2 ### Description: Enable users to filter nodes and edges by criteria such as name, type, or dependency depth. ### Details: Support command-line flags for filtering and interactive search if feasible. + +Implement MCP tool integration for task dependency visualization: + +1. Create task_diagram.js in mcp-server/src/tools/ following existing tool patterns +2. Implement taskDiagramDirect.js in mcp-server/src/core/direct-functions/ +3. Use Zod schema for parameter validation: + - diagramType (dependencies, subtasks, flow, gantt) + - taskId (optional string) + - format (mermaid, text, json) + - includeComplexity (boolean) + +4. Structure response data with: + - mermaidCode for client-side rendering + - metadata (nodeCount, edgeCount, cycleWarnings) + - support for both task-specific and project-wide diagrams + +5. Integrate with session management and project root handling +6. Implement error handling using handleApiResult pattern +7. Register the tool in tools/index.js + +Maintain compatibility with existing command-line flags for filtering and interactive search. + ## 7. Accessibility Features [pending] ### Dependencies: 41.3, 41.4 ### Description: Ensure the tool is accessible, including support for screen readers, high-contrast modes, and keyboard navigation. ### Details: Provide alternative text output and ensure color is not the sole means of conveying information. + +# Accessibility and Export Integration + +## Accessibility Features +- Provide alternative text output for visual elements +- Ensure color is not the sole means of conveying information +- Support keyboard navigation through the dependency graph +- Add screen reader compatible node descriptions + +## Export Integration +- Extend generateTaskFiles function in task-manager.js to include Mermaid diagram sections +- Add Mermaid code blocks to task markdown files under ## Diagrams header +- Follow existing task file generation patterns and markdown structure +- Support multiple diagram types per task file: + * Task dependencies (prerequisite relationships) + * Subtask hierarchy visualization + * Task flow context in project workflow +- Integrate with existing fs module file writing operations +- Add diagram export options to the generate command in commands.js +- Support SVG and PNG export using Mermaid CLI when available +- Implement error handling for diagram generation failures +- Reference exported diagrams in task markdown with proper paths +- Update CLI generate command with options like --include-diagrams + ## 8. Performance Optimization [pending] ### Dependencies: 41.2, 41.3, 41.4, 41.5, 41.6 ### Description: Profile and optimize the tool for large graphs to ensure responsive rendering and low memory usage. ### Details: Implement lazy loading, efficient data structures, and parallel processing where appropriate. + +# Mermaid Library Integration and Terminal-Specific Handling + +## Package Dependencies +- Add mermaid package as an optional dependency in package.json for generating raw Mermaid diagram code +- Consider mermaid-cli for SVG/PNG conversion capabilities +- Evaluate terminal-image or similar libraries for terminals with image support +- Explore ascii-art-ansi or box-drawing character libraries for text-only terminals + +## Terminal Capability Detection +- Leverage existing terminal detection from ui.js to assess rendering capabilities +- Implement detection for: + - iTerm2 and other terminals with image protocol support + - Terminals with Unicode/extended character support + - Basic terminals requiring pure ASCII output + +## Rendering Strategy with Fallbacks +1. Primary: Generate raw Mermaid code for user copy/paste +2. Secondary: Render simplified ASCII tree/flow representation using box characters +3. Tertiary: Present dependencies in tabular format for minimal terminals + +## Implementation Approach +- Use dynamic imports for optional rendering libraries to maintain lightweight core +- Implement graceful degradation when optional packages aren't available +- Follow Task Master's philosophy of minimal dependencies +- Ensure performance optimization through lazy loading where appropriate +- Design modular rendering components that can be swapped based on terminal capabilities + ## 9. Documentation [pending] ### Dependencies: 41.1, 41.2, 41.3, 41.4, 41.5, 41.6, 41.7, 41.8 @@ -131,4 +346,28 @@ Include examples, troubleshooting, and contribution guidelines. ### Description: Develop automated tests for all major features, including CLI parsing, layout correctness, rendering, color coding, filtering, and cycle detection. ### Details: Include unit, integration, and regression tests; validate accessibility and performance claims. + +# Documentation Tasks for Visual Task Dependency Graph + +## User Documentation +1. Update README.md with diagram command documentation following existing command reference format +2. Add examples to CLI command help text in commands.js matching patterns from other commands +3. Create docs/diagrams.md with detailed usage guide including: + - Command examples for each diagram type + - Mermaid code samples and output + - Terminal compatibility notes + - Integration with task workflow examples + - Troubleshooting section for common diagram rendering issues + - Accessibility features and terminal fallback options + +## Developer Documentation +1. Update MCP tool documentation to include the new task_diagram tool +2. Add JSDoc comments to all new functions following existing code standards +3. Create contributor documentation for extending diagram types +4. Update API documentation for any new MCP interface endpoints + +## Integration Documentation +1. Document integration with existing commands (analyze-complexity, generate, etc.) +2. Provide examples showing how diagrams complement other Task Master features + diff --git a/tasks/task_051.txt b/tasks/task_051.txt index 3ba70e12..24703339 100644 --- a/tasks/task_051.txt +++ b/tasks/task_051.txt @@ -3,56 +3,89 @@ # Status: pending # Dependencies: None # Priority: medium -# Description: Create a command that allows users to quickly research topics using Perplexity AI, with options to include task context or custom prompts. +# Description: Create an interactive REPL-style chat interface for AI-powered research that maintains conversation context, integrates project information, and provides session management capabilities. # Details: -Develop a new command called 'research' that integrates with Perplexity AI's API to fetch information on specified topics. The command should: +Develop an interactive REPL-style chat interface for AI-powered research that allows users to have ongoing research conversations with context awareness. The system should: -1. Accept the following parameters: - - A search query string (required) - - A task or subtask ID for context (optional) - - A custom prompt to guide the research (optional) +1. Create an interactive REPL using inquirer that: + - Maintains conversation history and context + - Provides a natural chat-like experience + - Supports special commands with the '/' prefix -2. When a task/subtask ID is provided, extract relevant information from it to enrich the research query with context. +2. Integrate with the existing ai-services-unified.js using research mode: + - Leverage our unified AI service architecture + - Configure appropriate system prompts for research context + - Handle streaming responses for real-time feedback -3. Implement proper API integration with Perplexity, including authentication and rate limiting handling. +3. Support multiple context sources: + - Task/subtask IDs for project context + - File paths for code or document context + - Custom prompts for specific research directions + - Project file tree for system context -4. Format and display the research results in a readable format in the terminal, with options to: - - Save the results to a file - - Copy results to clipboard - - Generate a summary of key points +4. Implement chat commands including: + - `/save` - Save conversation to file + - `/task` - Associate with or load context from a task + - `/help` - Show available commands and usage + - `/exit` - End the research session + - `/copy` - Copy last response to clipboard + - `/summary` - Generate summary of conversation + - `/detail` - Adjust research depth level -5. Cache research results to avoid redundant API calls for the same queries. +5. Create session management capabilities: + - Generate and track unique session IDs + - Save/load sessions automatically + - Browse and switch between previous sessions + - Export sessions to portable formats -6. Provide a configuration option to set the depth/detail level of research (quick overview vs. comprehensive). +6. Design a consistent UI using ui.js patterns: + - Color-coded messages for user/AI distinction + - Support for markdown rendering in terminal + - Progressive display of AI responses + - Clear visual hierarchy and readability -7. Handle errors gracefully, especially network issues or API limitations. +7. Follow the "taskmaster way": + - Create something new and exciting + - Focus on usefulness and practicality + - Avoid over-engineering + - Maintain consistency with existing patterns -The command should follow the existing CLI structure and maintain consistency with other commands in the system. +The REPL should feel like a natural conversation while providing powerful research capabilities that integrate seamlessly with the rest of the system. # Test Strategy: 1. Unit tests: - - Test the command with various combinations of parameters (query only, query+task, query+custom prompt, all parameters) - - Mock the Perplexity API responses to test different scenarios (successful response, error response, rate limiting) - - Verify that task context is correctly extracted and incorporated into the research query + - Test the REPL command parsing and execution + - Mock AI service responses to test different scenarios + - Verify context extraction and integration from various sources + - Test session serialization and deserialization 2. Integration tests: - - Test actual API calls to Perplexity with valid credentials (using a test account) - - Verify the caching mechanism works correctly for repeated queries - - Test error handling with intentionally invalid requests + - Test actual AI service integration with the REPL + - Verify session persistence across application restarts + - Test conversation state management with long interactions + - Verify context switching between different tasks and files 3. User acceptance testing: - - Have team members use the command for real research needs and provide feedback - - Verify the command works in different network environments - - Test the command with very long queries and responses + - Have team members use the REPL for real research needs + - Test the conversation flow and command usability + - Verify the UI is intuitive and responsive + - Test with various terminal sizes and environments 4. Performance testing: - Measure and optimize response time for queries - - Test behavior under poor network conditions + - Test behavior with large conversation histories + - Verify performance with complex context sources + - Test under poor network conditions -Validate that the research results are properly formatted, readable, and that all output options (save, copy) function correctly. +5. Specific test scenarios: + - Verify markdown rendering for complex formatting + - Test streaming display with various response lengths + - Verify export features create properly formatted files + - Test session recovery from simulated crashes + - Validate handling of special characters and unicode # Subtasks: -## 1. Create Perplexity API Client Service [pending] +## 1. Create Perplexity API Client Service [cancelled] ### Dependencies: None ### Description: Develop a service module that handles all interactions with the Perplexity AI API, including authentication, request formatting, and response handling. ### Details: @@ -72,6 +105,9 @@ Testing approach: - Test error handling with simulated network failures - Verify caching mechanism works correctly - Test with various query types and options + +DEPRECATION NOTICE: This subtask is no longer needed and has been marked for removal. Instead of creating a new Perplexity service, we will leverage the existing ai-services-unified.js with research mode. This approach allows us to maintain a unified architecture for AI services rather than implementing a separate service specifically for Perplexity. + ## 2. Implement Task Context Extraction Logic [pending] ### Dependencies: None @@ -94,6 +130,37 @@ Testing approach: - Test with various task structures and content types - Verify error handling for missing or invalid tasks - Test the quality of extracted context with sample queries + +Updated Implementation Approach: + +REFACTORED IMPLEMENTATION: +1. Extract the fuzzy search logic from add-task.js (lines ~240-400) into `utils/contextExtractor.js` +2. Implement a reusable `TaskContextExtractor` class with the following methods: + - `extractTaskContext(taskId)` - Base context extraction + - `performFuzzySearch(query, options)` - Enhanced Fuse.js implementation + - `getRelevanceScore(task, query)` - Scoring mechanism from add-task.js + - `detectPurposeCategories(task)` - Category classification logic + - `findRelatedTasks(taskId)` - Identify dependencies and relationships + - `aggregateMultiQueryContext(queries)` - Support for multiple search terms + +3. Add configurable context depth levels: + - Minimal: Just task title and description + - Standard: Include details and immediate relationships + - Comprehensive: Full context with all dependencies and related tasks + +4. Implement context formatters: + - `formatForSystemPrompt(context)` - Structured for AI system instructions + - `formatForChatContext(context)` - Conversational format for chat + - `formatForResearchQuery(context, query)` - Optimized for research commands + +5. Add caching layer for performance optimization: + - Implement LRU cache for expensive fuzzy search results + - Cache invalidation on task updates + +6. Ensure backward compatibility with existing context extraction requirements + +This approach leverages our existing sophisticated search logic rather than rebuilding from scratch, while making it more flexible and reusable across the application. + ## 3. Build Research Command CLI Interface [pending] ### Dependencies: 51.1, 51.2 @@ -120,6 +187,40 @@ Testing approach: - Verify command validation logic works correctly - Test with various combinations of options - Ensure proper error messages for invalid inputs + +Implementation details: +1. Create a new module `repl/research-chat.js` for the interactive research experience +2. Implement REPL-style chat interface using inquirer with: + - Persistent conversation history management + - Context-aware prompting system + - Command parsing for special instructions +3. Implement REPL commands: + - `/save` - Save conversation to file + - `/task` - Associate with or load context from a task + - `/help` - Show available commands and usage + - `/exit` - End the research session + - `/copy` - Copy last response to clipboard + - `/summary` - Generate summary of conversation + - `/detail` - Adjust research depth level +4. Create context initialization system: + - Task/subtask context loading + - File content integration + - System prompt configuration +5. Integrate with ai-services-unified.js research mode +6. Implement conversation state management: + - Track message history + - Maintain context window + - Handle context pruning for long conversations +7. Design consistent UI patterns using ui.js library +8. Add entry point in main CLI application + +Testing approach: +- Test REPL command parsing and execution +- Verify context initialization with various inputs +- Test conversation state management +- Ensure proper error handling and recovery +- Validate UI consistency across different terminal environments + ## 4. Implement Results Processing and Output Formatting [pending] ### Dependencies: 51.1, 51.3 @@ -145,8 +246,45 @@ Testing approach: - Verify file saving functionality creates proper files with correct content - Test clipboard functionality - Verify summarization produces useful results + +Implementation details: +1. Create a new module `utils/chatFormatter.js` for REPL interface formatting +2. Implement terminal output formatting for conversational display: + - Color-coded messages distinguishing user inputs and AI responses + - Proper text wrapping and indentation for readability + - Support for markdown rendering in terminal + - Visual indicators for system messages and status updates +3. Implement streaming/progressive display of AI responses: + - Character-by-character or chunk-by-chunk display + - Cursor animations during response generation + - Ability to interrupt long responses +4. Design chat history visualization: + - Scrollable history with clear message boundaries + - Timestamp display options + - Session identification +5. Create specialized formatters for different content types: + - Code blocks with syntax highlighting + - Bulleted and numbered lists + - Tables and structured data + - Citations and references +6. Implement export functionality: + - Save conversations to markdown or text files + - Export individual responses + - Copy responses to clipboard +7. Adapt existing ui.js patterns for conversational context: + - Maintain consistent styling while supporting chat flow + - Handle multi-turn context appropriately -## 5. Implement Caching and Results Management System [pending] +Testing approach: +- Test streaming display with various response lengths and speeds +- Verify markdown rendering accuracy for complex formatting +- Test history navigation and scrolling functionality +- Verify export features create properly formatted files +- Test display on various terminal sizes and configurations +- Verify handling of special characters and unicode + + +## 5. Implement Caching and Results Management System [cancelled] ### Dependencies: 51.1, 51.4 ### Description: Create a persistent caching system for research results and implement functionality to manage, retrieve, and reference previous research. ### Details: @@ -173,4 +311,142 @@ Testing approach: - Test history management commands - Verify task association functionality - Test with large cache sizes to ensure performance + +Implementation details: +1. Create a session management system for the REPL experience: + - Generate and track unique session IDs + - Store conversation history with timestamps + - Maintain context and state between interactions +2. Implement session persistence: + - Save sessions to disk automatically + - Load previous sessions on startup + - Handle graceful recovery from crashes +3. Build session browser and selector: + - List available sessions with preview + - Filter sessions by date, topic, or content + - Enable quick switching between sessions +4. Implement conversation state serialization: + - Capture full conversation context + - Preserve user preferences per session + - Handle state migration during updates +5. Add session sharing capabilities: + - Export sessions to portable formats + - Import sessions from files + - Generate shareable links (if applicable) +6. Create session management commands: + - Create new sessions + - Clone existing sessions + - Archive or delete old sessions + +Testing approach: +- Verify session persistence across application restarts +- Test session recovery from simulated crashes +- Validate state serialization with complex conversations +- Ensure session switching maintains proper context +- Test session import/export functionality +- Verify performance with large conversation histories + + +## 6. Implement Project Context Generation [pending] +### Dependencies: 51.2 +### Description: Create functionality to generate and include project-level context such as file trees, repository structure, and codebase insights for more informed research. +### Details: +Implementation details: +1. Create a new module `utils/projectContextGenerator.js` for project-level context extraction +2. Implement file tree generation functionality: + - Scan project directory structure recursively + - Filter out irrelevant files (node_modules, .git, etc.) + - Format file tree for AI consumption + - Include file counts and structure statistics +3. Add code analysis capabilities: + - Extract key imports and dependencies + - Identify main modules and their relationships + - Generate high-level architecture overview +4. Implement context summarization: + - Create concise project overview + - Identify key technologies and patterns + - Summarize project purpose and structure +5. Add caching for expensive operations: + - Cache file tree with invalidation on changes + - Store analysis results with TTL +6. Create integration with research REPL: + - Add project context to system prompts + - Support `/project` command to refresh context + - Allow selective inclusion of project components + +Testing approach: +- Test file tree generation with various project structures +- Verify filtering logic works correctly +- Test context summarization quality +- Measure performance impact of context generation +- Verify caching mechanism effectiveness + +## 7. Create REPL Command System [pending] +### Dependencies: 51.3 +### Description: Implement a flexible command system for the research REPL that allows users to control the conversation flow, manage sessions, and access additional functionality. +### Details: +Implementation details: +1. Create a new module `repl/commands.js` for REPL command handling +2. Implement a command parser that: + - Detects commands starting with `/` + - Parses arguments and options + - Handles quoted strings and special characters +3. Create a command registry system: + - Register command handlers with descriptions + - Support command aliases + - Enable command discovery and help +4. Implement core commands: + - `/save [filename]` - Save conversation + - `/task ` - Load task context + - `/file ` - Include file content + - `/help [command]` - Show help + - `/exit` - End session + - `/copy [n]` - Copy nth response + - `/summary` - Generate conversation summary + - `/detail ` - Set detail level + - `/clear` - Clear conversation + - `/project` - Refresh project context + - `/session ` - Switch/create session +5. Add command completion and suggestions +6. Implement error handling for invalid commands +7. Create a help system with examples + +Testing approach: +- Test command parsing with various inputs +- Verify command execution and error handling +- Test command completion functionality +- Verify help system provides useful information +- Test with complex command sequences + +## 8. Integrate with AI Services Unified [pending] +### Dependencies: 51.3, 51.4 +### Description: Integrate the research REPL with the existing ai-services-unified.js to leverage the unified AI service architecture with research mode. +### Details: +Implementation details: +1. Update `repl/research-chat.js` to integrate with ai-services-unified.js +2. Configure research mode in AI service: + - Set appropriate system prompts + - Configure temperature and other parameters + - Enable streaming responses +3. Implement context management: + - Format conversation history for AI context + - Include task and project context + - Handle context window limitations +4. Add support for different research styles: + - Exploratory research with broader context + - Focused research with specific questions + - Comparative analysis between concepts +5. Implement response handling: + - Process streaming chunks + - Format and display responses + - Handle errors and retries +6. Add configuration options for AI service selection +7. Implement fallback mechanisms for service unavailability + +Testing approach: +- Test integration with mocked AI services +- Verify context formatting and management +- Test streaming response handling +- Verify error handling and recovery +- Test with various research styles and queries diff --git a/tasks/task_063.txt b/tasks/task_063.txt index 86bf3a5a..2268ee52 100644 --- a/tasks/task_063.txt +++ b/tasks/task_063.txt @@ -1,6 +1,6 @@ # Task ID: 63 # Title: Add pnpm Support for the Taskmaster Package -# Status: pending +# Status: done # Dependencies: None # Priority: medium # Description: Implement full support for pnpm as an alternative package manager in the Taskmaster application, ensuring users have the exact same experience as with npm when installing and managing the package. The installation process, including any CLI prompts or web interfaces, must serve the exact same content and user experience regardless of whether npm or pnpm is used. The project uses 'module' as the package type, defines binaries 'task-master' and 'task-master-mcp', and its core logic resides in 'scripts/modules/'. The 'init' command (via scripts/init.js) creates the directory structure (.cursor/rules, scripts, tasks), copies templates (.env.example, .gitignore, rule files, dev.js), manages package.json merging, and sets up MCP config (.cursor/mcp.json). All dependencies are standard npm dependencies listed in package.json, and manual modifications are being removed. @@ -88,49 +88,49 @@ This implementation should maintain full feature parity and identical user exper Success criteria: Taskmaster should install and function identically regardless of whether it was installed via npm or pnpm, with no degradation in functionality, performance, or user experience. All binaries should be properly linked, and the directory structure should be correctly created. # Subtasks: -## 1. Update Documentation for pnpm Support [pending] +## 1. Update Documentation for pnpm Support [done] ### Dependencies: None ### Description: Revise installation and usage documentation to include pnpm commands and instructions for installing and managing Taskmaster with pnpm. Clearly state that the installation process, including any website or UI shown, is identical to npm. Ensure documentation reflects the use of 'module' package type, binaries, and the init process as defined in scripts/init.js. ### Details: Add pnpm installation commands (e.g., `pnpm add taskmaster`) and update all relevant sections in the README and official docs to reflect pnpm as a supported package manager. Document that any installation website or prompt is the same as with npm. Include notes on the 'module' package type, binaries, and the directory/template setup performed by scripts/init.js. -## 2. Ensure Package Scripts Compatibility with pnpm [pending] +## 2. Ensure Package Scripts Compatibility with pnpm [done] ### Dependencies: 63.1 ### Description: Review and update package.json scripts to ensure they work seamlessly with pnpm's execution model. Confirm that any scripts responsible for showing a website or prompt during install behave identically with pnpm and npm. Ensure compatibility with 'module' package type and correct binary definitions. ### Details: Test all scripts using `pnpm run