Compare commits

..

1 Commits

Author SHA1 Message Date
Eyal Toledano
465ae252f0 refactor(mcp): Enforce projectRoot and centralize path validation
This commit refactors how project paths are handled in MCP direct functions to improve reliability, particularly when session context is incomplete or missing.

Key changes: 1) Made projectRoot required in MCP tools. 2) Refactored findTasksJsonPath to return {tasksPath, validatedProjectRoot}. 3) Updated all direct functions to pass session to findTasksJsonPath. 4) Updated analyzeTaskComplexityDirect to use the validated root for output path resolution.

This ensures operations relying on project context receive an explicitly provided and validated project root directory, resolving errors caused by incorrect path resolution.
2025-04-11 03:44:27 -04:00
70 changed files with 9640 additions and 9895 deletions

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': patch
---
- Fix expand-all command bugs that caused NaN errors with --all option and JSON formatting errors with research enabled. Improved error handling to provide clear feedback when subtask generation fails, including task IDs and actionable suggestions.

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': patch
---
Ensures add-task also has manual creation flags like --title/-t, --description/-d etc.

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Add CI for testing

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': patch
---
fix threshold parameter validation and testing for analyze-complexity.

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Fix github actions creating npm releases on next branch push

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': patch
---
Adjusts the taskmaster.mdc rules for init and parse-prd so the LLM correctly reaches for the next steps rather than trying to reinitialize or access tasks not yet created until PRD has been parsed."

View File

@@ -0,0 +1,11 @@
---
'task-master-ai': patch
---
Two improvements to MCP tools:
1. Adjusts the response sent to the MCP client for `initialize-project` tool so it includes an explicit `next_steps` object. This is in an effort to reduce variability in what the LLM chooses to do as soon as the confirmation of initialized project. Instead of arbitrarily looking for tasks, it will know that a PRD is required next and will steer the user towards that before reaching for the parse-prd command.
2. Updates the `parse_prd` tool parameter description to explicitly mention support for .md file formats, clarifying that users can provide PRD documents in various text formats including Markdown.
3. Updates the `parse_prd` tool `numTasks` param description to encourage the LLM agent to use a number of tasks to break down the PRD into that is logical relative to project complexity.

View File

@@ -0,0 +1,332 @@
---
"task-master-ai": patch
---
- **Major Usability & Stability Enhancements:**
- Taskmaster can now be seamlessly used either via the globally installed `task-master` CLI (npm package) or directly via the MCP server (e.g., within Cursor). Onboarding/initialization is supported through both methods.
- MCP implementation is now complete and stable, making it the preferred method for integrated environments.
- **Bug Fixes & Reliability:**
- Fixed MCP server invocation issue in `mcp.json` shipped with `task-master init`.
- Resolved issues with CLI error messages for flags and unknown commands, added confirmation prompts for destructive actions (e.g., `remove-task`).
- Numerous other CLI and MCP tool bugs fixed across the suite (details may be in other changesets like `@all-parks-sort.md`).
- **Core Functionality & Commands:**
- Added complete `remove-task` functionality for permanent task deletion.
- Implemented `initialize_project` MCP tool for easier setup in integrated environments.
- Introduced AsyncOperationManager for handling long-running operations (e.g., `expand`, `analyze`) in the background via MCP, with status checking.
- **Interface & Configuration:**
- Renamed MCP tools for intuitive usage (`list-tasks``get-tasks`, `show-task``get-task`).
- Added binary alias `task-master-mcp-server`.
- Clarified environment configuration: `.env` for npm package, `.cursor/mcp.json` for MCP.
- Updated model configurations (context window, temperature, defaults) for improved performance/consistency.
- **Internal Refinements & Fixes:**
- Refactored AI tool patterns, implemented Logger Wrapper, fixed critical issues in `analyze-project-complexity`, `update-task`, `update-subtask`, `set-task-status`, `update`, `expand-task`, `parse-prd`, `expand-all`.
- Standardized and improved silent mode implementation across MCP tools to prevent JSON response issues.
- Improved parameter handling and project root detection for MCP tools.
- Centralized AI client utilities and refactored AI services.
- Optimized `get-task` MCP response payload.
- **Dependency & Licensing:**
- Removed dependency on non-existent package `@model-context-protocol/sdk`.
- Updated license to MIT + Commons Clause v1.0.
- **Documentation & UI:**
- Added comprehensive `taskmaster.mdc` command/tool reference and other rule updates (specific rule adjustments may be in other changesets like `@silly-horses-grin.md`).
- Enhanced CLI progress bars and status displays. Added "cancelled" status.
- Updated README, added tutorial/examples guide, supported client list documentation.
- Adjusts the MCP server invokation in the mcp.json we ship with `task-master init`. Fully functional now.
- Rename the npx -y command. It's now `npx -y task-master-ai task-master-mcp`
- Add additional binary alias: `task-master-mcp-server` pointing to the same MCP server script
- **Significant improvements to model configuration:**
- Increase context window from 64k to 128k tokens (MAX_TOKENS=128000) for handling larger codebases
- Reduce temperature from 0.4 to 0.2 for more consistent, deterministic outputs
- Set default model to "claude-3-7-sonnet-20250219" in configuration
- Update Perplexity model to "sonar-pro" for research operations
- Increase default subtasks generation from 4 to 5 for more granular task breakdown
- Set consistent default priority to "medium" for all new tasks
- **Clarify environment configuration approaches:**
- For direct MCP usage: Configure API keys directly in `.cursor/mcp.json`
- For npm package usage: Configure API keys in `.env` file
- Update templates with clearer placeholder values and formatting
- Provide explicit documentation about configuration methods in both environments
- Use consistent placeholder format "YOUR_ANTHROPIC_API_KEY_HERE" in mcp.json
- Rename MCP tools to better align with API conventions and natural language in client chat:
- Rename `list-tasks` to `get-tasks` for more intuitive client requests like "get my tasks"
- Rename `show-task` to `get-task` for consistency with GET-based API naming conventions
- **Refine AI-based MCP tool implementation patterns:**
- Establish clear responsibilities for direct functions vs MCP tools when handling AI operations
- Update MCP direct function signatures to expect `context = { session }` for AI-based tools, without `reportProgress`
- Clarify that AI client initialization, API calls, and response parsing should be handled within the direct function
- Define standard error codes for AI operations (`AI_CLIENT_ERROR`, `RESPONSE_PARSING_ERROR`, etc.)
- Document that `reportProgress` should not be used within direct functions due to client validation issues
- Establish that progress indication within direct functions should use standard logging (`log.info()`)
- Clarify that `AsyncOperationManager` should manage progress reporting at the MCP tool layer, not in direct functions
- Update `mcp.mdc` rule to reflect the refined patterns for AI-based MCP tools
- **Document and implement the Logger Wrapper Pattern:**
- Add comprehensive documentation in `mcp.mdc` and `utilities.mdc` on the Logger Wrapper Pattern
- Explain the dual purpose of the wrapper: preventing runtime errors and controlling output format
- Include implementation examples with detailed explanations of why and when to use this pattern
- Clearly document that this pattern has proven successful in resolving issues in multiple MCP tools
- Cross-reference between rule files to ensure consistent guidance
- **Fix critical issue in `analyze-project-complexity` MCP tool:**
- Implement proper logger wrapper in `analyzeTaskComplexityDirect` to fix `mcpLog[level] is not a function` errors
- Update direct function to handle both Perplexity and Claude AI properly for research-backed analysis
- Improve silent mode handling with proper wasSilent state tracking
- Add comprehensive error handling for AI client errors and report file parsing
- Ensure proper report format detection and analysis with fallbacks
- Fix variable name conflicts between the `report` logging function and data structures in `analyzeTaskComplexity`
- **Fix critical issue in `update-task` MCP tool:**
- Implement proper logger wrapper in `updateTaskByIdDirect` to ensure mcpLog[level] calls work correctly
- Update Zod schema in `update-task.js` to accept both string and number type IDs
- Fix silent mode implementation with proper try/finally blocks
- Add comprehensive error handling for missing parameters, invalid task IDs, and failed updates
- **Refactor `update-subtask` MCP tool to follow established patterns:**
- Update `updateSubtaskByIdDirect` function to accept `context = { session }` parameter
- Add proper AI client initialization with error handling for both Anthropic and Perplexity
- Implement the Logger Wrapper Pattern to prevent mcpLog[level] errors
- Support both string and number subtask IDs with appropriate validation
- Update MCP tool to pass session to direct function but not reportProgress
- Remove commented-out calls to reportProgress for cleaner code
- Add comprehensive error handling for various failure scenarios
- Implement proper silent mode with try/finally blocks
- Ensure detailed successful update response information
- **Fix issues in `set-task-status` MCP tool:**
- Remove reportProgress parameter as it's not needed
- Improve project root handling for better session awareness
- Reorganize function call arguments for setTaskStatusDirect
- Add proper silent mode handling with try/catch/finally blocks
- Enhance logging for both success and error cases
- **Refactor `update` MCP tool to follow established patterns:**
- Update `updateTasksDirect` function to accept `context = { session }` parameter
- Add proper AI client initialization with error handling
- Update MCP tool to pass session to direct function but not reportProgress
- Simplify parameter validation using string type for 'from' parameter
- Improve error handling for AI client errors
- Implement proper silent mode handling with try/finally blocks
- Use `isSilentMode()` function instead of accessing global variables directly
- **Refactor `expand-task` MCP tool to follow established patterns:**
- Update `expandTaskDirect` function to accept `context = { session }` parameter
- Add proper AI client initialization with error handling
- Update MCP tool to pass session to direct function but not reportProgress
- Add comprehensive tests for the refactored implementation
- Improve error handling for AI client errors
- Remove non-existent 'force' parameter from direct function implementation
- Ensure direct function parameters match core function parameters
- Implement proper silent mode handling with try/finally blocks
- Use `isSilentMode()` function instead of accessing global variables directly
- **Refactor `parse-prd` MCP tool to follow established patterns:**
- Update `parsePRDDirect` function to accept `context = { session }` parameter for proper AI initialization
- Implement AI client initialization with proper error handling using `getAnthropicClientForMCP`
- Add the Logger Wrapper Pattern to ensure proper logging via `mcpLog`
- Update the core `parsePRD` function to accept an AI client parameter
- Implement proper silent mode handling with try/finally blocks
- Remove `reportProgress` usage from MCP tool for better client compatibility
- Fix console output that was breaking the JSON response format
- Improve error handling with specific error codes
- Pass session object to the direct function correctly
- Update task-manager-core.js to export AI client utilities for better organization
- Ensure proper option passing between functions to maintain logging context
- **Update MCP Logger to respect silent mode:**
- Import and check `isSilentMode()` function in logger implementation
- Skip all logging when silent mode is enabled
- Prevent console output from interfering with JSON responses
- Fix "Unexpected token 'I', "[INFO] Gene"... is not valid JSON" errors by suppressing log output during silent mode
- **Refactor `expand-all` MCP tool to follow established patterns:**
- Update `expandAllTasksDirect` function to accept `context = { session }` parameter
- Add proper AI client initialization with error handling for research-backed expansion
- Pass session to direct function but not reportProgress in the MCP tool
- Implement directory switching to work around core function limitations
- Add comprehensive error handling with specific error codes
- Ensure proper restoration of working directory after execution
- Use try/finally pattern for both silent mode and directory management
- Add comprehensive tests for the refactored implementation
- **Standardize and improve silent mode implementation across MCP direct functions:**
- Add proper import of all silent mode utilities: `import { enableSilentMode, disableSilentMode, isSilentMode } from 'utils.js'`
- Replace direct access to global silentMode variable with `isSilentMode()` function calls
- Implement consistent try/finally pattern to ensure silent mode is always properly disabled
- Add error handling with finally blocks to prevent silent mode from remaining enabled after errors
- Create proper mixed parameter/global silent mode check pattern: `const isSilent = options.silentMode || (typeof options.silentMode === 'undefined' && isSilentMode())`
- Update all direct functions to follow the new implementation pattern
- Fix issues with silent mode not being properly disabled when errors occur
- **Improve parameter handling between direct functions and core functions:**
- Verify direct function parameters match core function signatures
- Remove extraction and use of parameters that don't exist in core functions (e.g., 'force')
- Implement appropriate type conversion for parameters (e.g., `parseInt(args.id, 10)`)
- Set defaults that match core function expectations
- Add detailed documentation on parameter matching in guidelines
- Add explicit examples of correct parameter handling patterns
- **Create standardized MCP direct function implementation checklist:**
- Comprehensive imports and dependencies section
- Parameter validation and matching guidelines
- Silent mode implementation best practices
- Error handling and response format patterns
- Path resolution and core function call guidelines
- Function export and testing verification steps
- Specific issues to watch for related to silent mode, parameters, and error cases
- Add checklist to subtasks for uniform implementation across all direct functions
- **Implement centralized AI client utilities for MCP tools:**
- Create new `ai-client-utils.js` module with standardized client initialization functions
- Implement session-aware AI client initialization for both Anthropic and Perplexity
- Add comprehensive error handling with user-friendly error messages
- Create intelligent AI model selection based on task requirements
- Implement model configuration utilities that respect session environment variables
- Add extensive unit tests for all utility functions
- Significantly improve MCP tool reliability for AI operations
- **Specific implementations include:**
- `getAnthropicClientForMCP`: Initializes Anthropic client with session environment variables
- `getPerplexityClientForMCP`: Initializes Perplexity client with session environment variables
- `getModelConfig`: Retrieves model parameters from session or fallbacks to defaults
- `getBestAvailableAIModel`: Selects the best available model based on requirements
- `handleClaudeError`: Processes Claude API errors into user-friendly messages
- **Updated direct functions to use centralized AI utilities:**
- Refactored `addTaskDirect` to use the new AI client utilities with proper AsyncOperationManager integration
- Implemented comprehensive error handling for API key validation, AI processing, and response parsing
- Added session-aware parameter handling with proper propagation of context to AI streaming functions
- Ensured proper fallback to process.env when session variables aren't available
- **Refine AI services for reusable operations:**
- Refactor `ai-services.js` to support consistent AI operations across CLI and MCP
- Implement shared helpers for streaming responses, prompt building, and response parsing
- Standardize client initialization patterns with proper session parameter handling
- Enhance error handling and loading indicator management
- Fix process exit issues to prevent MCP server termination on API errors
- Ensure proper resource cleanup in all execution paths
- Add comprehensive test coverage for AI service functions
- **Key improvements include:**
- Stream processing safety with explicit completion detection
- Standardized function parameter patterns
- Session-aware parameter extraction with sensible defaults
- Proper cleanup using try/catch/finally patterns
- **Optimize MCP response payloads:**
- Add custom `processTaskResponse` function to `get-task` MCP tool to filter out unnecessary `allTasks` array data
- Significantly reduce response size by returning only the specific requested task instead of all tasks
- Preserve dependency status relationships for the UI/CLI while keeping MCP responses lean and efficient
- **Implement complete remove-task functionality:**
- Add `removeTask` core function to permanently delete tasks or subtasks from tasks.json
- Implement CLI command `remove-task` with confirmation prompt and force flag support
- Create MCP `remove_task` tool for AI-assisted task removal
- Automatically handle dependency cleanup by removing references to deleted tasks
- Update task files after removal to maintain consistency
- Provide robust error handling and detailed feedback messages
- **Update Cursor rules and documentation:**
- Enhance `new_features.mdc` with comprehensive guidelines for implementing removal commands
- Update `commands.mdc` with best practices for confirmation flows and cleanup procedures
- Expand `mcp.mdc` with detailed instructions for MCP tool implementation patterns
- Add examples of proper error handling and parameter validation to all relevant rules
- Include new sections about handling dependencies during task removal operations
- Document naming conventions and implementation patterns for destructive operations
- Update silent mode implementation documentation with proper examples
- Add parameter handling guidelines emphasizing matching with core functions
- Update architecture documentation with dedicated section on silent mode implementation
- **Implement silent mode across all direct functions:**
- Add `enableSilentMode` and `disableSilentMode` utility imports to all direct function files
- Wrap all core function calls with silent mode to prevent console logs from interfering with JSON responses
- Add comprehensive error handling to ensure silent mode is disabled even when errors occur
- Fix "Unexpected token 'I', "[INFO] Gene"... is not valid JSON" errors by suppressing log output
- Apply consistent silent mode pattern across all MCP direct functions
- Maintain clean JSON responses for better integration with client tools
- **Implement AsyncOperationManager for background task processing:**
- Add new `async-manager.js` module to handle long-running operations asynchronously
- Support background execution of computationally intensive tasks like expansion and analysis
- Implement unique operation IDs with UUID generation for reliable tracking
- Add operation status tracking (pending, running, completed, failed)
- Create `get_operation_status` MCP tool to check on background task progress
- Forward progress reporting from background tasks to the client
- Implement operation history with automatic cleanup of completed operations
- Support proper error handling in background tasks with detailed status reporting
- Maintain context (log, session) for background operations ensuring consistent behavior
- **Implement initialize_project command:**
- Add new MCP tool to allow project setup via integrated MCP clients
- Create `initialize_project` direct function with proper parameter handling
- Improve onboarding experience by adding to mcp.json configuration
- Support project-specific metadata like name, description, and version
- Handle shell alias creation with proper confirmation
- Improve first-time user experience in AI environments
- **Refactor project root handling for MCP Server:**
- **Prioritize Session Roots**: MCP tools now extract the project root path directly from `session.roots[0].uri` provided by the client (e.g., Cursor).
- **New Utility `getProjectRootFromSession`**: Added to `mcp-server/src/tools/utils.js` to encapsulate session root extraction and decoding. **Further refined for more reliable detection, especially in integrated environments, including deriving root from script path and avoiding fallback to '/'.**
- **Simplify `findTasksJsonPath`**: The core path finding utility in `mcp-server/src/core/utils/path-utils.js` now prioritizes the `projectRoot` passed in `args` (originating from the session). Removed checks for `TASK_MASTER_PROJECT_ROOT` env var (we do not use this anymore) and package directory fallback. **Enhanced error handling to include detailed debug information (paths searched, CWD, server dir, etc.) and clearer potential solutions when `tasks.json` is not found.**
- **Retain CLI Fallbacks**: Kept `lastFoundProjectRoot` cache check and CWD search in `findTasksJsonPath` for compatibility with direct CLI usage.
- Updated all MCP tools to use the new project root handling:
- Tools now call `getProjectRootFromSession` to determine the root.
- This root is passed explicitly as `projectRoot` in the `args` object to the corresponding `*Direct` function.
- Direct functions continue to use the (now simplified) `findTasksJsonPath` to locate `tasks.json` within the provided root.
- This ensures tools work reliably in integrated environments without requiring the user to specify `--project-root`.
- Add comprehensive PROJECT_MARKERS array for detecting common project files (used in CLI fallback logic).
- Improved error messages with specific troubleshooting guidance.
- **Enhanced logging:**
- Indicate the source of project root selection more clearly.
- **Add verbose logging in `get-task.js` to trace session object content and resolved project root path, aiding debugging.**
- DRY refactoring by centralizing path utilities in `core/utils/path-utils.js` and session handling in `tools/utils.js`.
- Keep caching of `lastFoundProjectRoot` for CLI performance.
- Split monolithic task-master-core.js into separate function files within direct-functions directory.
- Implement update-task MCP command for updating a single task by ID.
- Implement update-subtask MCP command for appending information to specific subtasks.
- Implement generate MCP command for creating individual task files from tasks.json.
- Implement set-status MCP command for updating task status.
- Implement get-task MCP command for displaying detailed task information (renamed from show-task).
- Implement next-task MCP command for finding the next task to work on.
- Implement expand-task MCP command for breaking down tasks into subtasks.
- Implement add-task MCP command for creating new tasks using AI assistance.
- Implement add-subtask MCP command for adding subtasks to existing tasks.
- Implement remove-subtask MCP command for removing subtasks from parent tasks.
- Implement expand-all MCP command for expanding all tasks into subtasks.
- Implement analyze-complexity MCP command for analyzing task complexity.
- Implement clear-subtasks MCP command for clearing subtasks from parent tasks.
- Implement remove-dependency MCP command for removing dependencies from tasks.
- Implement validate-dependencies MCP command for checking validity of task dependencies.
- Implement fix-dependencies MCP command for automatically fixing invalid dependencies.
- Implement complexity-report MCP command for displaying task complexity analysis reports.
- Implement add-dependency MCP command for creating dependency relationships between tasks.
- Implement get-tasks MCP command for listing all tasks (renamed from list-tasks).
- Implement `initialize_project` MCP tool to allow project setup via MCP client and radically improve and simplify onboarding by adding to mcp.json (e.g., Cursor).
- Enhance documentation and tool descriptions:
- Create new `taskmaster.mdc` Cursor rule for comprehensive MCP tool and CLI command reference.
- Bundle taskmaster.mdc with npm package and include in project initialization.
- Add detailed descriptions for each tool's purpose, parameters, and common use cases.
- Include natural language patterns and keywords for better intent recognition.
- Document parameter descriptions with clear examples and default values.
- Add usage examples and context for each command/tool.
- **Update documentation (`mcp.mdc`, `utilities.mdc`, `architecture.mdc`, `new_features.mdc`, `commands.mdc`) to reflect the new session-based project root handling and the preferred MCP vs. CLI interaction model.**
- Improve clarity around project root auto-detection in tool documentation.
- Update tool descriptions to better reflect their actual behavior and capabilities.
- Add cross-references between related tools and commands.
- Include troubleshooting guidance in tool descriptions.
- **Add default values for `DEFAULT_SUBTASKS` and `DEFAULT_PRIORITY` to the example `.cursor/mcp.json` configuration.**
- Document MCP server naming conventions in architecture.mdc and mcp.mdc files (file names use kebab-case, direct functions use camelCase with Direct suffix, tool registration functions use camelCase with Tool suffix, and MCP tool names use snake_case).
- Update MCP tool naming to follow more intuitive conventions that better align with natural language requests in client chat applications.
- Enhance task show view with a color-coded progress bar for visualizing subtask completion percentage.
- Add "cancelled" status to UI module status configurations for marking tasks as cancelled without deletion.
- Improve MCP server resource documentation with comprehensive implementation examples and best practices.
- Enhance progress bars with status breakdown visualization showing proportional sections for different task statuses.
- Add improved status tracking for both tasks and subtasks with detailed counts by status.
- Optimize progress bar display with width constraints to prevent UI overflow on smaller terminals.
- Improve status counts display with clear text labels beside status icons for better readability.
- Treat deferred and cancelled tasks as effectively complete for progress calculation while maintaining visual distinction.
- **Fix `reportProgress` calls** to use the correct `{ progress, total? }` format.
- **Standardize logging in core task-manager functions (`expandTask`, `expandAllTasks`, `updateTasks`, `updateTaskById`, `updateSubtaskById`, `parsePRD`, `analyzeTaskComplexity`):**
- Implement a local `report` function in each to handle context-aware logging.
- Use `report` to choose between `mcpLog` (if available) and global `log` (from `utils.js`).
- Only call global `log` when `outputFormat` is 'text' and silent mode is off.
- Wrap CLI UI elements (tables, boxes, spinners) in `outputFormat === 'text'` checks.

View File

@@ -5,7 +5,7 @@ PERPLEXITY_API_KEY=your_perplexity_api_key_here # Format: pplx-...
# Model Configuration
MODEL=claude-3-7-sonnet-20250219 # Recommended models: claude-3-7-sonnet-20250219, claude-3-opus-20240229
PERPLEXITY_MODEL=sonar-pro # Perplexity model for research-backed subtasks
MAX_TOKENS=64000 # Maximum tokens for model responses
MAX_TOKENS=128000 # Maximum tokens for model responses
TEMPERATURE=0.2 # Temperature for model responses (0.0-1.0)
# Logging Configuration

View File

@@ -4,4 +4,3 @@ coverage
.changeset
tasks
package-lock.json
tests/fixture/*.json

View File

@@ -1,68 +1,5 @@
# task-master-ai
## 0.11.0
### Minor Changes
- [#71](https://github.com/eyaltoledano/claude-task-master/pull/71) [`7141062`](https://github.com/eyaltoledano/claude-task-master/commit/71410629ba187776d92a31ea0729b2ff341b5e38) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - - **Easier Ways to Use Taskmaster (CLI & MCP):**
- You can now use Taskmaster either by installing it as a standard command-line tool (`task-master`) or as an MCP server directly within integrated development tools like Cursor (using its built-in features). **This makes Taskmaster accessible regardless of your preferred workflow.**
- Setting up a new project is simpler in integrated tools, thanks to the new `initialize_project` capability.
- **Complete MCP Implementation:**
- NOTE: Many MCP clients charge on a per tool basis. In that regard, the most cost-efficient way to use Taskmaster is through the CLI directly. Otherwise, the MCP offers the smoothest and most recommended user experience.
- All MCP tools now follow a standardized output format that mimicks RESTful API responses. They are lean JSON responses that are context-efficient. This is a net improvement over the last version which sent the whole CLI output directly, which needlessly wasted tokens.
- Added a `remove-task` command to permanently delete tasks you no longer need.
- Many new MCP tools are available for managing tasks (updating details, adding/removing subtasks, generating task files, setting status, finding the next task, breaking down complex tasks, handling dependencies, analyzing complexity, etc.), usable both from the command line and integrated tools. **(See the `taskmaster.mdc` reference guide and improved readme for a full list).**
- **Better Task Tracking:**
- Added a "cancelled" status option for tasks, providing more ways to categorize work.
- **Smoother Experience in Integrated Tools:**
- Long-running operations (like breaking down tasks or analysis) now run in the background **via an Async Operation Manager** with progress updates, so you know what's happening without waiting and can check status later.
- **Improved Documentation:**
- Added a comprehensive reference guide (`taskmaster.mdc`) detailing all commands and tools with examples, usage tips, and troubleshooting info. This is mostly for use by the AI but can be useful for human users as well.
- Updated the main README with clearer instructions and added a new tutorial/examples guide.
- Added documentation listing supported integrated tools (like Cursor).
- **Increased Stability & Reliability:**
- Using Taskmaster within integrated tools (like Cursor) is now **more stable and the recommended approach.**
- Added automated testing (CI) to catch issues earlier, leading to a more reliable tool.
- Fixed release process issues to ensure users get the correct package versions when installing or updating via npm.
- **Better Command-Line Experience:**
- Fixed bugs in the `expand-all` command that could cause **NaN errors or JSON formatting issues (especially when using `--research`).**
- Fixed issues with parameter validation in the `analyze-complexity` command (specifically related to the `threshold` parameter).
- Made the `add-task` command more consistent by adding standard flags like `--title`, `--description` for manual task creation so you don't have to use `--prompt` and can quickly drop new ideas and stay in your flow.
- Improved error messages for incorrect commands or flags, making them easier to understand.
- Added confirmation warnings before permanently deleting tasks (`remove-task`) to prevent mistakes. There's a known bug for deleting multiple tasks with comma-separated values. It'll be fixed next release.
- Renamed some background tool names used by integrated tools (e.g., `list-tasks` is now `get_tasks`) to be more intuitive if seen in logs or AI interactions.
- Smoother project start: **Improved the guidance provided to AI assistants immediately after setup** (related to `init` and `parse-prd` steps). This ensures the AI doesn't go on a tangent deciding its own workflow, and follows the exact process outlined in the Taskmaster workflow.
- **Clearer Error Messages:**
- When generating subtasks fails, error messages are now clearer, **including specific task IDs and potential suggestions.**
- AI fallback from Claude to Perplexity now also works the other way around. If Perplexity is down, will switch to Claude.
- **Simplified Setup & Configuration:**
- Made it clearer how to configure API keys depending on whether you're using the command-line tool (`.env` file) or an integrated tool (`.cursor/mcp.json` file).
- Taskmaster is now better at automatically finding your project files, especially in integrated tools, reducing the need for manual path settings.
- Fixed an issue that could prevent Taskmaster from working correctly immediately after initialization in integrated tools (related to how the MCP server was invoked). This should solve the issue most users were experiencing with the last release (0.10.x)
- Updated setup templates with clearer examples for API keys.
- \*\*For advanced users setting up the MCP server manually, the command is now `npx -y task-master-ai task-master-mcp`.
- **Enhanced Performance & AI:**
- Updated underlying AI model settings:
- **Increased Context Window:** Can now handle larger projects/tasks due to an increased Claude context window (64k -> 128k tokens).
- **Reduced AI randomness:** More consistent and predictable AI outputs (temperature 0.4 -> 0.2).
- **Updated default AI models:** Uses newer models like `claude-3-7-sonnet-20250219` and Perplexity `sonar-pro` by default.
- **More granular breakdown:** Increased the default number of subtasks generated by `expand` to 5 (from 4).
- **Consistent defaults:** Set the default priority for new tasks consistently to "medium".
- Improved performance when viewing task details in integrated tools by sending less redundant data.
- **Documentation Clarity:**
- Clarified in documentation that Markdown files (`.md`) can be used for Product Requirements Documents (`parse_prd`).
- Improved the description for the `numTasks` option in `parse_prd` for better guidance.
- **Improved Visuals (CLI):**
- Enhanced the look and feel of progress bars and status updates in the command line.
- Added a helpful color-coded progress bar to the task details view (`show` command) to visualize subtask completion.
- Made progress bars show a breakdown of task statuses (e.g., how many are pending vs. done).
- Made status counts clearer with text labels next to icons.
- Prevented progress bars from messing up the display on smaller terminal windows.
- Adjusted how progress is calculated for 'deferred' and 'cancelled' tasks in the progress bar, while still showing their distinct status visually.
- **Fixes for Integrated Tools:**
- Fixed how progress updates are sent to integrated tools, ensuring they display correctly.
- Fixed internal issues that could cause errors or invalid JSON responses when using Taskmaster with integrated tools.
## 0.10.1
### Patch Changes

View File

@@ -146,7 +146,7 @@ To enable enhanced task management capabilities directly within Cursor using the
4. Configure with the following details:
- Name: "Task Master"
- Type: "Command"
- Command: "npx -y task-master-mcp"
- Command: "npx -y --package task-master-ai task-master-mcp"
5. Save the settings
Once configured, you can interact with Task Master's task management commands directly through Cursor's interface, providing a more integrated experience.

View File

@@ -20,26 +20,20 @@ A task management system for AI-driven development with Claude, designed to work
MCP (Model Control Protocol) provides the easiest way to get started with Task Master directly in your editor.
1. **Install the package**
```bash
npm i -g task-master-ai
```
2. **Add the MCP config to your editor** (Cursor recommended, but it works with other text editors):
1. **Add the MCP config to your editor** (Cursor recommended, but it works with other text editors):
```json
{
"mcpServers": {
"taskmaster-ai": {
"command": "npx",
"args": ["-y", "task-master-mcp"],
"args": ["-y", "--package", "task-master-ai", "task-master-mcp"],
"env": {
"ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE",
"PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE",
"MODEL": "claude-3-7-sonnet-20250219",
"PERPLEXITY_MODEL": "sonar-pro",
"MAX_TOKENS": 64000,
"MAX_TOKENS": 128000,
"TEMPERATURE": 0.2,
"DEFAULT_SUBTASKS": 5,
"DEFAULT_PRIORITY": "medium"

View File

@@ -10,26 +10,20 @@ There are two ways to set up Task Master: using MCP (recommended) or via npm ins
MCP (Model Control Protocol) provides the easiest way to get started with Task Master directly in your editor.
1. **Install the package**
```bash
npm i -g task-master-ai
```
2. **Add the MCP config to your editor** (Cursor recommended, but it works with other text editors):
1. **Add the MCP config to your editor** (Cursor recommended, but it works with other text editors):
```json
{
"mcpServers": {
"taskmaster-ai": {
"command": "npx",
"args": ["-y", "task-master-mcp"],
"args": ["-y", "--package", "task-master-ai", "task-master-mcp"],
"env": {
"ANTHROPIC_API_KEY": "YOUR_ANTHROPIC_API_KEY_HERE",
"PERPLEXITY_API_KEY": "YOUR_PERPLEXITY_API_KEY_HERE",
"MODEL": "claude-3-7-sonnet-20250219",
"PERPLEXITY_MODEL": "sonar-pro",
"MAX_TOKENS": 64000,
"MAX_TOKENS": 128000,
"TEMPERATURE": 0.2,
"DEFAULT_SUBTASKS": 5,
"DEFAULT_PRIORITY": "medium"
@@ -138,7 +132,7 @@ You can also set up the MCP server in Cursor settings:
4. Configure with the following details:
- Name: "Task Master"
- Type: "Command"
- Command: "npx -y task-master-mcp"
- Command: "npx -y --package task-master-ai task-master-mcp"
5. Save the settings
Once configured, you can interact with Task Master's task management commands directly through Cursor's interface, providing a more integrated experience.

View File

@@ -4,6 +4,7 @@
*/
import { addDependency } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -13,32 +14,19 @@ import {
* Direct function wrapper for addDependency with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string|number} args.id - Task ID to add dependency to
* @param {string|number} args.dependsOn - Task ID that will become a dependency
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Result object with success status and data/error information
*/
export async function addDependencyDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id, dependsOn } = args;
export async function addDependencyDirect(args, log, { session }) {
try {
log.info(`Adding dependency with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('addDependencyDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Validate required parameters
if (!id) {
if (!args.id) {
return {
success: false,
error: {
@@ -48,7 +36,7 @@ export async function addDependencyDirect(args, log) {
};
}
if (!dependsOn) {
if (!args.dependsOn) {
return {
success: false,
error: {
@@ -58,16 +46,18 @@ export async function addDependencyDirect(args, log) {
};
}
// Use provided path
const tasksPath = tasksJsonPath;
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log, session);
// Format IDs for the core function
const taskId =
id && id.includes && id.includes('.') ? id : parseInt(id, 10);
args.id.includes && args.id.includes('.')
? args.id
: parseInt(args.id, 10);
const dependencyId =
dependsOn && dependsOn.includes && dependsOn.includes('.')
? dependsOn
: parseInt(dependsOn, 10);
args.dependsOn.includes && args.dependsOn.includes('.')
? args.dependsOn
: parseInt(args.dependsOn, 10);
log.info(
`Adding dependency: task ${taskId} will depend on ${dependencyId}`
@@ -76,7 +66,7 @@ export async function addDependencyDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function using the provided path
// Call the core function
await addDependency(tasksPath, taskId, dependencyId);
// Restore normal logging

View File

@@ -3,6 +3,7 @@
*/
import { addSubtask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -11,7 +12,6 @@ import {
/**
* Add a subtask to an existing task
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - Parent task ID
* @param {string} [args.taskId] - Existing task ID to convert to subtask (optional)
* @param {string} [args.title] - Title for new subtask (when creating a new subtask)
@@ -19,39 +19,17 @@ import {
* @param {string} [args.details] - Implementation details for new subtask
* @param {string} [args.status] - Status for new subtask (default: 'pending')
* @param {string} [args.dependencies] - Comma-separated list of dependency IDs
* @param {string} [args.file] - Path to the tasks file
* @param {boolean} [args.skipGenerate] - Skip regenerating task files
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: string}>}
*/
export async function addSubtaskDirect(args, log) {
// Destructure expected args
const {
tasksJsonPath,
id,
taskId,
title,
description,
details,
status,
dependencies: dependenciesStr,
skipGenerate
} = args;
export async function addSubtaskDirect(args, log, { session }) {
try {
log.info(`Adding subtask with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('addSubtaskDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
if (!id) {
if (!args.id) {
return {
success: false,
error: {
@@ -62,7 +40,7 @@ export async function addSubtaskDirect(args, log) {
}
// Either taskId or title must be provided
if (!taskId && !title) {
if (!args.taskId && !args.title) {
return {
success: false,
error: {
@@ -72,26 +50,26 @@ export async function addSubtaskDirect(args, log) {
};
}
// Use provided path
const tasksPath = tasksJsonPath;
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log, session);
// Parse dependencies if provided
let dependencies = [];
if (dependenciesStr) {
dependencies = dependenciesStr.split(',').map((depId) => {
if (args.dependencies) {
dependencies = args.dependencies.split(',').map((id) => {
// Handle both regular IDs and dot notation
return depId.includes('.') ? depId.trim() : parseInt(depId.trim(), 10);
return id.includes('.') ? id.trim() : parseInt(id.trim(), 10);
});
}
// Convert existingTaskId to a number if provided
const existingTaskId = taskId ? parseInt(taskId, 10) : null;
const existingTaskId = args.taskId ? parseInt(args.taskId, 10) : null;
// Convert parent ID to a number
const parentId = parseInt(id, 10);
const parentId = parseInt(args.id, 10);
// Determine if we should generate files
const generateFiles = !skipGenerate;
const generateFiles = !args.skipGenerate;
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
@@ -123,10 +101,10 @@ export async function addSubtaskDirect(args, log) {
log.info(`Creating new subtask for parent task ${parentId}`);
const newSubtaskData = {
title: title,
description: description || '',
details: details || '',
status: status || 'pending',
title: args.title,
description: args.description || '',
details: args.details || '',
status: args.status || 'pending',
dependencies: dependencies
};

View File

@@ -4,6 +4,7 @@
*/
import { addTask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -36,28 +37,13 @@ import {
* @param {Object} context - Additional context (reportProgress, session)
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
*/
export async function addTaskDirect(args, log, context = {}) {
// Destructure expected args
const { tasksJsonPath, prompt, dependencies, priority, research } = args;
export async function addTaskDirect(args, log, { session }) {
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('addTaskDirect called without tasksJsonPath');
disableSilentMode(); // Disable before returning
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Use provided path
const tasksPath = tasksJsonPath;
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log, session);
// Check if this is manual task creation or AI-driven task creation
const isManualCreation = args.title && args.description;
@@ -79,18 +65,15 @@ export async function addTaskDirect(args, log, context = {}) {
}
// Extract and prepare parameters
const taskPrompt = prompt;
const taskDependencies = Array.isArray(dependencies)
? dependencies
: dependencies
? String(dependencies)
const prompt = args.prompt;
const dependencies = Array.isArray(args.dependencies)
? args.dependencies
: args.dependencies
? String(args.dependencies)
.split(',')
.map((id) => parseInt(id.trim(), 10))
: [];
const taskPriority = priority || 'medium';
// Extract context parameters for advanced functionality
const { session } = context;
const priority = args.priority || 'medium';
let manualTaskData = null;
@@ -104,14 +87,14 @@ export async function addTaskDirect(args, log, context = {}) {
};
log.info(
`Adding new task manually with title: "${args.title}", dependencies: [${taskDependencies.join(', ')}], priority: ${priority}`
`Adding new task manually with title: "${args.title}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`
);
// Call the addTask function with manual task data
const newTaskId = await addTask(
tasksPath,
null, // No prompt needed for manual creation
taskDependencies,
dependencies,
priority,
{
mcpLog: log,
@@ -135,7 +118,7 @@ export async function addTaskDirect(args, log, context = {}) {
} else {
// AI-driven task creation
log.info(
`Adding new task with prompt: "${prompt}", dependencies: [${taskDependencies.join(', ')}], priority: ${priority}`
`Adding new task with prompt: "${prompt}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`
);
// Initialize AI client with session environment
@@ -221,7 +204,7 @@ export async function addTaskDirect(args, log, context = {}) {
const newTaskId = await addTask(
tasksPath,
prompt,
taskDependencies,
dependencies,
priority,
{
mcpLog: log,

View File

@@ -3,6 +3,11 @@
*/
import { analyzeTaskComplexity } from '../../../../scripts/modules/task-manager.js';
import {
findTasksJsonPath,
resolveProjectPath,
ensureDirectoryExists
} from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode,
@@ -15,60 +20,55 @@ import path from 'path';
/**
* Analyze task complexity and generate recommendations
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.outputPath - Explicit absolute path to save the report.
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.output] - Output file path for the report
* @param {string} [args.model] - LLM model to use for analysis
* @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.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @param {Object} [context={}] - Context object containing session data
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function analyzeTaskComplexityDirect(args, log, context = {}) {
const { session } = context; // Only extract session, not reportProgress
// Destructure expected args
const { tasksJsonPath, outputPath, model, threshold, research } = args;
export async function analyzeTaskComplexityDirect(args, log, { session }) {
try {
log.info(`Analyzing task complexity with args: ${JSON.stringify(args)}`);
// Check if required paths were provided
if (!tasksJsonPath) {
log.error('analyzeTaskComplexityDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
if (!outputPath) {
log.error('analyzeTaskComplexityDirect called without outputPath');
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: 'outputPath is required' }
};
}
// Find the tasks.json path AND get the validated project root
const { tasksPath, validatedProjectRoot } = findTasksJsonPath(
args,
log,
session
);
log.info(
`Using tasks file: ${tasksPath} located within project root: ${validatedProjectRoot}`
);
// Use the provided paths
const tasksPath = tasksJsonPath;
const resolvedOutputPath = outputPath;
// Determine and resolve the output path using the VALIDATED root
const relativeOutputPath =
args.output || 'scripts/task-complexity-report.json';
const absoluteOutputPath = resolveProjectPath(
relativeOutputPath,
validatedProjectRoot,
log
);
log.info(`Analyzing task complexity from: ${tasksPath}`);
log.info(`Output report will be saved to: ${resolvedOutputPath}`);
// Ensure the output directory exists
ensureDirectoryExists(path.dirname(absoluteOutputPath), log);
if (research) {
log.info(`Output report will be saved to: ${absoluteOutputPath}`);
if (args.research) {
log.info('Using Perplexity AI for research-backed complexity analysis');
}
// Create options object for analyzeTaskComplexity using provided paths
// Create options object for analyzeTaskComplexity
const options = {
file: tasksPath,
output: resolvedOutputPath,
model: model,
threshold: threshold,
research: research === true
output: absoluteOutputPath,
model: args.model,
threshold: args.threshold,
research: args.research === true
};
// Enable silent mode to prevent console logs from interfering with JSON response
@@ -109,7 +109,7 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
}
// Verify the report file was created
if (!fs.existsSync(resolvedOutputPath)) {
if (!fs.existsSync(absoluteOutputPath)) {
return {
success: false,
error: {
@@ -122,7 +122,7 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
// Read the report file
let report;
try {
report = JSON.parse(fs.readFileSync(resolvedOutputPath, 'utf8'));
report = JSON.parse(fs.readFileSync(absoluteOutputPath, 'utf8'));
// Important: Handle different report formats
// The core function might return an array or an object with a complexityAnalysis property
@@ -144,8 +144,8 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
return {
success: true,
data: {
message: `Task complexity analysis complete. Report saved to ${resolvedOutputPath}`,
reportPath: resolvedOutputPath,
message: `Task complexity analysis complete. Report saved to ${absoluteOutputPath}`,
reportPath: absoluteOutputPath,
reportSummary: {
taskCount: analysisArray.length,
highComplexityTasks,
@@ -165,18 +165,23 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
};
}
} catch (error) {
// Make sure to restore normal logging even if there's an error
// Centralized error catching for issues like invalid root, file not found, core errors etc.
if (isSilentMode()) {
disableSilentMode();
}
log.error(`Error in analyzeTaskComplexityDirect: ${error.message}`);
log.error(`Error in analyzeTaskComplexityDirect: ${error.message}`, {
code: error.code,
details: error.details,
stack: error.stack
});
return {
success: false,
error: {
code: 'CORE_FUNCTION_ERROR',
code: error.code || 'ANALYZE_COMPLEXITY_ERROR',
message: error.message
}
},
fromCache: false // Assume errors are not from cache
};
}
}

View File

@@ -3,6 +3,7 @@
*/
import { clearSubtasks } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -12,32 +13,19 @@ import fs from 'fs';
/**
* Clear subtasks from specified tasks
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} [args.id] - Task IDs (comma-separated) to clear subtasks from
* @param {boolean} [args.all] - Clear subtasks from all tasks
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function clearSubtasksDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id, all } = args;
export async function clearSubtasksDirect(args, log, { session }) {
try {
log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('clearSubtasksDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Either id or all must be provided
if (!id && !all) {
if (!args.id && !args.all) {
return {
success: false,
error: {
@@ -48,8 +36,8 @@ export async function clearSubtasksDirect(args, log) {
};
}
// Use provided path
const tasksPath = tasksJsonPath;
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log, session);
// Check if tasks.json exists
if (!fs.existsSync(tasksPath)) {
@@ -65,7 +53,7 @@ export async function clearSubtasksDirect(args, log) {
let taskIds;
// If all is specified, get all task IDs
if (all) {
if (args.all) {
log.info('Clearing subtasks from all tasks');
const data = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
if (!data || !data.tasks || data.tasks.length === 0) {
@@ -80,7 +68,7 @@ export async function clearSubtasksDirect(args, log) {
taskIds = data.tasks.map((t) => t.id).join(',');
} else {
// Use the provided task IDs
taskIds = id;
taskIds = args.id;
}
log.info(`Clearing subtasks from tasks: ${taskIds}`);

View File

@@ -8,34 +8,37 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import path from 'path';
/**
* Direct function wrapper for displaying the complexity report with error handling and caching.
*
* @param {Object} args - Command arguments containing reportPath.
* @param {string} args.reportPath - Explicit path to the complexity report file.
* @param {Object} args - Command arguments containing file path option
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Result object with success status and data/error information
*/
export async function complexityReportDirect(args, log) {
// Destructure expected args
const { reportPath } = args;
export async function complexityReportDirect(args, log, { session }) {
try {
log.info(`Getting complexity report with args: ${JSON.stringify(args)}`);
// Check if reportPath was provided
if (!reportPath) {
log.error('complexityReportDirect called without reportPath');
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' },
fromCache: false
};
// Get tasks file path to determine project root for the default report location
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log, session);
} catch (error) {
log.warn(
`Tasks file not found, using current directory: ${error.message}`
);
// Continue with default or specified report path
}
// Use the provided report path
// Get report file path from args or use default
const reportPath =
args.file ||
path.join(process.cwd(), 'scripts', 'task-complexity-report.json');
log.info(`Looking for complexity report at: ${reportPath}`);
// Generate cache key based on report path

View File

@@ -8,6 +8,7 @@ import {
disableSilentMode,
isSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { getAnthropicClientForMCP } from '../utils/ai-client-utils.js';
import path from 'path';
import fs from 'fs';
@@ -15,51 +16,32 @@ import fs from 'fs';
/**
* Expand all pending tasks with subtasks
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {number|string} [args.num] - Number of subtasks to generate
* @param {boolean} [args.research] - Enable Perplexity AI for research-backed subtask generation
* @param {string} [args.prompt] - Additional context to guide subtask generation
* @param {boolean} [args.force] - Force regeneration of subtasks for tasks that already have them
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @param {Object} context - Context object containing session
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function expandAllTasksDirect(args, log, context = {}) {
const { session } = context; // Only extract session, not reportProgress
// Destructure expected args
const { tasksJsonPath, num, research, prompt, force } = args;
export async function expandAllTasksDirect(args, log, { session }) {
try {
log.info(`Expanding all tasks with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('expandAllTasksDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Enable silent mode early to prevent any console output
enableSilentMode();
try {
// Remove internal path finding
/*
const tasksPath = findTasksJsonPath(args, log);
*/
// Use provided path
const tasksPath = tasksJsonPath;
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log, session);
// Parse parameters
const numSubtasks = num ? parseInt(num, 10) : undefined;
const useResearch = research === true;
const additionalContext = prompt || '';
const forceFlag = force === true;
const numSubtasks = args.num ? parseInt(args.num, 10) : undefined;
const useResearch = args.research === true;
const additionalContext = args.prompt || '';
const forceFlag = args.force === true;
log.info(
`Expanding all tasks with ${numSubtasks || 'default'} subtasks each...`

View File

@@ -11,6 +11,7 @@ import {
disableSilentMode,
isSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
getAnthropicClientForMCP,
getModelConfig
@@ -22,21 +23,11 @@ import fs from 'fs';
* Direct function wrapper for expanding a task into subtasks with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - The ID of the task to expand.
* @param {number|string} [args.num] - Number of subtasks to generate.
* @param {boolean} [args.research] - Enable Perplexity AI for research-backed subtask generation.
* @param {string} [args.prompt] - Additional context to guide subtask generation.
* @param {boolean} [args.force] - Force expansion even if subtasks exist.
* @param {Object} log - Logger object
* @param {Object} context - Context object containing session and reportProgress
* @returns {Promise<Object>} - Task expansion result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
*/
export async function expandTaskDirect(args, log, context = {}) {
const { session } = context;
// Destructure expected args
const { tasksJsonPath, id, num, research, prompt, force } = args;
export async function expandTaskDirect(args, log, { session }) {
// Log session root data for debugging
log.info(
`Session data in expandTaskDirect: ${JSON.stringify({
@@ -47,26 +38,48 @@ export async function expandTaskDirect(args, log, context = {}) {
})}`
);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('expandTaskDirect called without tasksJsonPath');
let tasksPath;
try {
// If a direct file path is provided, use it directly
if (args.file && fs.existsSync(args.file)) {
log.info(
`[expandTaskDirect] Using explicitly provided tasks file: ${args.file}`
);
tasksPath = args.file;
} else {
// Find the tasks path through standard logic
log.info(
`[expandTaskDirect] No direct file path provided or file not found at ${args.file}, searching using findTasksJsonPath`
);
tasksPath = findTasksJsonPath(args, log, session);
}
} catch (error) {
log.error(
`[expandTaskDirect] Error during tasksPath determination: ${error.message}`
);
// Include session roots information in error
const sessionRootsInfo = session
? `\nSession.roots: ${JSON.stringify(session.roots)}\n` +
`Current Working Directory: ${process.cwd()}\n` +
`Args.projectRoot: ${args.projectRoot}\n` +
`Args.file: ${args.file}\n`
: '\nSession object not available';
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
code: 'FILE_NOT_FOUND_ERROR',
message: `Error determining tasksPath: ${error.message}${sessionRootsInfo}`
},
fromCache: false
};
}
// Use provided path
const tasksPath = tasksJsonPath;
log.info(`[expandTaskDirect] Using tasksPath: ${tasksPath}`);
log.info(`[expandTaskDirect] Determined tasksPath: ${tasksPath}`);
// Validate task ID
const taskId = id ? parseInt(id, 10) : null;
const taskId = args.id ? parseInt(args.id, 10) : null;
if (!taskId) {
log.error('Task ID is required');
return {
@@ -80,10 +93,9 @@ export async function expandTaskDirect(args, log, context = {}) {
}
// Process other parameters
const numSubtasks = num ? parseInt(num, 10) : undefined;
const useResearch = research === true;
const additionalContext = prompt || '';
const forceFlag = force === true;
const numSubtasks = args.num ? parseInt(args.num, 10) : undefined;
const useResearch = args.research === true;
const additionalContext = args.prompt || '';
// Initialize AI client if needed (for expandTask function)
try {
@@ -158,16 +170,15 @@ export async function expandTaskDirect(args, log, context = {}) {
};
}
// Check for existing subtasks and force flag
// Check for existing subtasks
const hasExistingSubtasks = task.subtasks && task.subtasks.length > 0;
if (hasExistingSubtasks && !forceFlag) {
log.info(
`Task ${taskId} already has ${task.subtasks.length} subtasks. Use --force to overwrite.`
);
// If the task already has subtasks, just return it (matching core behavior)
if (hasExistingSubtasks) {
log.info(`Task ${taskId} already has ${task.subtasks.length} subtasks`);
return {
success: true,
data: {
message: `Task ${taskId} already has subtasks. Expansion skipped.`,
task,
subtasksAdded: 0,
hasExistingSubtasks
@@ -176,14 +187,6 @@ export async function expandTaskDirect(args, log, context = {}) {
};
}
// If force flag is set, clear existing subtasks
if (hasExistingSubtasks && forceFlag) {
log.info(
`Force flag set. Clearing existing subtasks for task ${taskId}.`
);
task.subtasks = [];
}
// Keep a copy of the task before modification
const originalTask = JSON.parse(JSON.stringify(task));

View File

@@ -3,6 +3,7 @@
*/
import { fixDependenciesCommand } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -12,30 +13,17 @@ import fs from 'fs';
/**
* Fix invalid dependencies in tasks.json automatically
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function fixDependenciesDirect(args, log) {
// Destructure expected args
const { tasksJsonPath } = args;
export async function fixDependenciesDirect(args, log, { session }) {
try {
log.info(`Fixing invalid dependencies in tasks: ${tasksJsonPath}`);
log.info(`Fixing invalid dependencies in tasks...`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('fixDependenciesDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Use provided path
const tasksPath = tasksJsonPath;
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log, session);
// Verify the file exists
if (!fs.existsSync(tasksPath)) {
@@ -51,7 +39,7 @@ export async function fixDependenciesDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the original command function using the provided path
// Call the original command function
await fixDependenciesCommand(tasksPath);
// Restore normal logging

View File

@@ -8,46 +8,40 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import path from 'path';
/**
* Direct function wrapper for generateTaskFiles with error handling.
*
* @param {Object} args - Command arguments containing tasksJsonPath and outputDir.
* @param {Object} args - Command arguments containing file and output path options.
* @param {Object} log - Logger object.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function generateTaskFilesDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, outputDir } = args;
export async function generateTaskFilesDirect(args, log, { session }) {
try {
log.info(`Generating task files with args: ${JSON.stringify(args)}`);
// Check if paths were provided
if (!tasksJsonPath) {
const errorMessage = 'tasksJsonPath is required but was not provided.';
log.error(errorMessage);
// Get tasks file path
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log, session);
} catch (error) {
log.error(`Error finding tasks file: ${error.message}`);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
error: { code: 'TASKS_FILE_ERROR', message: error.message },
fromCache: false
};
}
// Get output directory (defaults to the same directory as the tasks file)
let outputDir = args.output;
if (!outputDir) {
const errorMessage = 'outputDir is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
fromCache: false
};
outputDir = path.dirname(tasksPath);
}
// Use the provided paths
const tasksPath = tasksJsonPath;
const resolvedOutputDir = outputDir;
log.info(`Generating task files from ${tasksPath} to ${resolvedOutputDir}`);
log.info(`Generating task files from ${tasksPath} to ${outputDir}`);
// Execute core generateTaskFiles function in a separate try/catch
try {
@@ -55,7 +49,7 @@ export async function generateTaskFilesDirect(args, log) {
enableSilentMode();
// The function is synchronous despite being awaited elsewhere
generateTaskFiles(tasksPath, resolvedOutputDir);
generateTaskFiles(tasksPath, outputDir);
// Restore normal logging after task generation
disableSilentMode();
@@ -76,8 +70,8 @@ export async function generateTaskFilesDirect(args, log) {
success: true,
data: {
message: `Successfully generated task files`,
tasksPath: tasksPath,
outputDir: resolvedOutputDir,
tasksPath,
outputDir,
taskFiles:
'Individual task files have been generated in the output directory'
},

View File

@@ -1,4 +1,5 @@
import { initializeProject } from '../../../../scripts/init.js'; // Import core function and its logger if needed separately
import path from 'path';
import { initializeProject, log as initLog } from '../../../../scripts/init.js'; // Import core function and its logger if needed separately
import {
enableSilentMode,
disableSilentMode
@@ -15,8 +16,7 @@ import os from 'os'; // Import os module for home directory check
* @param {object} context - The context object, must contain { session }.
* @returns {Promise<{success: boolean, data?: any, error?: {code: string, message: string}}>} - Standard result object.
*/
export async function initializeProjectDirect(args, log, context = {}) {
const { session } = context;
export async function initializeProjectDirect(args, log, { session }) {
const homeDir = os.homedir();
let targetDirectory = null;

View File

@@ -5,6 +5,7 @@
import { listTasks } from '../../../../scripts/modules/task-manager.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -13,30 +14,38 @@ import {
/**
* Direct function wrapper for listTasks with error handling and caching.
*
* @param {Object} args - Command arguments (now expecting tasksJsonPath explicitly).
* @param {Object} args - Command arguments (projectRoot is expected to be resolved).
* @param {Object} log - Logger object.
* @returns {Promise<Object>} - Task list result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }.
*/
export async function listTasksDirect(args, log) {
// Destructure the explicit tasksJsonPath from args
const { tasksJsonPath, status, withSubtasks } = args;
if (!tasksJsonPath) {
log.error('listTasksDirect called without tasksJsonPath');
export async function listTasksDirect(args, log, { session }) {
let tasksPath;
try {
// Find the tasks path first - needed for cache key and execution
tasksPath = findTasksJsonPath(args, log, session);
} catch (error) {
if (error.code === 'TASKS_FILE_NOT_FOUND') {
log.error(`Tasks file not found: ${error.message}`);
// Return the error structure expected by the calling tool/handler
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
},
error: { code: error.code, message: error.message },
fromCache: false
};
}
log.error(`Unexpected error finding tasks file: ${error.message}`);
// Re-throw for outer catch or return structured error
return {
success: false,
error: { code: 'FIND_TASKS_PATH_ERROR', message: error.message },
fromCache: false
};
}
// Use the explicit tasksJsonPath for cache key
const statusFilter = status || 'all';
const withSubtasksFilter = withSubtasks || false;
const cacheKey = `listTasks:${tasksJsonPath}:${statusFilter}:${withSubtasksFilter}`;
// Generate cache key *after* finding tasksPath
const statusFilter = args.status || 'all';
const withSubtasks = args.withSubtasks || false;
const cacheKey = `listTasks:${tasksPath}:${statusFilter}:${withSubtasks}`;
// Define the action function to be executed on cache miss
const coreListTasksAction = async () => {
@@ -45,13 +54,12 @@ export async function listTasksDirect(args, log) {
enableSilentMode();
log.info(
`Executing core listTasks function for path: ${tasksJsonPath}, filter: ${statusFilter}, subtasks: ${withSubtasksFilter}`
`Executing core listTasks function for path: ${tasksPath}, filter: ${statusFilter}, subtasks: ${withSubtasks}`
);
// Pass the explicit tasksJsonPath to the core function
const resultData = listTasks(
tasksJsonPath,
tasksPath,
statusFilter,
withSubtasksFilter,
withSubtasks,
'json'
);

View File

@@ -6,6 +6,7 @@
import { findNextTask } from '../../../../scripts/modules/task-manager.js';
import { readJSON } from '../../../../scripts/modules/utils.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -15,28 +16,28 @@ import {
* Direct function wrapper for finding the next task to work on with error handling and caching.
*
* @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Next task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
*/
export async function nextTaskDirect(args, log) {
// Destructure expected args
const { tasksJsonPath } = args;
if (!tasksJsonPath) {
log.error('nextTaskDirect called without tasksJsonPath');
export async function nextTaskDirect(args, log, { session }) {
let tasksPath;
try {
// Find the tasks path first - needed for cache key and execution
tasksPath = findTasksJsonPath(args, log, session);
} catch (error) {
log.error(`Tasks file not found: ${error.message}`);
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
code: 'FILE_NOT_FOUND_ERROR',
message: error.message
},
fromCache: false
};
}
// Generate cache key using the provided task path
const cacheKey = `nextTask:${tasksJsonPath}`;
// Generate cache key using task path
const cacheKey = `nextTask:${tasksPath}`;
// Define the action function to be executed on cache miss
const coreNextTaskAction = async () => {
@@ -44,17 +45,16 @@ export async function nextTaskDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Finding next task from ${tasksJsonPath}`);
log.info(`Finding next task from ${tasksPath}`);
// Read tasks data using the provided path
const data = readJSON(tasksJsonPath);
// Read tasks data
const data = readJSON(tasksPath);
if (!data || !data.tasks) {
disableSilentMode(); // Disable before return
return {
success: false,
error: {
code: 'INVALID_TASKS_FILE',
message: `No valid tasks found in ${tasksJsonPath}`
message: `No valid tasks found in ${tasksPath}`
}
};
}

View File

@@ -47,9 +47,21 @@ export async function parsePRDDirect(args, log, context = {}) {
};
}
// Validate required parameters
// --- Parameter validation and path resolution ---
if (!args.input) {
const errorMessage =
'No input file specified. Please provide an input PRD document path.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_INPUT_FILE', message: errorMessage },
fromCache: false
};
}
// Validate projectRoot
if (!args.projectRoot) {
const errorMessage = 'Project root is required for parsePRDDirect';
const errorMessage = 'Project root is required but was not provided';
log.error(errorMessage);
return {
success: false,
@@ -58,32 +70,53 @@ export async function parsePRDDirect(args, log, context = {}) {
};
}
if (!args.input) {
const errorMessage = 'Input file path is required for parsePRDDirect';
const homeDir = os.homedir();
// Disallow invalid projectRoot values
if (args.projectRoot === '/' || args.projectRoot === homeDir) {
const errorMessage = `Invalid project root: ${args.projectRoot}. Cannot use root or home directory.`;
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_INPUT_PATH', message: errorMessage },
error: { code: 'INVALID_PROJECT_ROOT', message: errorMessage },
fromCache: false
};
}
if (!args.output) {
const errorMessage = 'Output file path is required for parsePRDDirect';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_OUTPUT_PATH', message: errorMessage },
fromCache: false
};
}
// Resolve input path (expecting absolute path or path relative to project root)
// Resolve input path (relative to validated project root)
const projectRoot = args.projectRoot;
log.info(`Using validated project root: ${projectRoot}`);
// Make sure the project root directory exists
if (!fs.existsSync(projectRoot)) {
const errorMessage = `Project root directory does not exist: ${projectRoot}`;
log.error(errorMessage);
return {
success: false,
error: { code: 'PROJECT_ROOT_NOT_FOUND', message: errorMessage },
fromCache: false
};
}
// Resolve input path relative to validated project root
const inputPath = path.isAbsolute(args.input)
? args.input
: path.resolve(projectRoot, args.input);
log.info(`Resolved input path: ${inputPath}`);
// Determine output path
let outputPath;
if (args.output) {
outputPath = path.isAbsolute(args.output)
? args.output
: path.resolve(projectRoot, args.output);
} else {
// Default to tasks/tasks.json in the project root
outputPath = path.resolve(projectRoot, 'tasks', 'tasks.json');
}
log.info(`Resolved output path: ${outputPath}`);
// Verify input file exists
if (!fs.existsSync(inputPath)) {
const errorMessage = `Input file not found: ${inputPath}`;
@@ -99,18 +132,6 @@ export async function parsePRDDirect(args, log, context = {}) {
};
}
// Resolve output path (expecting absolute path or path relative to project root)
const outputPath = path.isAbsolute(args.output)
? args.output
: path.resolve(projectRoot, args.output);
// Ensure output directory exists
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
log.info(`Creating output directory: ${outputDir}`);
fs.mkdirSync(outputDir, { recursive: true });
}
// Parse number of tasks - handle both string and number values
let numTasks = 10; // Default
if (args.numTasks) {

View File

@@ -3,6 +3,7 @@
*/
import { removeDependency } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -11,32 +12,19 @@ import {
/**
* Remove a dependency from a task
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string|number} args.id - Task ID to remove dependency from
* @param {string|number} args.dependsOn - Task ID to remove as a dependency
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function removeDependencyDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id, dependsOn } = args;
export async function removeDependencyDirect(args, log, { session }) {
try {
log.info(`Removing dependency with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('removeDependencyDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Validate required parameters
if (!id) {
if (!args.id) {
return {
success: false,
error: {
@@ -46,7 +34,7 @@ export async function removeDependencyDirect(args, log) {
};
}
if (!dependsOn) {
if (!args.dependsOn) {
return {
success: false,
error: {
@@ -56,16 +44,18 @@ export async function removeDependencyDirect(args, log) {
};
}
// Use provided path
const tasksPath = tasksJsonPath;
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log, session);
// Format IDs for the core function
const taskId =
id && id.includes && id.includes('.') ? id : parseInt(id, 10);
args.id.includes && args.id.includes('.')
? args.id
: parseInt(args.id, 10);
const dependencyId =
dependsOn && dependsOn.includes && dependsOn.includes('.')
? dependsOn
: parseInt(dependsOn, 10);
args.dependsOn.includes && args.dependsOn.includes('.')
? args.dependsOn
: parseInt(args.dependsOn, 10);
log.info(
`Removing dependency: task ${taskId} no longer depends on ${dependencyId}`
@@ -74,7 +64,7 @@ export async function removeDependencyDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function using the provided tasksPath
// Call the core function
await removeDependency(tasksPath, taskId, dependencyId);
// Restore normal logging

View File

@@ -3,6 +3,7 @@
*/
import { removeSubtask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -11,37 +12,22 @@ import {
/**
* Remove a subtask from its parent task
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - Subtask ID in format "parentId.subtaskId" (required)
* @param {boolean} [args.convert] - Whether to convert the subtask to a standalone task
* @param {string} [args.file] - Path to the tasks file
* @param {boolean} [args.skipGenerate] - Skip regenerating task files
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function removeSubtaskDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id, convert, skipGenerate } = args;
export async function removeSubtaskDirect(args, log, { session }) {
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('removeSubtaskDirect called without tasksJsonPath');
disableSilentMode(); // Disable before returning
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
if (!id) {
disableSilentMode(); // Disable before returning
if (!args.id) {
return {
success: false,
error: {
@@ -53,34 +39,32 @@ export async function removeSubtaskDirect(args, log) {
}
// Validate subtask ID format
if (!id.includes('.')) {
disableSilentMode(); // Disable before returning
if (!args.id.includes('.')) {
return {
success: false,
error: {
code: 'INPUT_VALIDATION_ERROR',
message: `Invalid subtask ID format: ${id}. Expected format: "parentId.subtaskId"`
message: `Invalid subtask ID format: ${args.id}. Expected format: "parentId.subtaskId"`
}
};
}
// Use provided path
const tasksPath = tasksJsonPath;
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log, session);
// Convert convertToTask to a boolean
const convertToTask = convert === true;
const convertToTask = args.convert === true;
// Determine if we should generate files
const generateFiles = !skipGenerate;
const generateFiles = !args.skipGenerate;
log.info(
`Removing subtask ${id} (convertToTask: ${convertToTask}, generateFiles: ${generateFiles})`
`Removing subtask ${args.id} (convertToTask: ${convertToTask}, generateFiles: ${generateFiles})`
);
// Use the provided tasksPath
const result = await removeSubtask(
tasksPath,
id,
args.id,
convertToTask,
generateFiles
);
@@ -93,7 +77,7 @@ export async function removeSubtaskDirect(args, log) {
return {
success: true,
data: {
message: `Subtask ${id} successfully converted to task #${result.id}`,
message: `Subtask ${args.id} successfully converted to task #${result.id}`,
task: result
}
};
@@ -102,7 +86,7 @@ export async function removeSubtaskDirect(args, log) {
return {
success: true,
data: {
message: `Subtask ${id} successfully removed`
message: `Subtask ${args.id} successfully removed`
}
};
}

View File

@@ -8,35 +8,35 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
/**
* Direct function wrapper for removeTask with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - The ID of the task or subtask to remove.
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: false }
*/
export async function removeTaskDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id } = args;
export async function removeTaskDirect(args, log, { session }) {
try {
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('removeTaskDirect called without tasksJsonPath');
// Find the tasks path first
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log, session);
} catch (error) {
log.error(`Tasks file not found: ${error.message}`);
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
code: 'FILE_NOT_FOUND_ERROR',
message: error.message
},
fromCache: false
};
}
// Validate task ID parameter
const taskId = id;
const taskId = args.id;
if (!taskId) {
log.error('Task ID is required');
return {
@@ -50,14 +50,14 @@ export async function removeTaskDirect(args, log) {
}
// Skip confirmation in the direct function since it's handled by the client
log.info(`Removing task with ID: ${taskId} from ${tasksJsonPath}`);
log.info(`Removing task with ID: ${taskId} from ${tasksPath}`);
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core removeTask function using the provided path
const result = await removeTask(tasksJsonPath, taskId);
// Call the core removeTask function
const result = await removeTask(tasksPath, taskId);
// Restore normal logging
disableSilentMode();
@@ -70,7 +70,7 @@ export async function removeTaskDirect(args, log) {
data: {
message: result.message,
taskId: taskId,
tasksPath: tasksJsonPath,
tasksPath: tasksPath,
removedTask: result.removedTask
},
fromCache: false

View File

@@ -4,6 +4,7 @@
*/
import { setTaskStatus } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode,
@@ -13,29 +14,16 @@ import {
/**
* Direct function wrapper for setTaskStatus with error handling.
*
* @param {Object} args - Command arguments containing id, status and tasksJsonPath.
* @param {Object} args - Command arguments containing id, status and file path options.
* @param {Object} log - Logger object.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function setTaskStatusDirect(args, log) {
// Destructure expected args, including the resolved tasksJsonPath
const { tasksJsonPath, id, status } = args;
export async function setTaskStatusDirect(args, log, { session }) {
try {
log.info(`Setting task status with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
const errorMessage = 'tasksJsonPath is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
fromCache: false
};
}
// Check required parameters (id and status)
if (!id) {
// Check required parameters
if (!args.id) {
const errorMessage =
'No task ID specified. Please provide a task ID to update.';
log.error(errorMessage);
@@ -46,7 +34,7 @@ export async function setTaskStatusDirect(args, log) {
};
}
if (!status) {
if (!args.status) {
const errorMessage =
'No status specified. Please provide a new status value.';
log.error(errorMessage);
@@ -57,16 +45,32 @@ export async function setTaskStatusDirect(args, log) {
};
}
// Use the provided path
const tasksPath = tasksJsonPath;
// Get tasks file path
let tasksPath;
try {
// The enhanced findTasksJsonPath will now search in parent directories if needed
tasksPath = findTasksJsonPath(args, log, session);
log.info(`Found tasks file at: ${tasksPath}`);
} catch (error) {
log.error(`Error finding tasks file: ${error.message}`);
return {
success: false,
error: {
code: 'TASKS_FILE_ERROR',
message: `${error.message}\n\nPlease ensure you are in a Task Master project directory or use the --project-root parameter to specify the path to your project.`
},
fromCache: false
};
}
// Execute core setTaskStatus function
const taskId = id;
const newStatus = status;
const taskId = args.id;
const newStatus = args.status;
log.info(`Setting task ${taskId} status to "${newStatus}"`);
// Call the core function with proper silent mode handling
let result;
enableSilentMode(); // Enable silent mode before calling core function
try {
// Call the core function
@@ -75,20 +79,19 @@ export async function setTaskStatusDirect(args, log) {
log.info(`Successfully set task ${taskId} status to ${newStatus}`);
// Return success data
const result = {
result = {
success: true,
data: {
message: `Successfully updated task ${taskId} status to "${newStatus}"`,
taskId,
status: newStatus,
tasksPath: tasksPath // Return the path used
tasksPath
},
fromCache: false // This operation always modifies state and should never be cached
};
return result;
} catch (error) {
log.error(`Error setting task status: ${error.message}`);
return {
result = {
success: false,
error: {
code: 'SET_STATUS_ERROR',
@@ -100,6 +103,8 @@ export async function setTaskStatusDirect(args, log) {
// ALWAYS restore normal logging in finally block
disableSilentMode();
}
return result;
} catch (error) {
// Ensure silent mode is disabled if there was an uncaught error in the outer try block
if (isSilentMode()) {

View File

@@ -6,6 +6,7 @@
import { findTaskById } from '../../../../scripts/modules/utils.js';
import { readJSON } from '../../../../scripts/modules/utils.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -15,29 +16,28 @@ import {
* Direct function wrapper for showing task details with error handling and caching.
*
* @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - The ID of the task or subtask to show.
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Task details result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
*/
export async function showTaskDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id } = args;
if (!tasksJsonPath) {
log.error('showTaskDirect called without tasksJsonPath');
export async function showTaskDirect(args, log, { session }) {
let tasksPath;
try {
// Find the tasks path first - needed for cache key and execution
tasksPath = findTasksJsonPath(args, log, session);
} catch (error) {
log.error(`Tasks file not found: ${error.message}`);
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
code: 'FILE_NOT_FOUND_ERROR',
message: error.message
},
fromCache: false
};
}
// Validate task ID
const taskId = id;
const taskId = args.id;
if (!taskId) {
log.error('Task ID is required');
return {
@@ -50,8 +50,8 @@ export async function showTaskDirect(args, log) {
};
}
// Generate cache key using the provided task path and ID
const cacheKey = `showTask:${tasksJsonPath}:${taskId}`;
// Generate cache key using task path and ID
const cacheKey = `showTask:${tasksPath}:${taskId}`;
// Define the action function to be executed on cache miss
const coreShowTaskAction = async () => {
@@ -59,19 +59,16 @@ export async function showTaskDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(
`Retrieving task details for ID: ${taskId} from ${tasksJsonPath}`
);
log.info(`Retrieving task details for ID: ${taskId} from ${tasksPath}`);
// Read tasks data using the provided path
const data = readJSON(tasksJsonPath);
// Read tasks data
const data = readJSON(tasksPath);
if (!data || !data.tasks) {
disableSilentMode(); // Disable before returning
return {
success: false,
error: {
code: 'INVALID_TASKS_FILE',
message: `No valid tasks found in ${tasksJsonPath}`
message: `No valid tasks found in ${tasksPath}`
}
};
}
@@ -80,7 +77,6 @@ export async function showTaskDirect(args, log) {
const task = findTaskById(data.tasks, taskId);
if (!task) {
disableSilentMode(); // Disable before returning
return {
success: false,
error: {

View File

@@ -8,6 +8,7 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
getAnthropicClientForMCP,
getPerplexityClientForMCP
@@ -16,31 +17,17 @@ import {
/**
* Direct function wrapper for updateSubtaskById with error handling.
*
* @param {Object} args - Command arguments containing id, prompt, useResearch and tasksJsonPath.
* @param {Object} args - Command arguments containing id, prompt, useResearch and file path options.
* @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function updateSubtaskByIdDirect(args, log, context = {}) {
const { session } = context; // Only extract session, not reportProgress
const { tasksJsonPath, id, prompt, research } = args;
export async function updateSubtaskByIdDirect(args, log, { session }) {
try {
log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
const errorMessage = 'tasksJsonPath is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
fromCache: false
};
}
// Check required parameters (id and prompt)
if (!id) {
// Check required parameters
if (!args.id) {
const errorMessage =
'No subtask ID specified. Please provide a subtask ID to update.';
log.error(errorMessage);
@@ -51,7 +38,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
};
}
if (!prompt) {
if (!args.prompt) {
const errorMessage =
'No prompt specified. Please provide a prompt with information to add to the subtask.';
log.error(errorMessage);
@@ -63,7 +50,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
}
// Validate subtask ID format
const subtaskId = id;
const subtaskId = args.id;
if (typeof subtaskId !== 'string' && typeof subtaskId !== 'number') {
const errorMessage = `Invalid subtask ID type: ${typeof subtaskId}. Subtask ID must be a string or number.`;
log.error(errorMessage);
@@ -85,14 +72,24 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
};
}
// Use the provided path
const tasksPath = tasksJsonPath;
// Get tasks file path
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log, session);
} catch (error) {
log.error(`Error finding tasks file: ${error.message}`);
return {
success: false,
error: { code: 'TASKS_FILE_ERROR', message: error.message },
fromCache: false
};
}
// Get research flag
const useResearch = research === true;
const useResearch = args.research === true;
log.info(
`Updating subtask with ID ${subtaskIdStr} with prompt "${prompt}" and research: ${useResearch}`
`Updating subtask with ID ${subtaskIdStr} with prompt "${args.prompt}" and research: ${useResearch}`
);
// Initialize the appropriate AI client based on research flag
@@ -135,7 +132,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
const updatedSubtask = await updateSubtaskById(
tasksPath,
subtaskIdStr,
prompt,
args.prompt,
useResearch,
{
session,

View File

@@ -4,6 +4,7 @@
*/
import { updateTaskById } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -16,32 +17,17 @@ import {
/**
* Direct function wrapper for updateTaskById with error handling.
*
* @param {Object} args - Command arguments containing id, prompt, useResearch and tasksJsonPath.
* @param {Object} args - Command arguments containing id, prompt, useResearch and file path options.
* @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function updateTaskByIdDirect(args, log, context = {}) {
const { session } = context; // Only extract session, not reportProgress
// Destructure expected args, including the resolved tasksJsonPath
const { tasksJsonPath, id, prompt, research } = args;
export async function updateTaskByIdDirect(args, log, { session }) {
try {
log.info(`Updating task with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
const errorMessage = 'tasksJsonPath is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
fromCache: false
};
}
// Check required parameters (id and prompt)
if (!id) {
// Check required parameters
if (!args.id) {
const errorMessage =
'No task ID specified. Please provide a task ID to update.';
log.error(errorMessage);
@@ -52,7 +38,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
};
}
if (!prompt) {
if (!args.prompt) {
const errorMessage =
'No prompt specified. Please provide a prompt with new information for the task update.';
log.error(errorMessage);
@@ -65,15 +51,15 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
// Parse taskId - handle both string and number values
let taskId;
if (typeof id === 'string') {
if (typeof args.id === 'string') {
// Handle subtask IDs (e.g., "5.2")
if (id.includes('.')) {
taskId = id; // Keep as string for subtask IDs
if (args.id.includes('.')) {
taskId = args.id; // Keep as string for subtask IDs
} else {
// Parse as integer for main task IDs
taskId = parseInt(id, 10);
taskId = parseInt(args.id, 10);
if (isNaN(taskId)) {
const errorMessage = `Invalid task ID: ${id}. Task ID must be a positive integer or subtask ID (e.g., "5.2").`;
const errorMessage = `Invalid task ID: ${args.id}. Task ID must be a positive integer or subtask ID (e.g., "5.2").`;
log.error(errorMessage);
return {
success: false,
@@ -83,14 +69,24 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
}
}
} else {
taskId = id;
taskId = args.id;
}
// Use the provided path
const tasksPath = tasksJsonPath;
// Get tasks file path
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log, session);
} catch (error) {
log.error(`Error finding tasks file: ${error.message}`);
return {
success: false,
error: { code: 'TASKS_FILE_ERROR', message: error.message },
fromCache: false
};
}
// Get research flag
const useResearch = research === true;
const useResearch = args.research === true;
// Initialize appropriate AI client based on research flag
let aiClient;
@@ -115,7 +111,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
}
log.info(
`Updating task with ID ${taskId} with prompt "${prompt}" and research: ${useResearch}`
`Updating task with ID ${taskId} with prompt "${args.prompt}" and research: ${useResearch}`
);
try {
@@ -135,7 +131,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
await updateTaskById(
tasksPath,
taskId,
prompt,
args.prompt,
useResearch,
{
mcpLog: logWrapper, // Use our wrapper object that has the expected method structure
@@ -151,7 +147,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
data: {
message: `Successfully updated task with ID ${taskId} based on the prompt`,
taskId,
tasksPath: tasksPath, // Return the used path
tasksPath,
useResearch
},
fromCache: false // This operation always modifies state and should never be cached

View File

@@ -8,6 +8,7 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
getAnthropicClientForMCP,
getPerplexityClientForMCP
@@ -16,31 +17,17 @@ import {
/**
* Direct function wrapper for updating tasks based on new context/prompt.
*
* @param {Object} args - Command arguments containing fromId, prompt, useResearch and tasksJsonPath.
* @param {Object} args - Command arguments containing fromId, prompt, useResearch and file path options.
* @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function updateTasksDirect(args, log, context = {}) {
const { session } = context; // Only extract session, not reportProgress
const { tasksJsonPath, from, prompt, research } = args;
export async function updateTasksDirect(args, log, { session }) {
try {
log.info(`Updating tasks with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
const errorMessage = 'tasksJsonPath is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
fromCache: false
};
}
// Check for the common mistake of using 'id' instead of 'from'
if (args.id !== undefined && from === undefined) {
if (args.id !== undefined && args.from === undefined) {
const errorMessage =
"You specified 'id' parameter but 'update' requires 'from' parameter. Use 'from' for this tool or use 'update_task' tool if you want to update a single task.";
log.error(errorMessage);
@@ -57,7 +44,7 @@ export async function updateTasksDirect(args, log, context = {}) {
}
// Check required parameters
if (!from) {
if (!args.from) {
const errorMessage =
'No from ID specified. Please provide a task ID to start updating from.';
log.error(errorMessage);
@@ -68,7 +55,7 @@ export async function updateTasksDirect(args, log, context = {}) {
};
}
if (!prompt) {
if (!args.prompt) {
const errorMessage =
'No prompt specified. Please provide a prompt with new context for task updates.';
log.error(errorMessage);
@@ -81,10 +68,10 @@ export async function updateTasksDirect(args, log, context = {}) {
// Parse fromId - handle both string and number values
let fromId;
if (typeof from === 'string') {
fromId = parseInt(from, 10);
if (typeof args.from === 'string') {
fromId = parseInt(args.from, 10);
if (isNaN(fromId)) {
const errorMessage = `Invalid from ID: ${from}. Task ID must be a positive integer.`;
const errorMessage = `Invalid from ID: ${args.from}. Task ID must be a positive integer.`;
log.error(errorMessage);
return {
success: false,
@@ -93,11 +80,24 @@ export async function updateTasksDirect(args, log, context = {}) {
};
}
} else {
fromId = from;
fromId = args.from;
}
// Get tasks file path
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log, session);
} catch (error) {
log.error(`Error finding tasks file: ${error.message}`);
return {
success: false,
error: { code: 'TASKS_FILE_ERROR', message: error.message },
fromCache: false
};
}
// Get research flag
const useResearch = research === true;
const useResearch = args.research === true;
// Initialize appropriate AI client based on research flag
let aiClient;
@@ -122,25 +122,16 @@ export async function updateTasksDirect(args, log, context = {}) {
}
log.info(
`Updating tasks from ID ${fromId} with prompt "${prompt}" and research: ${useResearch}`
`Updating tasks from ID ${fromId} with prompt "${args.prompt}" and research: ${useResearch}`
);
// Create the logger wrapper to ensure compatibility with core functions
const logWrapper = {
info: (message, ...args) => log.info(message, ...args),
warn: (message, ...args) => log.warn(message, ...args),
error: (message, ...args) => log.error(message, ...args),
debug: (message, ...args) => log.debug && log.debug(message, ...args), // Handle optional debug
success: (message, ...args) => log.info(message, ...args) // Map success to info if needed
};
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Execute core updateTasks function, passing the AI client and session
await updateTasks(tasksJsonPath, fromId, prompt, useResearch, {
mcpLog: logWrapper, // Pass the wrapper instead of the raw log object
await updateTasks(tasksPath, fromId, args.prompt, useResearch, {
mcpLog: log,
session
});
@@ -151,7 +142,7 @@ export async function updateTasksDirect(args, log, context = {}) {
data: {
message: `Successfully updated tasks from ID ${fromId} based on the prompt`,
fromId,
tasksPath: tasksJsonPath,
tasksPath,
useResearch
},
fromCache: false // This operation always modifies state and should never be cached

View File

@@ -3,6 +3,7 @@
*/
import { validateDependenciesCommand } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -12,30 +13,17 @@ import fs from 'fs';
/**
* Validate dependencies in tasks.json
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function validateDependenciesDirect(args, log) {
// Destructure the explicit tasksJsonPath
const { tasksJsonPath } = args;
if (!tasksJsonPath) {
log.error('validateDependenciesDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
export async function validateDependenciesDirect(args, log, { session }) {
try {
log.info(`Validating dependencies in tasks: ${tasksJsonPath}`);
log.info(`Validating dependencies in tasks...`);
// Use the provided tasksJsonPath
const tasksPath = tasksJsonPath;
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log, session);
// Verify the file exists
if (!fs.existsSync(tasksPath)) {
@@ -51,7 +39,7 @@ export async function validateDependenciesDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the original command function using the provided tasksPath
// Call the original command function
await validateDependenciesCommand(tasksPath);
// Restore normal logging

View File

@@ -12,11 +12,11 @@ import path from 'path';
import fs from 'fs';
import { fileURLToPath } from 'url';
import os from 'os';
// Removed lastFoundProjectRoot as it's not suitable for MCP server
// Assuming getProjectRootFromSession is available
import { getProjectRootFromSession } from '../../tools/utils.js';
// Store last found project root to improve performance on subsequent calls (primarily for CLI)
export let lastFoundProjectRoot = null;
// Project marker files that indicate a potential project root
// Project marker files that indicate a potential project root (can be kept for potential future use or logging)
export const PROJECT_MARKERS = [
// Task Master specific
'tasks.json',
@@ -75,109 +75,142 @@ export function getPackagePath() {
}
/**
* Finds the absolute path to the tasks.json file based on project root and arguments.
* Finds the absolute path to the tasks.json file and returns the validated project root.
* Determines the project root using args and session, validates it, searches for tasks.json.
*
* @param {Object} args - Command arguments, potentially including 'projectRoot' and 'file'.
* @param {Object} log - Logger object.
* @returns {string} - Absolute path to the tasks.json file.
* @throws {Error} - If tasks.json cannot be found.
* @param {Object} session - MCP session object.
* @returns {Promise<{tasksPath: string, validatedProjectRoot: string}>} - Object containing absolute path to tasks.json and the validated root.
* @throws {Error} - If a valid project root cannot be determined or tasks.json cannot be found.
*/
export function findTasksJsonPath(args, log) {
// PRECEDENCE ORDER for finding tasks.json:
// 1. Explicitly provided `projectRoot` in args (Highest priority, expected in MCP context)
// 2. Previously found/cached `lastFoundProjectRoot` (primarily for CLI performance)
// 3. Search upwards from current working directory (`process.cwd()`) - CLI usage
export function findTasksJsonPath(args, log, session) {
const homeDir = os.homedir();
let targetDirectory = null;
let rootSource = 'unknown';
// 1. If project root is explicitly provided (e.g., from MCP session), use it directly
if (args.projectRoot) {
const projectRoot = args.projectRoot;
log.info(`Using explicitly provided project root: ${projectRoot}`);
try {
// This will throw if tasks.json isn't found within this root
return findTasksJsonInDirectory(projectRoot, args.file, log);
} catch (error) {
// Include debug info in error
const debugInfo = {
projectRoot,
currentDir: process.cwd(),
serverDir: path.dirname(process.argv[1]),
possibleProjectRoot: path.resolve(
path.dirname(process.argv[1]),
'../..'
),
lastFoundProjectRoot,
searchedPaths: error.message
log.info(
`Finding tasks.json path. Args: ${JSON.stringify(args)}, Session available: ${!!session}`
);
// --- Determine Target Directory ---
if (
args.projectRoot &&
args.projectRoot !== '/' &&
args.projectRoot !== homeDir
) {
log.info(`Using projectRoot directly from args: ${args.projectRoot}`);
targetDirectory = args.projectRoot;
rootSource = 'args.projectRoot';
} else {
log.warn(
`args.projectRoot ('${args.projectRoot}') is missing or invalid. Attempting to derive from session.`
);
const sessionDerivedPath = getProjectRootFromSession(session, log);
if (
sessionDerivedPath &&
sessionDerivedPath !== '/' &&
sessionDerivedPath !== homeDir
) {
log.info(
`Using project root derived from session: ${sessionDerivedPath}`
);
targetDirectory = sessionDerivedPath;
rootSource = 'session';
} else {
log.error(
`Could not derive a valid project root from session. Session path='${sessionDerivedPath}'`
);
}
}
// --- Validate the final targetDirectory ---
if (!targetDirectory) {
const error = new Error(
`Cannot find tasks.json: Could not determine a valid project root directory. Please ensure a workspace/folder is open or specify projectRoot.`
);
error.code = 'INVALID_PROJECT_ROOT';
error.details = {
attemptedArgsProjectRoot: args.projectRoot,
sessionAvailable: !!session,
// Add session derived path attempt for better debugging
attemptedSessionDerivedPath: getProjectRootFromSession(session, {
info: () => {},
warn: () => {},
error: () => {}
}), // Call again silently for details
finalDeterminedRoot: targetDirectory // Will be null here
};
error.message = `Tasks file not found in any of the expected locations relative to project root "${projectRoot}" (from session).\nDebug Info: ${JSON.stringify(debugInfo, null, 2)}`;
log.error(`Validation failed: ${error.message}`, error.details);
throw error;
}
// --- Verify targetDirectory exists ---
if (!fs.existsSync(targetDirectory)) {
const error = new Error(
`Determined project root directory does not exist: ${targetDirectory}`
);
error.code = 'PROJECT_ROOT_NOT_FOUND';
error.details = {
/* ... add details ... */
};
log.error(error.message, error.details);
throw error;
}
if (!fs.statSync(targetDirectory).isDirectory()) {
const error = new Error(
`Determined project root path is not a directory: ${targetDirectory}`
);
error.code = 'PROJECT_ROOT_NOT_A_DIRECTORY';
error.details = {
/* ... add details ... */
};
log.error(error.message, error.details);
throw error;
}
// --- Fallback logic primarily for CLI or when projectRoot isn't passed ---
// 2. If we have a last known project root that worked, try it first
if (lastFoundProjectRoot) {
log.info(`Trying last known project root: ${lastFoundProjectRoot}`);
try {
// Use the cached root
const tasksPath = findTasksJsonInDirectory(
lastFoundProjectRoot,
args.file,
log
);
return tasksPath; // Return if found in cached root
} catch (error) {
// --- Search within the validated targetDirectory ---
log.info(
`Task file not found in last known project root, continuing search.`
`Validated project root (${rootSource}): ${targetDirectory}. Searching for tasks file.`
);
// Continue with search if not found in cache
}
}
// 3. Start search from current directory (most common CLI scenario)
const startDir = process.cwd();
log.info(
`Searching for tasks.json starting from current directory: ${startDir}`
);
// Try to find tasks.json by walking up the directory tree from cwd
try {
// This will throw if not found in the CWD tree
return findTasksJsonWithParentSearch(startDir, args.file, log);
const tasksPath = findTasksJsonInDirectory(targetDirectory, args.file, log);
// Return both the tasks path and the validated root
return { tasksPath: tasksPath, validatedProjectRoot: targetDirectory };
} catch (error) {
// If all attempts fail, augment and throw the original error from CWD search
error.message = `${error.message}\n\nPossible solutions:\n1. Run the command from your project directory containing tasks.json\n2. Use --project-root=/path/to/project to specify the project location (if using CLI)\n3. Ensure the project root is correctly passed from the client (if using MCP)\n\nCurrent working directory: ${startDir}\nLast known project root: ${lastFoundProjectRoot}\nProject root from args: ${args.projectRoot}`;
// Augment the error
error.message = `Tasks file not found within validated project root "${targetDirectory}" (source: ${rootSource}). Ensure 'tasks.json' exists at the root or in a 'tasks/' subdirectory.\nOriginal Error: ${error.message}`;
error.details = {
...(error.details || {}), // Keep original details if any
validatedProjectRoot: targetDirectory,
rootSource: rootSource,
attemptedArgsProjectRoot: args.projectRoot,
sessionAvailable: !!session
};
log.error(`Search failed: ${error.message}`, error.details);
throw error;
}
}
/**
* Check if a directory contains any project marker files or directories
* @param {string} dirPath - Directory to check
* @returns {boolean} - True if the directory contains any project markers
*/
function hasProjectMarkers(dirPath) {
return PROJECT_MARKERS.some((marker) => {
const markerPath = path.join(dirPath, marker);
// Check if the marker exists as either a file or directory
return fs.existsSync(markerPath);
});
}
/**
* Search for tasks.json in a specific directory
* @param {string} dirPath - Directory to search in
* @param {string} explicitFilePath - Optional explicit file path relative to dirPath
* Search for tasks.json in a specific directory (now assumes dirPath is a validated project root)
* @param {string} dirPath - The validated project root directory to search in.
* @param {string} explicitFilePath - Optional explicit file path relative to dirPath (e.g., args.file)
* @param {Object} log - Logger object
* @returns {string} - Absolute path to tasks.json
* @throws {Error} - If tasks.json cannot be found
* @throws {Error} - If tasks.json cannot be found in the standard locations within dirPath.
*/
function findTasksJsonInDirectory(dirPath, explicitFilePath, log) {
const possiblePaths = [];
// 1. If a file is explicitly provided relative to dirPath
// 1. If an explicit file path is provided (relative to dirPath)
if (explicitFilePath) {
possiblePaths.push(path.resolve(dirPath, explicitFilePath));
// Ensure it's treated as relative to the project root if not absolute
const resolvedExplicitPath = path.isAbsolute(explicitFilePath)
? explicitFilePath
: path.resolve(dirPath, explicitFilePath);
possiblePaths.push(resolvedExplicitPath);
log.info(`Explicit file path provided, checking: ${resolvedExplicitPath}`);
}
// 2. Check the standard locations relative to dirPath
@@ -186,208 +219,152 @@ function findTasksJsonInDirectory(dirPath, explicitFilePath, log) {
path.join(dirPath, 'tasks', 'tasks.json')
);
log.info(`Checking potential task file paths: ${possiblePaths.join(', ')}`);
// Deduplicate paths in case explicitFilePath matches a standard location
const uniquePaths = [...new Set(possiblePaths)];
log.info(
`Checking for tasks file in validated root ${dirPath}. Potential paths: ${uniquePaths.join(', ')}`
);
// Find the first existing path
for (const p of possiblePaths) {
log.info(`Checking if exists: ${p}`);
for (const p of uniquePaths) {
// log.info(`Checking if exists: ${p}`); // Can reduce verbosity
const exists = fs.existsSync(p);
log.info(`Path ${p} exists: ${exists}`);
// log.info(`Path ${p} exists: ${exists}`); // Can reduce verbosity
if (exists) {
log.info(`Found tasks file at: ${p}`);
// Store the project root for future use
lastFoundProjectRoot = dirPath;
// No need to set lastFoundProjectRoot anymore
return p;
}
}
// If no file was found, throw an error
const error = new Error(
`Tasks file not found in any of the expected locations relative to ${dirPath}: ${possiblePaths.join(', ')}`
`Tasks file not found in any of the expected locations within directory ${dirPath}: ${uniquePaths.join(', ')}`
);
error.code = 'TASKS_FILE_NOT_FOUND';
error.code = 'TASKS_FILE_NOT_FOUND_IN_ROOT';
error.details = { searchedDirectory: dirPath, checkedPaths: uniquePaths };
throw error;
}
// Removed findTasksJsonWithParentSearch, hasProjectMarkers, and findTasksWithNpmConsideration
// as the project root is now determined upfront and validated.
/**
* Recursively search for tasks.json in the given directory and parent directories
* Also looks for project markers to identify potential project roots
* @param {string} startDir - Directory to start searching from
* @param {string} explicitFilePath - Optional explicit file path
* @param {Object} log - Logger object
* @returns {string} - Absolute path to tasks.json
* @throws {Error} - If tasks.json cannot be found in any parent directory
* Resolves a relative path against the project root, ensuring it's within the project.
* @param {string} relativePath - The relative path (e.g., 'scripts/report.json').
* @param {string} projectRoot - The validated absolute path to the project root.
* @param {Object} log - Logger object.
* @returns {string} - The absolute path.
* @throws {Error} - If the resolved path is outside the project root or resolution fails.
*/
function findTasksJsonWithParentSearch(startDir, explicitFilePath, log) {
let currentDir = startDir;
const rootDir = path.parse(currentDir).root;
// Keep traversing up until we hit the root directory
while (currentDir !== rootDir) {
// First check for tasks.json directly
try {
return findTasksJsonInDirectory(currentDir, explicitFilePath, log);
} catch (error) {
// If tasks.json not found but the directory has project markers,
// log it as a potential project root (helpful for debugging)
if (hasProjectMarkers(currentDir)) {
log.info(`Found project markers in ${currentDir}, but no tasks.json`);
export function resolveProjectPath(relativePath, projectRoot, log) {
if (!projectRoot || !path.isAbsolute(projectRoot)) {
log.error(
`Cannot resolve project path: Invalid projectRoot provided: ${projectRoot}`
);
throw new Error(
`Internal Error: Cannot resolve project path due to invalid projectRoot: ${projectRoot}`
);
}
if (!relativePath || typeof relativePath !== 'string') {
log.error(
`Cannot resolve project path: Invalid relativePath provided: ${relativePath}`
);
throw new Error(
`Internal Error: Cannot resolve project path due to invalid relativePath: ${relativePath}`
);
}
// Move up to parent directory
const parentDir = path.dirname(currentDir);
// Check if we've reached the root
if (parentDir === currentDir) {
break;
// If relativePath is already absolute, check if it's within the project root
if (path.isAbsolute(relativePath)) {
if (!relativePath.startsWith(projectRoot)) {
log.error(
`Path Security Violation: Absolute path \"${relativePath}\" provided is outside the project root \"${projectRoot}\"`
);
throw new Error(
`Provided absolute path is outside the project directory: ${relativePath}`
);
}
log.info(
`Tasks file not found in ${currentDir}, searching in parent directory: ${parentDir}`
`Provided path is already absolute and within project root: ${relativePath}`
);
currentDir = parentDir;
}
return relativePath; // Return as is if valid absolute path within project
}
// If we've searched all the way to the root and found nothing
const error = new Error(
`Tasks file not found in ${startDir} or any parent directory.`
// Resolve relative path against project root
const absolutePath = path.resolve(projectRoot, relativePath);
// Security check: Ensure the resolved path is still within the project root boundary
// Normalize paths to handle potential .. usages properly before comparison
const normalizedAbsolutePath = path.normalize(absolutePath);
const normalizedProjectRoot = path.normalize(projectRoot + path.sep); // Ensure trailing separator for accurate startsWith check
if (
!normalizedAbsolutePath.startsWith(normalizedProjectRoot) &&
normalizedAbsolutePath !== path.normalize(projectRoot)
) {
log.error(
`Path Security Violation: Resolved path \"${normalizedAbsolutePath}\" is outside project root \"${normalizedProjectRoot}\"`
);
throw new Error(
`Resolved path is outside the project directory: ${relativePath}`
);
error.code = 'TASKS_FILE_NOT_FOUND';
throw error;
}
// Note: findTasksWithNpmConsideration is not used by findTasksJsonPath and might be legacy or used elsewhere.
// If confirmed unused, it could potentially be removed in a separate cleanup.
function findTasksWithNpmConsideration(startDir, log) {
// First try our recursive parent search from cwd
log.info(`Resolved project path: \"${relativePath}\" -> \"${absolutePath}\"`);
return absolutePath;
}
/**
* Ensures a directory exists, creating it if necessary.
* Also verifies that if the path already exists, it is indeed a directory.
* @param {string} dirPath - The absolute path to the directory.
* @param {Object} log - Logger object.
*/
export function ensureDirectoryExists(dirPath, log) {
// Validate dirPath is an absolute path before proceeding
if (!path.isAbsolute(dirPath)) {
log.error(
`Cannot ensure directory: Path provided is not absolute: ${dirPath}`
);
throw new Error(
`Internal Error: ensureDirectoryExists requires an absolute path.`
);
}
if (!fs.existsSync(dirPath)) {
log.info(`Directory does not exist, creating recursively: ${dirPath}`);
try {
return findTasksJsonWithParentSearch(startDir, null, log);
fs.mkdirSync(dirPath, { recursive: true });
log.info(`Successfully created directory: ${dirPath}`);
} catch (error) {
// If that fails, try looking relative to the executable location
const execPath = process.argv[1];
const execDir = path.dirname(execPath);
log.info(`Looking for tasks file relative to executable at: ${execDir}`);
try {
return findTasksJsonWithParentSearch(execDir, null, log);
} catch (secondError) {
// If that also fails, check standard locations in user's home directory
const homeDir = os.homedir();
log.info(`Looking for tasks file in home directory: ${homeDir}`);
try {
// Check standard locations in home dir
return findTasksJsonInDirectory(
path.join(homeDir, '.task-master'),
null,
log
log.error(`Failed to create directory ${dirPath}: ${error.message}`);
// Re-throw the error after logging
throw new Error(
`Could not create directory: ${dirPath}. Reason: ${error.message}`
);
} catch (thirdError) {
// If all approaches fail, throw the original error
throw error;
}
}
}
}
/**
* Finds potential PRD document files based on common naming patterns
* @param {string} projectRoot - The project root directory
* @param {string|null} explicitPath - Optional explicit path provided by the user
* @param {Object} log - Logger object
* @returns {string|null} - The path to the first found PRD file, or null if none found
*/
export function findPRDDocumentPath(projectRoot, explicitPath, log) {
// If explicit path is provided, check if it exists
if (explicitPath) {
const fullPath = path.isAbsolute(explicitPath)
? explicitPath
: path.resolve(projectRoot, explicitPath);
if (fs.existsSync(fullPath)) {
log.info(`Using provided PRD document path: ${fullPath}`);
return fullPath;
} else {
log.warn(
`Provided PRD document path not found: ${fullPath}, will search for alternatives`
// Path exists, verify it's a directory
try {
const stats = fs.statSync(dirPath);
if (!stats.isDirectory()) {
log.error(`Path exists but is not a directory: ${dirPath}`);
throw new Error(
`Expected directory but found file at path: ${dirPath}`
);
}
log.info(`Directory already exists and is valid: ${dirPath}`);
} catch (error) {
// Handle potential errors from statSync (e.g., permissions) or the explicit throw above
log.error(
`Error checking existing directory ${dirPath}: ${error.message}`
);
throw new Error(
`Error verifying existing directory: ${dirPath}. Reason: ${error.message}`
);
}
}
// Common locations and file patterns for PRD documents
const commonLocations = [
'', // Project root
'scripts/'
];
const commonFileNames = ['PRD.md', 'prd.md', 'PRD.txt', 'prd.txt'];
// Check all possible combinations
for (const location of commonLocations) {
for (const fileName of commonFileNames) {
const potentialPath = path.join(projectRoot, location, fileName);
if (fs.existsSync(potentialPath)) {
log.info(`Found PRD document at: ${potentialPath}`);
return potentialPath;
}
}
}
log.warn(`No PRD document found in common locations within ${projectRoot}`);
return null;
}
/**
* Resolves the tasks output directory path
* @param {string} projectRoot - The project root directory
* @param {string|null} explicitPath - Optional explicit output path provided by the user
* @param {Object} log - Logger object
* @returns {string} - The resolved tasks directory path
*/
export function resolveTasksOutputPath(projectRoot, explicitPath, log) {
// If explicit path is provided, use it
if (explicitPath) {
const outputPath = path.isAbsolute(explicitPath)
? explicitPath
: path.resolve(projectRoot, explicitPath);
log.info(`Using provided tasks output path: ${outputPath}`);
return outputPath;
}
// Default output path: tasks/tasks.json in the project root
const defaultPath = path.resolve(projectRoot, 'tasks', 'tasks.json');
log.info(`Using default tasks output path: ${defaultPath}`);
// Ensure the directory exists
const outputDir = path.dirname(defaultPath);
if (!fs.existsSync(outputDir)) {
log.info(`Creating tasks directory: ${outputDir}`);
fs.mkdirSync(outputDir, { recursive: true });
}
return defaultPath;
}
/**
* Resolves various file paths needed for MCP operations based on project root
* @param {string} projectRoot - The project root directory
* @param {Object} args - Command arguments that may contain explicit paths
* @param {Object} log - Logger object
* @returns {Object} - An object containing resolved paths
*/
export function resolveProjectPaths(projectRoot, args, log) {
const prdPath = findPRDDocumentPath(projectRoot, args.input, log);
const tasksJsonPath = resolveTasksOutputPath(projectRoot, args.output, log);
// You can add more path resolutions here as needed
return {
projectRoot,
prdPath,
tasksJsonPath
// Add additional path properties as needed
};
}

View File

@@ -0,0 +1 @@

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { addDependencyDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the addDependency tool with the MCP server
@@ -33,52 +32,39 @@ export function registerAddDependencyTool(server) {
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(
`Adding dependency for task ${args.id} to depend on ${args.dependsOn}`
);
reportProgress({ progress: 0 });
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
// Get project root using the utility function
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
// Fallback to args.projectRoot if session didn't provide one
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Call the direct function with the resolved path
// Call the direct function with the resolved rootFolder
const result = await addDependencyDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
dependsOn: args.dependsOn
projectRoot: rootFolder,
...args
},
log
// Remove context object
log,
{ reportProgress, mcpLog: log, session }
);
reportProgress({ progress: 100 });
// Log result
if (result.success) {
log.info(`Successfully added dependency: ${result.data.message}`);

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { addSubtaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the addSubtask tool with the MCP server
@@ -58,48 +57,29 @@ export function registerAddSubtaskTool(server) {
.describe('Skip regenerating task files'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Adding subtask with args: ${JSON.stringify(args)}`);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
const result = await addSubtaskDirect(
{
tasksJsonPath: tasksJsonPath,
id: args.id,
taskId: args.taskId,
title: args.title,
description: args.description,
details: args.details,
status: args.status,
dependencies: args.dependencies,
skipGenerate: args.skipGenerate
projectRoot: rootFolder,
...args
},
log
log,
{ reportProgress, mcpLog: log, session }
);
if (result.success) {

View File

@@ -12,7 +12,6 @@ import {
handleApiResult
} from './utils.js';
import { addTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the addTask tool with the MCP server
@@ -59,54 +58,35 @@ export function registerAddTaskTool(server) {
.describe('Path to the tasks file (default: tasks/tasks.json)'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
.optional()
.describe(
'Root directory of the project (default: current working directory)'
),
research: z
.boolean()
.optional()
.describe('Whether to use research capabilities for task creation')
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, reportProgress, session }) => {
try {
log.info(`Starting add-task with args: ${JSON.stringify(args)}`);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
// Get project root from session
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
// Call the direct function
const result = await addTaskDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
prompt: args.prompt,
dependencies: args.dependencies,
priority: args.priority,
research: args.research
...args,
projectRoot: rootFolder
},
log,
{ session }
{ reportProgress, session }
);
// Return the result

View File

@@ -6,12 +6,10 @@
import { z } from 'zod';
import {
handleApiResult,
createErrorResponse,
getProjectRootFromSession
createErrorResponse
// getProjectRootFromSession // No longer needed here
} from './utils.js';
import { analyzeTaskComplexityDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
import path from 'path';
/**
* Register the analyze tool with the MCP server
@@ -21,19 +19,18 @@ export function registerAnalyzeTool(server) {
server.addTool({
name: 'analyze_project_complexity',
description:
'Analyze task complexity and generate expansion recommendations',
'Analyze task complexity and generate expansion recommendations. Requires the project root path.',
parameters: z.object({
projectRoot: z
.string()
.describe(
'Required. Absolute path to the root directory of the project being analyzed.'
),
output: z
.string()
.optional()
.describe(
'Output file path for the report (default: scripts/task-complexity-report.json)'
),
model: z
.string()
.optional()
.describe(
'LLM model to use for analysis (defaults to configured model)'
'Output file path for the report, relative to projectRoot (default: scripts/task-complexity-report.json)'
),
threshold: z.coerce
.number()
@@ -41,82 +38,43 @@ export function registerAnalyzeTool(server) {
.max(10)
.optional()
.describe(
'Minimum complexity score to recommend expansion (1-10) (default: 5)'
),
file: z
.string()
.optional()
.describe(
'Absolute path to the tasks file (default: tasks/tasks.json)'
'Minimum complexity score to recommend expansion (1-10) (default: 5). If the complexity score is below this threshold, the tool will not recommend adding subtasks.'
),
research: z
.boolean()
.optional()
.describe('Use Perplexity AI for research-backed complexity analysis'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.describe('Use Perplexity AI for research-backed complexity analysis')
}),
execute: async (args, { log, session }) => {
try {
log.info(
`Analyzing task complexity with args: ${JSON.stringify(args)}`
`Analyzing task complexity with required projectRoot: ${args.projectRoot}, other args: ${JSON.stringify(args)}`
);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
const result = await analyzeTaskComplexityDirect(args, log, {
session
});
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const outputPath = args.output
? path.resolve(rootFolder, args.output)
: path.resolve(rootFolder, 'scripts', 'task-complexity-report.json');
const result = await analyzeTaskComplexityDirect(
{
tasksJsonPath: tasksJsonPath,
outputPath: outputPath,
model: args.model,
threshold: args.threshold,
research: args.research
},
log,
{ session }
);
if (result.success) {
if (result.success && result.data) {
log.info(`Task complexity analysis complete: ${result.data.message}`);
log.info(
`Report summary: ${JSON.stringify(result.data.reportSummary)}`
);
} else {
} else if (!result.success && result.error) {
log.error(
`Failed to analyze task complexity: ${result.error.message}`
`Failed to analyze task complexity: ${result.error.message} (Code: ${result.error.code})`
);
}
return handleApiResult(result, log, 'Error analyzing task complexity');
} catch (error) {
log.error(`Error in analyze tool: ${error.message}`);
return createErrorResponse(error.message);
log.error(
`Unexpected error in analyze tool execute method: ${error.message}`,
{ stack: error.stack }
);
return createErrorResponse(
`Unexpected error in analyze tool: ${error.message}`
);
}
}
});

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { clearSubtasksDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the clearSubtasks tool with the MCP server
@@ -35,53 +34,38 @@ export function registerClearSubtasksTool(server) {
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
})
.refine((data) => data.id || data.all, {
message: "Either 'id' or 'all' parameter must be provided",
path: ['id', 'all']
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
const result = await clearSubtasksDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
all: args.all
projectRoot: rootFolder,
...args
},
log
// Remove context object as clearSubtasksDirect likely doesn't need session/reportProgress
log,
{ reportProgress, mcpLog: log, session }
);
reportProgress({ progress: 100 });
if (result.success) {
log.info(`Subtasks cleared successfully: ${result.data.message}`);
} else {

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { complexityReportDirect } from '../core/task-master-core.js';
import path from 'path';
/**
* Register the complexityReport tool with the MCP server
@@ -29,40 +28,35 @@ export function registerComplexityReportTool(server) {
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(
`Getting complexity report with args: ${JSON.stringify(args)}`
);
// await reportProgress({ progress: 0 });
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
// Resolve the path to the complexity report file
// Default to scripts/task-complexity-report.json relative to root
const reportPath = args.file
? path.resolve(rootFolder, args.file)
: path.resolve(rootFolder, 'scripts', 'task-complexity-report.json');
const result = await complexityReportDirect(
{
// Pass the explicitly resolved path
reportPath: reportPath
// No other args specific to this tool
projectRoot: rootFolder,
...args
},
log
log /*, { reportProgress, mcpLog: log, session}*/
);
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(
`Successfully retrieved complexity report${result.fromCache ? ' (from cache)' : ''}`

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { expandAllTasksDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the expandAll tool with the MCP server
@@ -49,46 +48,26 @@ export function registerExpandAllTool(server) {
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
try {
log.info(`Expanding all tasks with args: ${JSON.stringify(args)}`);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
const result = await expandAllTasksDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
num: args.num,
research: args.research,
prompt: args.prompt,
force: args.force
projectRoot: rootFolder,
...args
},
log,
{ session }

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { expandTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
import fs from 'fs';
import path from 'path';
@@ -24,7 +23,10 @@ export function registerExpandTaskTool(server) {
description: 'Expand a task into subtasks for detailed implementation',
parameters: z.object({
id: z.string().describe('ID of task to expand'),
num: z.string().optional().describe('Number of subtasks to generate'),
num: z
.union([z.string(), z.number()])
.optional()
.describe('Number of subtasks to generate'),
research: z
.boolean()
.optional()
@@ -36,52 +38,42 @@ export function registerExpandTaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
force: z.boolean().optional().describe('Force the expansion')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
try {
log.info(`Starting expand-task with args: ${JSON.stringify(args)}`);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
// Get project root from session
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
log.info(`Project root resolved to: ${rootFolder}`);
// Resolve the path to tasks.json using the utility
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
// Check for tasks.json in the standard locations
const tasksJsonPath = path.join(rootFolder, 'tasks', 'tasks.json');
if (fs.existsSync(tasksJsonPath)) {
log.info(`Found tasks.json at ${tasksJsonPath}`);
// Add the file parameter directly to args
args.file = tasksJsonPath;
} else {
log.warn(`Could not find tasks.json at ${tasksJsonPath}`);
}
// Call direct function with only session in the context, not reportProgress
// Use the pattern recommended in the MCP guidelines
const result = await expandTaskDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
num: args.num,
research: args.research,
prompt: args.prompt,
force: args.force // Need to add force to parameters
...args,
projectRoot: rootFolder
},
log,
{ session }

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { fixDependenciesDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the fixDependencies tool with the MCP server
@@ -24,42 +23,34 @@ export function registerFixDependenciesTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Fixing dependencies with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
const result = await fixDependenciesDirect(
{
tasksJsonPath: tasksJsonPath
projectRoot: rootFolder,
...args
},
log
log,
{ reportProgress, mcpLog: log, session }
);
await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully fixed dependencies: ${result.data.message}`);
} else {

View File

@@ -10,8 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { generateTaskFilesDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
import path from 'path';
/**
* Register the generate tool with the MCP server
@@ -30,52 +28,33 @@ export function registerGenerateTool(server) {
.describe('Output directory (default: same directory as tasks file)'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Generating task files with args: ${JSON.stringify(args)}`);
// await reportProgress({ progress: 0 });
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Determine output directory: use explicit arg or default to tasks.json directory
const outputDir = args.output
? path.resolve(rootFolder, args.output) // Resolve relative to root if needed
: path.dirname(tasksJsonPath);
const result = await generateTaskFilesDirect(
{
// Pass the explicitly resolved paths
tasksJsonPath: tasksJsonPath,
outputDir: outputDir
// No other args specific to this tool
projectRoot: rootFolder,
...args
},
log
log /*, { reportProgress, mcpLog: log, session}*/
);
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully generated task files: ${result.data.message}`);
} else {

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { showTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Custom processor function that removes allTasks from the response
@@ -43,9 +42,12 @@ export function registerShowTaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, session, reportProgress }) => {
// Log the session right at the start of execute
log.info(
`Session object received in execute: ${JSON.stringify(session)}`
@@ -58,43 +60,26 @@ export function registerShowTaskTool(server) {
`Session object received in execute: ${JSON.stringify(session)}`
); // Use JSON.stringify for better visibility
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
} else if (!rootFolder) {
// Ensure we always have *some* root, even if session failed and args didn't provide one
rootFolder = process.cwd();
log.warn(
`Session and args failed to provide root, using CWD: ${rootFolder}`
);
}
log.info(`Attempting to use project root: ${rootFolder}`); // Log the final resolved root
log.info(`Root folder: ${rootFolder}`); // Log the final resolved root
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
log.info(`Attempting to use tasks file path: ${tasksJsonPath}`);
const result = await showTaskDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id
projectRoot: rootFolder,
...args
},
log
);

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { listTasksDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the getTasks tool with the MCP server
@@ -40,47 +39,33 @@ export function registerListTasksTool(server) {
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: automatically detected from session or CWD)'
)
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Getting tasks with filters: ${JSON.stringify(args)}`);
// await reportProgress({ progress: 0 });
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
// Use the error message from findTasksJsonPath for better context
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
const result = await listTasksDirect(
{
tasksJsonPath: tasksJsonPath,
status: args.status,
withSubtasks: args.withSubtasks
projectRoot: rootFolder,
...args
},
log
log /*, { reportProgress, mcpLog: log, session}*/
);
// await reportProgress({ progress: 100 });
log.info(
`Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks${result.fromCache ? ' (from cache)' : ''}`
);

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { nextTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the next-task tool with the MCP server
@@ -25,46 +24,33 @@ export function registerNextTaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Finding next task with args: ${JSON.stringify(args)}`);
// await reportProgress({ progress: 0 });
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
const result = await nextTaskDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath
// No other args specific to this tool
projectRoot: rootFolder,
...args
},
log
log /*, { reportProgress, mcpLog: log, session}*/
);
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(
`Successfully found next task: ${result.data?.task?.id || 'No available tasks'}`

View File

@@ -5,16 +5,11 @@
import { z } from 'zod';
import {
getProjectRootFromSession,
handleApiResult,
createErrorResponse
createErrorResponse,
getProjectRootFromSession
} from './utils.js';
import { parsePRDDirect } from '../core/task-master-core.js';
import {
resolveProjectPaths,
findPRDDocumentPath,
resolveTasksOutputPath
} from '../core/utils/path-utils.js';
/**
* Register the parsePRD tool with the MCP server
@@ -28,7 +23,6 @@ export function registerParsePRDTool(server) {
parameters: z.object({
input: z
.string()
.optional()
.default('scripts/prd.txt')
.describe('Absolute path to the PRD document file (.txt, .md, etc.)'),
numTasks: z
@@ -41,7 +35,7 @@ export function registerParsePRDTool(server) {
.string()
.optional()
.describe(
'Output path for tasks.json file (default: tasks/tasks.json)'
'Output absolute path for tasks.json file (default: tasks/tasks.json)'
),
force: z
.boolean()
@@ -49,44 +43,39 @@ export function registerParsePRDTool(server) {
.describe('Allow overwriting an existing tasks.json file.'),
projectRoot: z
.string()
.describe('The directory of the project. Must be absolute path.')
.describe(
'Absolute path to the root directory of the project. Required - ALWAYS SET THIS TO THE PROJECT ROOT DIRECTORY.'
)
}),
execute: async (args, { log, session }) => {
try {
log.info(`Parsing PRD with args: ${JSON.stringify(args)}`);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
// Make sure projectRoot is passed directly in args or derive from session
// We prioritize projectRoot from args over session-derived path
let rootFolder = args.projectRoot;
// Only if args.projectRoot is undefined or null, try to get it from session
if (!rootFolder) {
log.warn(
'projectRoot not provided in args, attempting to derive from session'
);
rootFolder = getProjectRootFromSession(session, log);
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
const errorMessage =
'Could not determine project root directory. Please provide projectRoot parameter.';
log.error(errorMessage);
return createErrorResponse(errorMessage);
}
}
// Resolve input (PRD) and output (tasks.json) paths using the utility
const { projectRoot, prdPath, tasksJsonPath } = resolveProjectPaths(
rootFolder,
args,
log
);
log.info(`Using project root: ${rootFolder} for PRD parsing`);
// Check if PRD path was found (resolveProjectPaths returns null if not found and not provided)
if (!prdPath) {
return createErrorResponse(
'No PRD document found or provided. Please ensure a PRD file exists (e.g., PRD.md) or provide a valid input file path.'
);
}
// Call the direct function with fully resolved paths
const result = await parsePRDDirect(
{
projectRoot: projectRoot,
input: prdPath,
output: tasksJsonPath,
numTasks: args.numTasks,
force: args.force
projectRoot: rootFolder,
...args
},
log,
{ session }

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { removeDependencyDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the removeDependency tool with the MCP server
@@ -31,50 +30,35 @@ export function registerRemoveDependencyTool(server) {
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(
`Removing dependency for task ${args.id} from ${args.dependsOn} with args: ${JSON.stringify(args)}`
);
// await reportProgress({ progress: 0 });
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
const result = await removeDependencyDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
dependsOn: args.dependsOn
projectRoot: rootFolder,
...args
},
log
log /*, { reportProgress, mcpLog: log, session}*/
);
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully removed dependency: ${result.data.message}`);
} else {

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { removeSubtaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the removeSubtask tool with the MCP server
@@ -44,49 +43,33 @@ export function registerRemoveSubtaskTool(server) {
.describe('Skip regenerating task files'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
// await reportProgress({ progress: 0 });
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
const result = await removeSubtaskDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
convert: args.convert,
skipGenerate: args.skipGenerate
projectRoot: rootFolder,
...args
},
log
log /*, { reportProgress, mcpLog: log, session}*/
);
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Subtask removed successfully: ${result.data.message}`);
} else {

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { removeTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the remove-task tool with the MCP server
@@ -27,7 +26,10 @@ export function registerRemoveTaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
.optional()
.describe(
'Root directory of the project (default: current working directory)'
),
confirm: z
.boolean()
.optional()
@@ -37,40 +39,28 @@ export function registerRemoveTaskTool(server) {
try {
log.info(`Removing task with ID: ${args.id}`);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
// Get project root from session
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
} else if (!rootFolder) {
// Ensure we have a default if nothing else works
rootFolder = process.cwd();
log.warn(
`Session and args failed to provide root, using CWD: ${rootFolder}`
);
}
log.info(`Using project root: ${rootFolder}`);
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
log.info(`Using tasks file path: ${tasksJsonPath}`);
// Assume client has already handled confirmation if needed
const result = await removeTaskDirect(
{
tasksJsonPath: tasksJsonPath,
id: args.id
id: args.id,
file: args.file,
projectRoot: rootFolder
},
log
);

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { setTaskStatusDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the setTaskStatus tool with the MCP server
@@ -34,45 +33,28 @@ export function registerSetTaskStatusTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: automatically detected)'
)
}),
execute: async (args, { log, session }) => {
try {
log.info(`Setting status of task(s) ${args.id} to: ${args.status}`);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
// Get project root from session
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Call the direct function with the resolved path
// Call the direct function with the project root
const result = await setTaskStatusDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
status: args.status
...args,
projectRoot: rootFolder
},
log
);

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { updateSubtaskByIdDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the update-subtask tool with the MCP server
@@ -35,45 +34,26 @@ export function registerUpdateSubtaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
try {
log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
const result = await updateSubtaskByIdDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
prompt: args.prompt,
research: args.research
projectRoot: rootFolder,
...args
},
log,
{ session }

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { updateTaskByIdDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the update-task tool with the MCP server
@@ -35,45 +34,26 @@ export function registerUpdateTaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
try {
log.info(`Updating task with args: ${JSON.stringify(args)}`);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
const result = await updateTaskByIdDirect(
{
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
prompt: args.prompt,
research: args.research
projectRoot: rootFolder,
...args
},
log,
{ session }

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { updateTasksDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the update tool with the MCP server
@@ -37,43 +36,26 @@ export function registerUpdateTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
try {
log.info(`Updating tasks with args: ${JSON.stringify(args)}`);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
const result = await updateTasksDirect(
{
tasksJsonPath: tasksJsonPath,
from: args.from,
prompt: args.prompt,
research: args.research
projectRoot: rootFolder,
...args
},
log,
{ session }

View File

@@ -9,10 +9,7 @@ import fs from 'fs';
import { contextManager } from '../core/context-manager.js'; // Import the singleton
// Import path utilities to ensure consistent path resolution
import {
lastFoundProjectRoot,
PROJECT_MARKERS
} from '../core/utils/path-utils.js';
import { PROJECT_MARKERS } from '../core/utils/path-utils.js';
/**
* Get normalized project root path

View File

@@ -10,7 +10,6 @@ import {
getProjectRootFromSession
} from './utils.js';
import { validateDependenciesDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the validateDependencies tool with the MCP server
@@ -25,42 +24,34 @@ export function registerValidateDependenciesTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.')
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
}),
execute: async (args, { log, session }) => {
execute: async (args, { log, session, reportProgress }) => {
try {
log.info(`Validating dependencies with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
let rootFolder = getProjectRootFromSession(session, log);
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
}
const result = await validateDependenciesDirect(
{
tasksJsonPath: tasksJsonPath
projectRoot: rootFolder,
...args
},
log
log,
{ reportProgress, mcpLog: log, session }
);
await reportProgress({ progress: 100 });
if (result.success) {
log.info(
`Successfully validated dependencies: ${result.data.message}`

View File

@@ -1,6 +1,6 @@
{
"name": "task-master-ai",
"version": "0.11.0",
"version": "0.10.1",
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
"main": "index.js",
"type": "module",

View File

@@ -23,7 +23,23 @@ import chalk from 'chalk';
import figlet from 'figlet';
import boxen from 'boxen';
import gradient from 'gradient-string';
import { isSilentMode } from './modules/utils.js';
import {
isSilentMode,
enableSilentMode,
disableSilentMode
} from './modules/utils.js';
// Only log if not in silent mode
if (!isSilentMode()) {
console.log('Starting task-master-ai...');
}
// Debug information - only log if not in silent mode
if (!isSilentMode()) {
console.log('Node version:', process.version);
console.log('Current directory:', process.cwd());
console.log('Script path:', import.meta.url);
}
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
@@ -895,12 +911,12 @@ function setupMCPConfiguration(targetDir, projectName) {
command: 'npx',
args: ['-y', 'task-master-mcp'],
env: {
ANTHROPIC_API_KEY: 'YOUR_ANTHROPIC_API_KEY',
PERPLEXITY_API_KEY: 'YOUR_PERPLEXITY_API_KEY',
ANTHROPIC_API_KEY: '%ANTHROPIC_API_KEY%',
PERPLEXITY_API_KEY: '%PERPLEXITY_API_KEY%',
MODEL: 'claude-3-7-sonnet-20250219',
PERPLEXITY_MODEL: 'sonar-pro',
MAX_TOKENS: 64000,
TEMPERATURE: 0.2,
TEMPERATURE: 0.3,
DEFAULT_SUBTASKS: 5,
DEFAULT_PRIORITY: 'medium'
}
@@ -909,10 +925,7 @@ function setupMCPConfiguration(targetDir, projectName) {
// Check if mcp.json already exists
if (fs.existsSync(mcpJsonPath)) {
log(
'info',
'MCP configuration file already exists, checking for existing task-master-mcp...'
);
log('info', 'MCP configuration file already exists, updating...');
try {
// Read existing config
const mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8'));
@@ -922,23 +935,6 @@ function setupMCPConfiguration(targetDir, projectName) {
mcpConfig.mcpServers = {};
}
// Check if any existing server configuration already has task-master-mcp in its args
const hasMCPString = Object.values(mcpConfig.mcpServers).some(
(server) =>
server.args &&
server.args.some(
(arg) => typeof arg === 'string' && arg.includes('task-master-mcp')
)
);
if (hasMCPString) {
log(
'info',
'Found existing task-master-mcp configuration in mcp.json, leaving untouched'
);
return; // Exit early, don't modify the existing configuration
}
// Add the task-master-ai server if it doesn't exist
if (!mcpConfig.mcpServers['task-master-ai']) {
mcpConfig.mcpServers['task-master-ai'] = newMCPServer['task-master-ai'];

View File

@@ -14,8 +14,7 @@ import {
writeJSON,
taskExists,
formatTaskId,
findCycles,
isSilentMode
findCycles
} from './utils.js';
import { displayBanner } from './ui.js';
@@ -321,7 +320,6 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
`Removed dependency: Task ${formattedTaskId} no longer depends on ${formattedDependencyId}`
);
if (!isSilentMode()) {
// Display a more visually appealing success message
console.log(
boxen(
@@ -335,7 +333,6 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
}
)
);
}
// Regenerate task files
await generateTaskFiles(tasksPath, 'tasks');
@@ -556,11 +553,8 @@ function cleanupSubtaskDependencies(tasksData) {
* Validate dependencies in task files
* @param {string} tasksPath - Path to tasks.json
*/
async function validateDependenciesCommand(tasksPath, options = {}) {
// Only display banner if not in silent mode
if (!isSilentMode()) {
async function validateDependenciesCommand(tasksPath) {
displayBanner();
}
log('info', 'Checking for invalid dependencies in task files...');
@@ -665,8 +659,7 @@ async function validateDependenciesCommand(tasksPath, options = {}) {
if (changesDetected) {
log('success', 'Invalid dependencies were removed from tasks.json');
// Show detailed stats in a nice box - only if not in silent mode
if (!isSilentMode()) {
// Show detailed stats in a nice box
console.log(
boxen(
chalk.green(`Dependency Validation Results:\n\n`) +
@@ -692,7 +685,6 @@ async function validateDependenciesCommand(tasksPath, options = {}) {
console.log(` ${warning}`);
});
}
}
// Regenerate task files to reflect the changes
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
@@ -703,8 +695,7 @@ async function validateDependenciesCommand(tasksPath, options = {}) {
'No invalid dependencies found - all dependencies are valid'
);
// Show validation summary - only if not in silent mode
if (!isSilentMode()) {
// Show validation summary
console.log(
boxen(
chalk.green(`All Dependencies Are Valid\n\n`) +
@@ -720,7 +711,6 @@ async function validateDependenciesCommand(tasksPath, options = {}) {
)
);
}
}
} catch (error) {
log('error', 'Error validating dependencies:', error);
process.exit(1);
@@ -757,13 +747,9 @@ function countAllDependencies(tasks) {
/**
* Fixes invalid dependencies in tasks.json
* @param {string} tasksPath - Path to tasks.json
* @param {Object} options - Options object
*/
async function fixDependenciesCommand(tasksPath, options = {}) {
// Only display banner if not in silent mode
if (!isSilentMode()) {
async function fixDependenciesCommand(tasksPath) {
displayBanner();
}
log('info', 'Checking for and fixing invalid dependencies in tasks.json...');
@@ -1100,7 +1086,6 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
stats.duplicateDependenciesRemoved +
stats.circularDependenciesFixed;
if (!isSilentMode()) {
if (totalFixedAll > 0) {
log('success', `Fixed ${totalFixedAll} dependency issues in total!`);
@@ -1122,10 +1107,7 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
)
);
} else {
log(
'success',
'No dependency issues found - all dependencies are valid'
);
log('success', 'No dependency issues found - all dependencies are valid');
console.log(
boxen(
@@ -1141,7 +1123,6 @@ async function fixDependenciesCommand(tasksPath, options = {}) {
)
);
}
}
} catch (error) {
log('error', 'Error in fix-dependencies command:', error);
process.exit(1);

View File

@@ -1,73 +1,39 @@
# Task ID: 60
# Title: Implement Mentor System with Round-Table Discussion Feature
# Title: Implement isValidTaskId Utility Function
# Status: pending
# Dependencies: None
# Priority: medium
# Description: Create a mentor system that allows users to add simulated mentors to their projects and facilitate round-table discussions between these mentors to gain diverse perspectives and insights on tasks.
# Description: Create a utility function that validates whether a given string conforms to the project's task ID format specification.
# Details:
Implement a comprehensive mentor system with the following features:
Develop a function named `isValidTaskId` that takes a string parameter and returns a boolean indicating whether the string matches our task ID format. The task ID format follows these rules:
1. **Mentor Management**:
- Create a `mentors.json` file to store mentor data including name, personality, expertise, and other relevant attributes
- Implement `add-mentor` command that accepts a name and prompt describing the mentor's characteristics
- Implement `remove-mentor` command to delete mentors from the system
- Implement `list-mentors` command to display all configured mentors and their details
- Set a recommended maximum of 5 mentors with appropriate warnings
1. Must start with 'TASK-' prefix (case-sensitive)
2. Followed by a numeric value (at least 1 digit)
3. The numeric portion should not have leading zeros (unless it's just zero)
4. The total length should be between 6 and 12 characters inclusive
2. **Round-Table Discussion**:
- Create a `round-table` command with the following parameters:
- `--prompt`: Optional text prompt to guide the discussion
- `--id`: Optional task/subtask ID(s) to provide context (support comma-separated values)
- `--turns`: Number of discussion rounds (each mentor speaks once per turn)
- `--output`: Optional flag to export results to a file
- Implement an interactive CLI experience using inquirer for the round-table
- Generate a simulated discussion where each mentor speaks in turn based on their personality
- After all turns complete, generate insights, recommendations, and a summary
- Display results in the CLI
- When `--output` is specified, create a `round-table.txt` file containing:
- Initial prompt
- Target task ID(s)
- Full round-table discussion transcript
- Recommendations and insights section
Example valid IDs: 'TASK-1', 'TASK-42', 'TASK-1000'
Example invalid IDs: 'task-1' (wrong case), 'TASK-' (missing number), 'TASK-01' (leading zero), 'TASK-A1' (non-numeric), 'TSK-1' (wrong prefix)
3. **Integration with Task System**:
- Enhance `update`, `update-task`, and `update-subtask` commands to accept a round-table.txt file
- Use the round-table output as input for updating tasks or subtasks
- Allow appending round-table insights to subtasks
4. **LLM Integration**:
- Configure the system to effectively simulate different personalities using LLM
- Ensure mentors maintain consistent personalities across different round-tables
- Implement proper context handling to ensure relevant task information is included
Ensure all commands have proper help text and error handling for cases like no mentors configured, invalid task IDs, etc.
The function should be placed in the utilities directory and properly exported. Include JSDoc comments for clear documentation of parameters and return values.
# Test Strategy:
1. **Unit Tests**:
- Test mentor data structure creation and validation
- Test mentor addition with various input formats
- Test mentor removal functionality
- Test listing of mentors with different configurations
- Test round-table parameter parsing and validation
Testing should include the following cases:
2. **Integration Tests**:
- Test the complete flow of adding mentors and running a round-table
- Test round-table with different numbers of turns
- Test round-table with task context vs. custom prompt
- Test output file generation and format
- Test using round-table output to update tasks and subtasks
1. Valid task IDs:
- 'TASK-1'
- 'TASK-123'
- 'TASK-9999'
3. **Edge Cases**:
- Test behavior when no mentors are configured but round-table is called
- Test with invalid task IDs in the --id parameter
- Test with extremely long discussions (many turns)
- Test with mentors that have similar personalities
- Test removing a mentor that doesn't exist
- Test adding more than the recommended 5 mentors
2. Invalid task IDs:
- Null or undefined input
- Empty string
- 'task-1' (lowercase prefix)
- 'TASK-' (missing number)
- 'TASK-01' (leading zero)
- 'TASK-ABC' (non-numeric suffix)
- 'TSK-1' (incorrect prefix)
- 'TASK-12345678901' (too long)
- 'TASK1' (missing hyphen)
4. **Manual Testing Scenarios**:
- Create mentors with distinct personalities (e.g., Vitalik Buterin, Steve Jobs, etc.)
- Run a round-table on a complex task and verify the insights are helpful
- Verify the personality simulation is consistent and believable
- Test the round-table output file readability and usefulness
- Verify that using round-table output to update tasks produces meaningful improvements
Implement unit tests using the project's testing framework. Each test case should have a clear assertion message explaining why the test failed if it does. Also include edge cases such as strings with whitespace ('TASK- 1') or special characters ('TASK-1#').

View File

@@ -2729,10 +2729,10 @@
},
{
"id": 60,
"title": "Implement Mentor System with Round-Table Discussion Feature",
"description": "Create a mentor system that allows users to add simulated mentors to their projects and facilitate round-table discussions between these mentors to gain diverse perspectives and insights on tasks.",
"details": "Implement a comprehensive mentor system with the following features:\n\n1. **Mentor Management**:\n - Create a `mentors.json` file to store mentor data including name, personality, expertise, and other relevant attributes\n - Implement `add-mentor` command that accepts a name and prompt describing the mentor's characteristics\n - Implement `remove-mentor` command to delete mentors from the system\n - Implement `list-mentors` command to display all configured mentors and their details\n - Set a recommended maximum of 5 mentors with appropriate warnings\n\n2. **Round-Table Discussion**:\n - Create a `round-table` command with the following parameters:\n - `--prompt`: Optional text prompt to guide the discussion\n - `--id`: Optional task/subtask ID(s) to provide context (support comma-separated values)\n - `--turns`: Number of discussion rounds (each mentor speaks once per turn)\n - `--output`: Optional flag to export results to a file\n - Implement an interactive CLI experience using inquirer for the round-table\n - Generate a simulated discussion where each mentor speaks in turn based on their personality\n - After all turns complete, generate insights, recommendations, and a summary\n - Display results in the CLI\n - When `--output` is specified, create a `round-table.txt` file containing:\n - Initial prompt\n - Target task ID(s)\n - Full round-table discussion transcript\n - Recommendations and insights section\n\n3. **Integration with Task System**:\n - Enhance `update`, `update-task`, and `update-subtask` commands to accept a round-table.txt file\n - Use the round-table output as input for updating tasks or subtasks\n - Allow appending round-table insights to subtasks\n\n4. **LLM Integration**:\n - Configure the system to effectively simulate different personalities using LLM\n - Ensure mentors maintain consistent personalities across different round-tables\n - Implement proper context handling to ensure relevant task information is included\n\nEnsure all commands have proper help text and error handling for cases like no mentors configured, invalid task IDs, etc.",
"testStrategy": "1. **Unit Tests**:\n - Test mentor data structure creation and validation\n - Test mentor addition with various input formats\n - Test mentor removal functionality\n - Test listing of mentors with different configurations\n - Test round-table parameter parsing and validation\n\n2. **Integration Tests**:\n - Test the complete flow of adding mentors and running a round-table\n - Test round-table with different numbers of turns\n - Test round-table with task context vs. custom prompt\n - Test output file generation and format\n - Test using round-table output to update tasks and subtasks\n\n3. **Edge Cases**:\n - Test behavior when no mentors are configured but round-table is called\n - Test with invalid task IDs in the --id parameter\n - Test with extremely long discussions (many turns)\n - Test with mentors that have similar personalities\n - Test removing a mentor that doesn't exist\n - Test adding more than the recommended 5 mentors\n\n4. **Manual Testing Scenarios**:\n - Create mentors with distinct personalities (e.g., Vitalik Buterin, Steve Jobs, etc.)\n - Run a round-table on a complex task and verify the insights are helpful\n - Verify the personality simulation is consistent and believable\n - Test the round-table output file readability and usefulness\n - Verify that using round-table output to update tasks produces meaningful improvements",
"title": "Implement isValidTaskId Utility Function",
"description": "Create a utility function that validates whether a given string conforms to the project's task ID format specification.",
"details": "Develop a function named `isValidTaskId` that takes a string parameter and returns a boolean indicating whether the string matches our task ID format. The task ID format follows these rules:\n\n1. Must start with 'TASK-' prefix (case-sensitive)\n2. Followed by a numeric value (at least 1 digit)\n3. The numeric portion should not have leading zeros (unless it's just zero)\n4. The total length should be between 6 and 12 characters inclusive\n\nExample valid IDs: 'TASK-1', 'TASK-42', 'TASK-1000'\nExample invalid IDs: 'task-1' (wrong case), 'TASK-' (missing number), 'TASK-01' (leading zero), 'TASK-A1' (non-numeric), 'TSK-1' (wrong prefix)\n\nThe function should be placed in the utilities directory and properly exported. Include JSDoc comments for clear documentation of parameters and return values.",
"testStrategy": "Testing should include the following cases:\n\n1. Valid task IDs:\n - 'TASK-1'\n - 'TASK-123'\n - 'TASK-9999'\n\n2. Invalid task IDs:\n - Null or undefined input\n - Empty string\n - 'task-1' (lowercase prefix)\n - 'TASK-' (missing number)\n - 'TASK-01' (leading zero)\n - 'TASK-ABC' (non-numeric suffix)\n - 'TSK-1' (incorrect prefix)\n - 'TASK-12345678901' (too long)\n - 'TASK1' (missing hyphen)\n\nImplement unit tests using the project's testing framework. Each test case should have a clear assertion message explaining why the test failed if it does. Also include edge cases such as strings with whitespace ('TASK- 1') or special characters ('TASK-1#').",
"status": "pending",
"dependencies": [],
"priority": "medium"

View File

@@ -131,7 +131,7 @@ jest.mock('../../../scripts/modules/utils.js', () => ({
enableSilentMode: mockEnableSilentMode,
disableSilentMode: mockDisableSilentMode,
CONFIG: {
model: 'claude-3-7-sonnet-20250219',
model: 'claude-3-sonnet-20240229',
maxTokens: 64000,
temperature: 0.2,
defaultSubtasks: 5

View File

@@ -7,10 +7,10 @@
// Mock environment variables
process.env.MODEL = 'sonar-pro';
process.env.MAX_TOKENS = '64000';
process.env.TEMPERATURE = '0.2';
process.env.TEMPERATURE = '0.4';
process.env.DEBUG = 'false';
process.env.LOG_LEVEL = 'error'; // Set to error to reduce noise in tests
process.env.DEFAULT_SUBTASKS = '5';
process.env.DEFAULT_SUBTASKS = '3';
process.env.DEFAULT_PRIORITY = 'medium';
process.env.PROJECT_NAME = 'Test Project';
process.env.PROJECT_VERSION = '1.0.0';