Compare commits
125 Commits
direct-fun
...
revert-369
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6f5ddabc96 | ||
|
|
8dace2186c | ||
|
|
095e373843 | ||
|
|
0bc9bac392 | ||
|
|
0a45f4329c | ||
|
|
c4b2f7e514 | ||
|
|
9684beafc3 | ||
|
|
302b916045 | ||
|
|
e7f18f65b9 | ||
|
|
655c7c225a | ||
|
|
e1218b3747 | ||
|
|
ffa621a37c | ||
|
|
cd32fd9edf | ||
|
|
590e4bd66d | ||
|
|
70d3f2f103 | ||
|
|
424aae10ed | ||
|
|
a48d1f13e2 | ||
|
|
25ca1a45a0 | ||
|
|
2e17437da3 | ||
|
|
1f44ea5299 | ||
|
|
d63964a10e | ||
|
|
33559e368c | ||
|
|
9f86306766 | ||
|
|
8f8a3dc45d | ||
|
|
d18351dc38 | ||
|
|
9d437f8594 | ||
|
|
ad89253e31 | ||
|
|
70c5097553 | ||
|
|
c9e4558a19 | ||
|
|
cd4d8e335f | ||
|
|
16297058bb | ||
|
|
ae2d43de29 | ||
|
|
f5585e6c31 | ||
|
|
303b13e3d4 | ||
|
|
1862ca2360 | ||
|
|
ad1c234b4e | ||
|
|
d07f8fddc5 | ||
|
|
c7158d4910 | ||
|
|
2a07d366be | ||
|
|
40df57f969 | ||
|
|
d4a2e34b3b | ||
|
|
d67b21fd43 | ||
|
|
b1beae3042 | ||
|
|
d2f761c652 | ||
|
|
4cf7e8a74a | ||
|
|
5f504fafb8 | ||
|
|
e69a47d382 | ||
|
|
89bb62d44b | ||
|
|
5aea93d4c0 | ||
|
|
66ac9ab9f6 | ||
|
|
ca7b0457f1 | ||
|
|
87d97bba00 | ||
|
|
3516efdc3b | ||
|
|
c8722b0a7a | ||
|
|
ed79d4f473 | ||
|
|
2517bc112c | ||
|
|
842eaf7224 | ||
|
|
96aeeffc19 | ||
|
|
5a2371b7cc | ||
|
|
b47f189cc2 | ||
|
|
36d559db26 | ||
|
|
afb47584bd | ||
|
|
3721359782 | ||
|
|
ef782ff5bd | ||
|
|
99b1a0ad7a | ||
|
|
70cc15bc87 | ||
|
|
ce51b0d3ef | ||
|
|
a82284a2db | ||
|
|
205a11e82c | ||
|
|
be3f68e777 | ||
|
|
90c6c1e587 | ||
|
|
6cb213ebbd | ||
|
|
bd0ee1b6e3 | ||
|
|
8ed651c165 | ||
|
|
2829194d3c | ||
|
|
2acba945c0 | ||
|
|
78a5376796 | ||
|
|
b3b424be93 | ||
|
|
c90578b6da | ||
|
|
3a3ad9f4fe | ||
|
|
abdc15eab2 | ||
|
|
515dcae965 | ||
|
|
a40805adf7 | ||
|
|
4a9f6cd5f5 | ||
|
|
d46547a80f | ||
|
|
bcb885e0ba | ||
|
|
ddf0947710 | ||
|
|
3a6bc43778 | ||
|
|
73aa7ac32e | ||
|
|
538b874582 | ||
|
|
0300582b46 | ||
|
|
3aee9bc840 | ||
|
|
11b8d1bda5 | ||
|
|
ff8e75cded | ||
|
|
3e872f8afb | ||
|
|
0eb16d5ecb | ||
|
|
c17d912237 | ||
|
|
41b979c239 | ||
|
|
d99fa00980 | ||
|
|
b2ccd60526 | ||
|
|
454a1d9d37 | ||
|
|
d181c40a95 | ||
|
|
1ab836f191 | ||
|
|
d84c2486e4 | ||
|
|
329839aeb8 | ||
|
|
c7fefb0549 | ||
|
|
cde23946e9 | ||
|
|
1ceb545d86 | ||
|
|
9a482789f7 | ||
|
|
4c57537157 | ||
|
|
6599cb0bf9 | ||
|
|
48a8d952bc | ||
|
|
94601f1e11 | ||
|
|
9f834f5a27 | ||
|
|
f5c4eda132 | ||
|
|
9122e516b6 | ||
|
|
04de6d9698 | ||
|
|
3530e28ee3 | ||
|
|
08f0319058 | ||
|
|
6f2cda0a6f | ||
|
|
cb720ca298 | ||
|
|
c6b8783bce | ||
|
|
9c0ed3c799 | ||
|
|
d3d9dc6ebe | ||
|
|
30e6d47577 |
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'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.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'task-master-ai': patch
|
||||
---
|
||||
|
||||
Ensures add-task also has manual creation flags like --title/-t, --description/-d etc.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Add CI for testing
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'task-master-ai': patch
|
||||
---
|
||||
|
||||
fix threshold parameter validation and testing for analyze-complexity.
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix github actions creating npm releases on next branch push
|
||||
@@ -1,5 +0,0 @@
|
||||
---
|
||||
'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."
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
'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.
|
||||
@@ -1,332 +0,0 @@
|
||||
---
|
||||
"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.
|
||||
@@ -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=128000 # Maximum tokens for model responses
|
||||
MAX_TOKENS=64000 # Maximum tokens for model responses
|
||||
TEMPERATURE=0.2 # Temperature for model responses (0.0-1.0)
|
||||
|
||||
# Logging Configuration
|
||||
|
||||
3
.github/workflows/release.yml
vendored
3
.github/workflows/release.yml
vendored
@@ -3,6 +3,9 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
@@ -4,3 +4,4 @@ coverage
|
||||
.changeset
|
||||
tasks
|
||||
package-lock.json
|
||||
tests/fixture/*.json
|
||||
|
||||
165
CHANGELOG.md
165
CHANGELOG.md
@@ -1,5 +1,170 @@
|
||||
# task-master-ai
|
||||
|
||||
## 0.13.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`ef782ff`](https://github.com/eyaltoledano/claude-task-master/commit/ef782ff5bd4ceb3ed0dc9ea82087aae5f79ac933) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - feat(expand): Enhance `expand` and `expand-all` commands
|
||||
|
||||
- Integrate `task-complexity-report.json` to automatically determine the number of subtasks and use tailored prompts for expansion based on prior analysis. You no longer need to try copy-pasting the recommended prompt. If it exists, it will use it for you. You can just run `task-master update --id=[id of task] --research` and it will use that prompt automatically. No extra prompt needed.
|
||||
- Change default behavior to _append_ new subtasks to existing ones. Use the `--force` flag to clear existing subtasks before expanding. This is helpful if you need to add more subtasks to a task but you want to do it by the batch from a given prompt. Use force if you want to start fresh with a task's subtasks.
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`87d97bb`](https://github.com/eyaltoledano/claude-task-master/commit/87d97bba00d84e905756d46ef96b2d5b984e0f38) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Adds support for the OpenRouter AI provider. Users can now configure models available through OpenRouter (requiring an `OPENROUTER_API_KEY`) via the `task-master models` command, granting access to a wide range of additional LLMs. - IMPORTANT FYI ABOUT OPENROUTER: Taskmaster relies on AI SDK, which itself relies on tool use. It looks like **free** models sometimes do not include tool use. For example, Gemini 2.5 pro (free) failed via OpenRouter (no tool use) but worked fine on the paid version of the model. Custom model support for Open Router is considered experimental and likely will not be further improved for some time.
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`1ab836f`](https://github.com/eyaltoledano/claude-task-master/commit/1ab836f191cb8969153593a9a0bd47fc9aa4a831) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Adds model management and new configuration file .taskmasterconfig which houses the models used for main, research and fallback. Adds models command and setter flags. Adds a --setup flag with an interactive setup. We should be calling this during init. Shows a table of active and available models when models is called without flags. Includes SWE scores and token costs, which are manually entered into the supported_models.json, the new place where models are defined for support. Config-manager.js is the core module responsible for managing the new config."
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`c8722b0`](https://github.com/eyaltoledano/claude-task-master/commit/c8722b0a7a443a73b95d1bcd4a0b68e0fce2a1cd) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Adds custom model ID support for Ollama and OpenRouter providers.
|
||||
|
||||
- Adds the `--ollama` and `--openrouter` flags to `task-master models --set-<role>` command to set models for those providers outside of the support models list.
|
||||
- Updated `task-master models --setup` interactive mode with options to explicitly enter custom Ollama or OpenRouter model IDs.
|
||||
- Implemented live validation against OpenRouter API (`/api/v1/models`) when setting a custom OpenRouter model ID (via flag or setup).
|
||||
- Refined logic to prioritize explicit provider flags/choices over internal model list lookups in case of ID conflicts.
|
||||
- Added warnings when setting custom/unvalidated models.
|
||||
- We obviously don't recommend going with a custom, unproven model. If you do and find performance is good, please let us know so we can add it to the list of supported models.
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`2517bc1`](https://github.com/eyaltoledano/claude-task-master/commit/2517bc112c9a497110f3286ca4bfb4130c9addcb) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Integrate OpenAI as a new AI provider. - Enhance `models` command/tool to display API key status. - Implement model-specific `maxTokens` override based on `supported-models.json` to save you if you use an incorrect max token value.
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`9a48278`](https://github.com/eyaltoledano/claude-task-master/commit/9a482789f7894f57f655fb8d30ba68542bd0df63) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Tweaks Perplexity AI calls for research mode to max out input tokens and get day-fresh information - Forces temp at 0.1 for highly deterministic output, no variations - Adds a system prompt to further improve the output - Correctly uses the maximum input tokens (8,719, used 8,700) for perplexity - Specificies to use a high degree of research across the web - Specifies to use information that is as fresh as today; this support stuff like capturing brand new announcements like new GPT models and being able to query for those in research. 🔥
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`842eaf7`](https://github.com/eyaltoledano/claude-task-master/commit/842eaf722498ddf7307800b4cdcef4ac4fd7e5b0) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - - Add support for Google Gemini models via Vercel AI SDK integration.
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`ed79d4f`](https://github.com/eyaltoledano/claude-task-master/commit/ed79d4f4735dfab4124fa189214c0bd5e23a6860) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Add xAI provider and Grok models support
|
||||
|
||||
- [#378](https://github.com/eyaltoledano/claude-task-master/pull/378) [`ad89253`](https://github.com/eyaltoledano/claude-task-master/commit/ad89253e313a395637aa48b9f92cc39b1ef94ad8) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Better support for file paths on Windows, Linux & WSL.
|
||||
|
||||
- Standardizes handling of different path formats (URI encoded, Windows, Linux, WSL).
|
||||
- Ensures tools receive a clean, absolute path suitable for the server OS.
|
||||
- Simplifies tool implementation by centralizing normalization logic.
|
||||
|
||||
- [#285](https://github.com/eyaltoledano/claude-task-master/pull/285) [`2acba94`](https://github.com/eyaltoledano/claude-task-master/commit/2acba945c0afee9460d8af18814c87e80f747e9f) Thanks [@neno-is-ooo](https://github.com/neno-is-ooo)! - Add integration for Roo Code
|
||||
|
||||
- [#378](https://github.com/eyaltoledano/claude-task-master/pull/378) [`d63964a`](https://github.com/eyaltoledano/claude-task-master/commit/d63964a10eed9be17856757661ff817ad6bacfdc) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Improved update-subtask - Now it has context about the parent task details - It also has context about the subtask before it and the subtask after it (if they exist) - Not passing all subtasks to stay token efficient
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`5f504fa`](https://github.com/eyaltoledano/claude-task-master/commit/5f504fafb8bdaa0043c2d20dee8bbb8ec2040d85) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Improve and adjust `init` command for robustness and updated dependencies.
|
||||
|
||||
- **Update Initialization Dependencies:** Ensure newly initialized projects (`task-master init`) include all required AI SDK dependencies (`@ai-sdk/*`, `ai`, provider wrappers) in their `package.json` for out-of-the-box AI feature compatibility. Remove unnecessary dependencies (e.g., `uuid`) from the init template.
|
||||
- **Silence `npm install` during `init`:** Prevent `npm install` output from interfering with non-interactive/MCP initialization by suppressing its stdio in silent mode.
|
||||
- **Improve Conditional Model Setup:** Reliably skip interactive `models --setup` during non-interactive `init` runs (e.g., `init -y` or MCP) by checking `isSilentMode()` instead of passing flags.
|
||||
- **Refactor `init.js`:** Remove internal `isInteractive` flag logic.
|
||||
- **Update `init` Instructions:** Tweak the "Getting Started" text displayed after `init`.
|
||||
- **Fix MCP Server Launch:** Update `.cursor/mcp.json` template to use `node ./mcp-server/server.js` instead of `npx task-master-mcp`.
|
||||
- **Update Default Model:** Change the default main model in the `.taskmasterconfig` template.
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`96aeeff`](https://github.com/eyaltoledano/claude-task-master/commit/96aeeffc195372722c6a07370540e235bfe0e4d8) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Fixes an issue with add-task which did not use the manually defined properties and still needlessly hit the AI endpoint.
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`5aea93d`](https://github.com/eyaltoledano/claude-task-master/commit/5aea93d4c0490c242d7d7042a210611977848e0a) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Fixes an issue that prevented remove-subtask with comma separated tasks/subtasks from being deleted (only the first ID was being deleted). Closes #140
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`66ac9ab`](https://github.com/eyaltoledano/claude-task-master/commit/66ac9ab9f66d006da518d6e8a3244e708af2764d) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Improves next command to be subtask-aware - The logic for determining the "next task" (findNextTask function, used by task-master next and the next_task MCP tool) has been significantly improved. Previously, it only considered top-level tasks, making its recommendation less useful when a parent task containing subtasks was already marked 'in-progress'. - The updated logic now prioritizes finding the next available subtask within any 'in-progress' parent task, considering subtask dependencies and priority. - If no suitable subtask is found within active parent tasks, it falls back to recommending the next eligible top-level task based on the original criteria (status, dependencies, priority).
|
||||
|
||||
This change makes the next command much more relevant and helpful during the implementation phase of complex tasks.
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`ca7b045`](https://github.com/eyaltoledano/claude-task-master/commit/ca7b0457f1dc65fd9484e92527d9fd6d69db758d) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Add `--status` flag to `show` command to filter displayed subtasks.
|
||||
|
||||
- [#328](https://github.com/eyaltoledano/claude-task-master/pull/328) [`5a2371b`](https://github.com/eyaltoledano/claude-task-master/commit/5a2371b7cc0c76f5e95d43921c1e8cc8081bf14e) Thanks [@knoxgraeme](https://github.com/knoxgraeme)! - Fix --task to --num-tasks in ui + related tests - issue #324
|
||||
|
||||
- [#240](https://github.com/eyaltoledano/claude-task-master/pull/240) [`6cb213e`](https://github.com/eyaltoledano/claude-task-master/commit/6cb213ebbd51116ae0688e35b575d09443d17c3b) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Adds a 'models' CLI and MCP command to get the current model configuration, available models, and gives the ability to set main/research/fallback models." - In the CLI, `task-master models` shows the current models config. Using the `--setup` flag launches an interactive set up that allows you to easily select the models you want to use for each of the three roles. Use `q` during the interactive setup to cancel the setup. - In the MCP, responses are simplified in RESTful format (instead of the full CLI output). The agent can use the `models` tool with different arguments, including `listAvailableModels` to get available models. Run without arguments, it will return the current configuration. Arguments are available to set the model for each of the three roles. This allows you to manage Taskmaster AI providers and models directly from either the CLI or MCP or both. - Updated the CLI help menu when you run `task-master` to include missing commands and .taskmasterconfig information. - Adds `--research` flag to `add-task` so you can hit up Perplexity right from the add-task flow, rather than having to add a task and then update it.
|
||||
|
||||
## 0.12.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#307](https://github.com/eyaltoledano/claude-task-master/pull/307) [`2829194`](https://github.com/eyaltoledano/claude-task-master/commit/2829194d3c1dd5373d3bf40275cf4f63b12d49a7) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix add_dependency tool crashing the MCP Server
|
||||
|
||||
## 0.12.0
|
||||
|
||||
### Minor Changes
|
||||
|
||||
- [#253](https://github.com/eyaltoledano/claude-task-master/pull/253) [`b2ccd60`](https://github.com/eyaltoledano/claude-task-master/commit/b2ccd605264e47a61451b4c012030ee29011bb40) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add `npx task-master-ai` that runs mcp instead of using `task-master-mcp``
|
||||
|
||||
- [#267](https://github.com/eyaltoledano/claude-task-master/pull/267) [`c17d912`](https://github.com/eyaltoledano/claude-task-master/commit/c17d912237e6caaa2445e934fc48cd4841abf056) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Improve PRD parsing prompt with structured analysis and clearer task generation guidelines. We are testing a new prompt - please provide feedback on your experience.
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#243](https://github.com/eyaltoledano/claude-task-master/pull/243) [`454a1d9`](https://github.com/eyaltoledano/claude-task-master/commit/454a1d9d37439c702656eedc0702c2f7a4451517) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - - Fixes shebang issue not allowing task-master to run on certain windows operating systems
|
||||
|
||||
- Resolves #241 #211 #184 #193
|
||||
|
||||
- [#268](https://github.com/eyaltoledano/claude-task-master/pull/268) [`3e872f8`](https://github.com/eyaltoledano/claude-task-master/commit/3e872f8afbb46cd3978f3852b858c233450b9f33) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix remove-task command to handle multiple comma-separated task IDs
|
||||
|
||||
- [#239](https://github.com/eyaltoledano/claude-task-master/pull/239) [`6599cb0`](https://github.com/eyaltoledano/claude-task-master/commit/6599cb0bf9eccecab528207836e9d45b8536e5c2) Thanks [@eyaltoledano](https://github.com/eyaltoledano)! - Updates the parameter descriptions for update, update-task and update-subtask to ensure the MCP server correctly reaches for the right update command based on what is being updated -- all tasks, one task, or a subtask.
|
||||
|
||||
- [#272](https://github.com/eyaltoledano/claude-task-master/pull/272) [`3aee9bc`](https://github.com/eyaltoledano/claude-task-master/commit/3aee9bc840eb8f31230bd1b761ed156b261cabc4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Enhance the `parsePRD` to include `--append` flag. This flag allows users to append the parsed PRD to an existing file, making it easier to manage multiple PRD files without overwriting existing content.
|
||||
|
||||
- [#264](https://github.com/eyaltoledano/claude-task-master/pull/264) [`ff8e75c`](https://github.com/eyaltoledano/claude-task-master/commit/ff8e75cded91fb677903040002626f7a82fd5f88) Thanks [@joedanz](https://github.com/joedanz)! - Add quotes around numeric env vars in mcp.json (Windsurf, etc.)
|
||||
|
||||
- [#248](https://github.com/eyaltoledano/claude-task-master/pull/248) [`d99fa00`](https://github.com/eyaltoledano/claude-task-master/commit/d99fa00980fc61695195949b33dcda7781006f90) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - - Fix `task-master init` polluting codebase with new packages inside `package.json` and modifying project `README`
|
||||
|
||||
- Now only initializes with cursor rules, windsurf rules, mcp.json, scripts/example_prd.txt, .gitignore modifications, and `README-task-master.md`
|
||||
|
||||
- [#266](https://github.com/eyaltoledano/claude-task-master/pull/266) [`41b979c`](https://github.com/eyaltoledano/claude-task-master/commit/41b979c23963483e54331015a86e7c5079f657e4) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fixed a bug that prevented the task-master from running in a Linux container
|
||||
|
||||
- [#265](https://github.com/eyaltoledano/claude-task-master/pull/265) [`0eb16d5`](https://github.com/eyaltoledano/claude-task-master/commit/0eb16d5ecbb8402d1318ca9509e9d4087b27fb25) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Remove the need for project name, description, and version. Since we no longer create a package.json for you
|
||||
|
||||
## 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
|
||||
|
||||
@@ -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 --package task-master-ai task-master-mcp"
|
||||
- Command: "npx -y task-master-ai"
|
||||
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.
|
||||
|
||||
10
README.md
10
README.md
@@ -1,6 +1,6 @@
|
||||
# Task Master [](https://github.com/eyaltoledano/claude-task-master/stargazers)
|
||||
|
||||
[](https://github.com/eyaltoledano/claude-task-master/actions/workflows/ci.yml) [](https://badge.fury.io/js/task-master-ai)  [](LICENSE)
|
||||
[](https://github.com/eyaltoledano/claude-task-master/actions/workflows/ci.yml) [](https://badge.fury.io/js/task-master-ai) [](https://discord.gg/2ms58QJjqp) [](LICENSE)
|
||||
|
||||
### By [@eyaltoledano](https://x.com/eyaltoledano) & [@RalphEcom](https://x.com/RalphEcom)
|
||||
|
||||
@@ -27,15 +27,15 @@ MCP (Model Control Protocol) provides the easiest way to get started with Task M
|
||||
"mcpServers": {
|
||||
"taskmaster-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "--package", "task-master-ai", "task-master-mcp"],
|
||||
"args": ["-y", "--package=task-master-ai", "task-master-ai"],
|
||||
"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": 128000,
|
||||
"TEMPERATURE": 0.2,
|
||||
"DEFAULT_SUBTASKS": 5,
|
||||
"MAX_TOKENS": "64000",
|
||||
"TEMPERATURE": "0.2",
|
||||
"DEFAULT_SUBTASKS": "5",
|
||||
"DEFAULT_PRIORITY": "medium"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env node --trace-deprecation
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Task Master
|
||||
|
||||
@@ -17,13 +17,13 @@ MCP (Model Control Protocol) provides the easiest way to get started with Task M
|
||||
"mcpServers": {
|
||||
"taskmaster-ai": {
|
||||
"command": "npx",
|
||||
"args": ["-y", "--package", "task-master-ai", "task-master-mcp"],
|
||||
"args": ["-y", "--package=task-master-ai", "task-master-ai"],
|
||||
"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": 128000,
|
||||
"MAX_TOKENS": 64000,
|
||||
"TEMPERATURE": 0.2,
|
||||
"DEFAULT_SUBTASKS": 5,
|
||||
"DEFAULT_PRIORITY": "medium"
|
||||
@@ -132,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 --package task-master-ai task-master-mcp"
|
||||
- Command: "npx -y --package=task-master-ai task-master-ai"
|
||||
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.
|
||||
|
||||
44
index.js
44
index.js
@@ -46,22 +46,18 @@ export const initProject = async (options = {}) => {
|
||||
};
|
||||
|
||||
// Export a function to run init as a CLI command
|
||||
export const runInitCLI = async () => {
|
||||
// Using spawn to ensure proper handling of stdio and process exit
|
||||
const child = spawn('node', [resolve(__dirname, './scripts/init.js')], {
|
||||
stdio: 'inherit',
|
||||
cwd: process.cwd()
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
child.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
resolve();
|
||||
} else {
|
||||
reject(new Error(`Init script exited with code ${code}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
export const runInitCLI = async (options = {}) => {
|
||||
try {
|
||||
const init = await import('./scripts/init.js');
|
||||
const result = await init.initializeProject(options);
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Initialization failed:', error.message);
|
||||
if (process.env.DEBUG === 'true') {
|
||||
console.error('Debug stack trace:', error.stack);
|
||||
}
|
||||
throw error; // Re-throw to be handled by the command handler
|
||||
}
|
||||
};
|
||||
|
||||
// Export version information
|
||||
@@ -79,11 +75,21 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
program
|
||||
.command('init')
|
||||
.description('Initialize a new project')
|
||||
.action(() => {
|
||||
runInitCLI().catch((err) => {
|
||||
.option('-y, --yes', 'Skip prompts and use default values')
|
||||
.option('-n, --name <n>', 'Project name')
|
||||
.option('-d, --description <description>', 'Project description')
|
||||
.option('-v, --version <version>', 'Project version', '0.1.0')
|
||||
.option('-a, --author <author>', 'Author name')
|
||||
.option('--skip-install', 'Skip installing dependencies')
|
||||
.option('--dry-run', 'Show what would be done without making changes')
|
||||
.option('--aliases', 'Add shell aliases (tm, taskmaster)')
|
||||
.action(async (cmdOptions) => {
|
||||
try {
|
||||
await runInitCLI(cmdOptions);
|
||||
} catch (err) {
|
||||
console.error('Init failed:', err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { addDependency } from '../../../../scripts/modules/dependency-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
@@ -14,19 +13,32 @@ 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;
|
||||
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 (!args.id) {
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
@@ -36,7 +48,7 @@ export async function addDependencyDirect(args, log) {
|
||||
};
|
||||
}
|
||||
|
||||
if (!args.dependsOn) {
|
||||
if (!dependsOn) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
@@ -46,18 +58,16 @@ export async function addDependencyDirect(args, log) {
|
||||
};
|
||||
}
|
||||
|
||||
// Find the tasks.json path
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
// Use provided path
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
// Format IDs for the core function
|
||||
const taskId =
|
||||
args.id.includes && args.id.includes('.')
|
||||
? args.id
|
||||
: parseInt(args.id, 10);
|
||||
id && id.includes && id.includes('.') ? id : parseInt(id, 10);
|
||||
const dependencyId =
|
||||
args.dependsOn.includes && args.dependsOn.includes('.')
|
||||
? args.dependsOn
|
||||
: parseInt(args.dependsOn, 10);
|
||||
dependsOn && dependsOn.includes && dependsOn.includes('.')
|
||||
? dependsOn
|
||||
: parseInt(dependsOn, 10);
|
||||
|
||||
log.info(
|
||||
`Adding dependency: task ${taskId} will depend on ${dependencyId}`
|
||||
@@ -66,7 +76,7 @@ export async function addDependencyDirect(args, log) {
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Call the core function
|
||||
// Call the core function using the provided path
|
||||
await addDependency(tasksPath, taskId, dependencyId);
|
||||
|
||||
// Restore normal logging
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import { addSubtask } from '../../../../scripts/modules/task-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
@@ -12,6 +11,7 @@ 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,17 +19,39 @@ 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;
|
||||
try {
|
||||
log.info(`Adding subtask with args: ${JSON.stringify(args)}`);
|
||||
|
||||
if (!args.id) {
|
||||
// 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) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
@@ -40,7 +62,7 @@ export async function addSubtaskDirect(args, log) {
|
||||
}
|
||||
|
||||
// Either taskId or title must be provided
|
||||
if (!args.taskId && !args.title) {
|
||||
if (!taskId && !title) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
@@ -50,26 +72,26 @@ export async function addSubtaskDirect(args, log) {
|
||||
};
|
||||
}
|
||||
|
||||
// Find the tasks.json path
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
// Use provided path
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
// Parse dependencies if provided
|
||||
let dependencies = [];
|
||||
if (args.dependencies) {
|
||||
dependencies = args.dependencies.split(',').map((id) => {
|
||||
if (dependenciesStr) {
|
||||
dependencies = dependenciesStr.split(',').map((depId) => {
|
||||
// Handle both regular IDs and dot notation
|
||||
return id.includes('.') ? id.trim() : parseInt(id.trim(), 10);
|
||||
return depId.includes('.') ? depId.trim() : parseInt(depId.trim(), 10);
|
||||
});
|
||||
}
|
||||
|
||||
// Convert existingTaskId to a number if provided
|
||||
const existingTaskId = args.taskId ? parseInt(args.taskId, 10) : null;
|
||||
const existingTaskId = taskId ? parseInt(taskId, 10) : null;
|
||||
|
||||
// Convert parent ID to a number
|
||||
const parentId = parseInt(args.id, 10);
|
||||
const parentId = parseInt(id, 10);
|
||||
|
||||
// Determine if we should generate files
|
||||
const generateFiles = !args.skipGenerate;
|
||||
const generateFiles = !skipGenerate;
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
@@ -101,10 +123,10 @@ export async function addSubtaskDirect(args, log) {
|
||||
log.info(`Creating new subtask for parent task ${parentId}`);
|
||||
|
||||
const newSubtaskData = {
|
||||
title: args.title,
|
||||
description: args.description || '',
|
||||
details: args.details || '',
|
||||
status: args.status || 'pending',
|
||||
title: title,
|
||||
description: description || '',
|
||||
details: details || '',
|
||||
status: status || 'pending',
|
||||
dependencies: dependencies
|
||||
};
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { addTask } from '../../../../scripts/modules/task-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
@@ -38,12 +37,27 @@ import {
|
||||
* @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;
|
||||
try {
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Find the tasks.json path
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
// 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;
|
||||
|
||||
// Check if this is manual task creation or AI-driven task creation
|
||||
const isManualCreation = args.title && args.description;
|
||||
@@ -65,15 +79,15 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
}
|
||||
|
||||
// Extract and prepare parameters
|
||||
const prompt = args.prompt;
|
||||
const dependencies = Array.isArray(args.dependencies)
|
||||
? args.dependencies
|
||||
: args.dependencies
|
||||
? String(args.dependencies)
|
||||
const taskPrompt = prompt;
|
||||
const taskDependencies = Array.isArray(dependencies)
|
||||
? dependencies
|
||||
: dependencies
|
||||
? String(dependencies)
|
||||
.split(',')
|
||||
.map((id) => parseInt(id.trim(), 10))
|
||||
: [];
|
||||
const priority = args.priority || 'medium';
|
||||
const taskPriority = priority || 'medium';
|
||||
|
||||
// Extract context parameters for advanced functionality
|
||||
const { session } = context;
|
||||
@@ -90,14 +104,14 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
};
|
||||
|
||||
log.info(
|
||||
`Adding new task manually with title: "${args.title}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`
|
||||
`Adding new task manually with title: "${args.title}", dependencies: [${taskDependencies.join(', ')}], priority: ${priority}`
|
||||
);
|
||||
|
||||
// Call the addTask function with manual task data
|
||||
const newTaskId = await addTask(
|
||||
tasksPath,
|
||||
null, // No prompt needed for manual creation
|
||||
dependencies,
|
||||
taskDependencies,
|
||||
priority,
|
||||
{
|
||||
mcpLog: log,
|
||||
@@ -121,7 +135,7 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
} else {
|
||||
// AI-driven task creation
|
||||
log.info(
|
||||
`Adding new task with prompt: "${prompt}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`
|
||||
`Adding new task with prompt: "${prompt}", dependencies: [${taskDependencies.join(', ')}], priority: ${priority}`
|
||||
);
|
||||
|
||||
// Initialize AI client with session environment
|
||||
@@ -207,7 +221,7 @@ export async function addTaskDirect(args, log, context = {}) {
|
||||
const newTaskId = await addTask(
|
||||
tasksPath,
|
||||
prompt,
|
||||
dependencies,
|
||||
taskDependencies,
|
||||
priority,
|
||||
{
|
||||
mcpLog: log,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import { analyzeTaskComplexity } from '../../../../scripts/modules/task-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode,
|
||||
@@ -16,45 +15,60 @@ import path from 'path';
|
||||
/**
|
||||
* Analyze task complexity and generate recommendations
|
||||
* @param {Object} args - Function arguments
|
||||
* @param {string} [args.file] - Path to the tasks file
|
||||
* @param {string} [args.output] - Output file path for the report
|
||||
* @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.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;
|
||||
|
||||
try {
|
||||
log.info(`Analyzing task complexity with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Find the tasks.json path
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
|
||||
// Determine output path
|
||||
let outputPath = args.output || 'scripts/task-complexity-report.json';
|
||||
if (!path.isAbsolute(outputPath) && args.projectRoot) {
|
||||
outputPath = path.join(args.projectRoot, outputPath);
|
||||
// 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' }
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`Analyzing task complexity from: ${tasksPath}`);
|
||||
log.info(`Output report will be saved to: ${outputPath}`);
|
||||
// Use the provided paths
|
||||
const tasksPath = tasksJsonPath;
|
||||
const resolvedOutputPath = outputPath;
|
||||
|
||||
if (args.research) {
|
||||
log.info(`Analyzing task complexity from: ${tasksPath}`);
|
||||
log.info(`Output report will be saved to: ${resolvedOutputPath}`);
|
||||
|
||||
if (research) {
|
||||
log.info('Using Perplexity AI for research-backed complexity analysis');
|
||||
}
|
||||
|
||||
// Create options object for analyzeTaskComplexity
|
||||
// Create options object for analyzeTaskComplexity using provided paths
|
||||
const options = {
|
||||
file: tasksPath,
|
||||
output: outputPath,
|
||||
model: args.model,
|
||||
threshold: args.threshold,
|
||||
research: args.research === true
|
||||
output: resolvedOutputPath,
|
||||
model: model,
|
||||
threshold: threshold,
|
||||
research: research === true
|
||||
};
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
@@ -95,7 +109,7 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
||||
}
|
||||
|
||||
// Verify the report file was created
|
||||
if (!fs.existsSync(outputPath)) {
|
||||
if (!fs.existsSync(resolvedOutputPath)) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
@@ -108,7 +122,7 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
||||
// Read the report file
|
||||
let report;
|
||||
try {
|
||||
report = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
|
||||
report = JSON.parse(fs.readFileSync(resolvedOutputPath, 'utf8'));
|
||||
|
||||
// Important: Handle different report formats
|
||||
// The core function might return an array or an object with a complexityAnalysis property
|
||||
@@ -130,8 +144,8 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: `Task complexity analysis complete. Report saved to ${outputPath}`,
|
||||
reportPath: outputPath,
|
||||
message: `Task complexity analysis complete. Report saved to ${resolvedOutputPath}`,
|
||||
reportPath: resolvedOutputPath,
|
||||
reportSummary: {
|
||||
taskCount: analysisArray.length,
|
||||
highComplexityTasks,
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import { clearSubtasks } from '../../../../scripts/modules/task-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
@@ -13,19 +12,32 @@ 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;
|
||||
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 (!args.id && !args.all) {
|
||||
if (!id && !all) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
@@ -36,8 +48,8 @@ export async function clearSubtasksDirect(args, log) {
|
||||
};
|
||||
}
|
||||
|
||||
// Find the tasks.json path
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
// Use provided path
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
// Check if tasks.json exists
|
||||
if (!fs.existsSync(tasksPath)) {
|
||||
@@ -53,7 +65,7 @@ export async function clearSubtasksDirect(args, log) {
|
||||
let taskIds;
|
||||
|
||||
// If all is specified, get all task IDs
|
||||
if (args.all) {
|
||||
if (all) {
|
||||
log.info('Clearing subtasks from all tasks');
|
||||
const data = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
|
||||
if (!data || !data.tasks || data.tasks.length === 0) {
|
||||
@@ -68,7 +80,7 @@ export async function clearSubtasksDirect(args, log) {
|
||||
taskIds = data.tasks.map((t) => t.id).join(',');
|
||||
} else {
|
||||
// Use the provided task IDs
|
||||
taskIds = args.id;
|
||||
taskIds = id;
|
||||
}
|
||||
|
||||
log.info(`Clearing subtasks from tasks: ${taskIds}`);
|
||||
|
||||
@@ -8,37 +8,34 @@ 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 file path option
|
||||
* @param {Object} args - Command arguments containing reportPath.
|
||||
* @param {string} args.reportPath - Explicit path to the complexity report file.
|
||||
* @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;
|
||||
try {
|
||||
log.info(`Getting complexity report with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Get tasks file path to determine project root for the default report location
|
||||
let tasksPath;
|
||||
try {
|
||||
tasksPath = findTasksJsonPath(args, log);
|
||||
} catch (error) {
|
||||
log.warn(
|
||||
`Tasks file not found, using current directory: ${error.message}`
|
||||
);
|
||||
// Continue with default or specified report path
|
||||
// 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 report file path from args or use default
|
||||
const reportPath =
|
||||
args.file ||
|
||||
path.join(process.cwd(), 'scripts', 'task-complexity-report.json');
|
||||
|
||||
// Use the provided report path
|
||||
log.info(`Looking for complexity report at: ${reportPath}`);
|
||||
|
||||
// Generate cache key based on report path
|
||||
|
||||
@@ -8,7 +8,6 @@ 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';
|
||||
@@ -16,34 +15,51 @@ 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;
|
||||
|
||||
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 {
|
||||
// Find the tasks.json path
|
||||
// Remove internal path finding
|
||||
/*
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
*/
|
||||
// Use provided path
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
// Parse parameters
|
||||
const numSubtasks = args.num ? parseInt(args.num, 10) : undefined;
|
||||
const useResearch = args.research === true;
|
||||
const additionalContext = args.prompt || '';
|
||||
const forceFlag = args.force === true;
|
||||
const numSubtasks = num ? parseInt(num, 10) : undefined;
|
||||
const useResearch = research === true;
|
||||
const additionalContext = prompt || '';
|
||||
const forceFlag = force === true;
|
||||
|
||||
log.info(
|
||||
`Expanding all tasks with ${numSubtasks || 'default'} subtasks each...`
|
||||
|
||||
@@ -11,7 +11,6 @@ import {
|
||||
disableSilentMode,
|
||||
isSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
getAnthropicClientForMCP,
|
||||
getModelConfig
|
||||
@@ -23,12 +22,20 @@ 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;
|
||||
|
||||
// Log session root data for debugging
|
||||
log.info(
|
||||
@@ -40,48 +47,26 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
})}`
|
||||
);
|
||||
|
||||
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);
|
||||
}
|
||||
} 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';
|
||||
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('expandTaskDirect called without tasksJsonPath');
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'FILE_NOT_FOUND_ERROR',
|
||||
message: `Error determining tasksPath: ${error.message}${sessionRootsInfo}`
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
log.info(`[expandTaskDirect] Determined tasksPath: ${tasksPath}`);
|
||||
// Use provided path
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
log.info(`[expandTaskDirect] Using tasksPath: ${tasksPath}`);
|
||||
|
||||
// Validate task ID
|
||||
const taskId = args.id ? parseInt(args.id, 10) : null;
|
||||
const taskId = id ? parseInt(id, 10) : null;
|
||||
if (!taskId) {
|
||||
log.error('Task ID is required');
|
||||
return {
|
||||
@@ -95,9 +80,10 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
}
|
||||
|
||||
// Process other parameters
|
||||
const numSubtasks = args.num ? parseInt(args.num, 10) : undefined;
|
||||
const useResearch = args.research === true;
|
||||
const additionalContext = args.prompt || '';
|
||||
const numSubtasks = num ? parseInt(num, 10) : undefined;
|
||||
const useResearch = research === true;
|
||||
const additionalContext = prompt || '';
|
||||
const forceFlag = force === true;
|
||||
|
||||
// Initialize AI client if needed (for expandTask function)
|
||||
try {
|
||||
@@ -172,15 +158,16 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
// Check for existing subtasks
|
||||
// Check for existing subtasks and force flag
|
||||
const hasExistingSubtasks = task.subtasks && task.subtasks.length > 0;
|
||||
|
||||
// If the task already has subtasks, just return it (matching core behavior)
|
||||
if (hasExistingSubtasks) {
|
||||
log.info(`Task ${taskId} already has ${task.subtasks.length} subtasks`);
|
||||
if (hasExistingSubtasks && !forceFlag) {
|
||||
log.info(
|
||||
`Task ${taskId} already has ${task.subtasks.length} subtasks. Use --force to overwrite.`
|
||||
);
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: `Task ${taskId} already has subtasks. Expansion skipped.`,
|
||||
task,
|
||||
subtasksAdded: 0,
|
||||
hasExistingSubtasks
|
||||
@@ -189,6 +176,14 @@ 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));
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import { fixDependenciesCommand } from '../../../../scripts/modules/dependency-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
@@ -13,17 +12,30 @@ import fs from 'fs';
|
||||
/**
|
||||
* Fix invalid dependencies in tasks.json automatically
|
||||
* @param {Object} args - Function arguments
|
||||
* @param {string} [args.file] - Path to the tasks file
|
||||
* @param {string} [args.projectRoot] - Project root directory
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
|
||||
* @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;
|
||||
try {
|
||||
log.info(`Fixing invalid dependencies in tasks...`);
|
||||
log.info(`Fixing invalid dependencies in tasks: ${tasksJsonPath}`);
|
||||
|
||||
// Find the tasks.json path
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
// 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;
|
||||
|
||||
// Verify the file exists
|
||||
if (!fs.existsSync(tasksPath)) {
|
||||
@@ -39,7 +51,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
|
||||
// Call the original command function using the provided path
|
||||
await fixDependenciesCommand(tasksPath);
|
||||
|
||||
// Restore normal logging
|
||||
|
||||
@@ -8,40 +8,46 @@ 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 file and output path options.
|
||||
* @param {Object} args - Command arguments containing tasksJsonPath and outputDir.
|
||||
* @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;
|
||||
try {
|
||||
log.info(`Generating task files with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Get tasks file path
|
||||
let tasksPath;
|
||||
try {
|
||||
tasksPath = findTasksJsonPath(args, log);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks file: ${error.message}`);
|
||||
// Check if paths were provided
|
||||
if (!tasksJsonPath) {
|
||||
const errorMessage = 'tasksJsonPath is required but was not provided.';
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'TASKS_FILE_ERROR', message: error.message },
|
||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
// Get output directory (defaults to the same directory as the tasks file)
|
||||
let outputDir = args.output;
|
||||
if (!outputDir) {
|
||||
outputDir = path.dirname(tasksPath);
|
||||
}
|
||||
// Use the provided paths
|
||||
const tasksPath = tasksJsonPath;
|
||||
const resolvedOutputDir = outputDir;
|
||||
|
||||
log.info(`Generating task files from ${tasksPath} to ${outputDir}`);
|
||||
log.info(`Generating task files from ${tasksPath} to ${resolvedOutputDir}`);
|
||||
|
||||
// Execute core generateTaskFiles function in a separate try/catch
|
||||
try {
|
||||
@@ -49,7 +55,7 @@ export async function generateTaskFilesDirect(args, log) {
|
||||
enableSilentMode();
|
||||
|
||||
// The function is synchronous despite being awaited elsewhere
|
||||
generateTaskFiles(tasksPath, outputDir);
|
||||
generateTaskFiles(tasksPath, resolvedOutputDir);
|
||||
|
||||
// Restore normal logging after task generation
|
||||
disableSilentMode();
|
||||
@@ -70,8 +76,8 @@ export async function generateTaskFilesDirect(args, log) {
|
||||
success: true,
|
||||
data: {
|
||||
message: `Successfully generated task files`,
|
||||
tasksPath,
|
||||
outputDir,
|
||||
tasksPath: tasksPath,
|
||||
outputDir: resolvedOutputDir,
|
||||
taskFiles:
|
||||
'Individual task files have been generated in the output directory'
|
||||
},
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import path from 'path';
|
||||
import { initializeProject, log as initLog } from '../../../../scripts/init.js'; // Import core function and its logger if needed separately
|
||||
import { initializeProject } from '../../../../scripts/init.js'; // Import core function and its logger if needed separately
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
@@ -11,7 +10,7 @@ import os from 'os'; // Import os module for home directory check
|
||||
/**
|
||||
* Direct function wrapper for initializing a project.
|
||||
* Derives target directory from session, sets CWD, and calls core init logic.
|
||||
* @param {object} args - Arguments containing project details and options (projectName, projectDescription, yes, etc.)
|
||||
* @param {object} args - Arguments containing initialization options (addAliases, skipInstall, yes, projectRoot)
|
||||
* @param {object} log - The FastMCP logger instance.
|
||||
* @param {object} context - The context object, must contain { session }.
|
||||
* @returns {Promise<{success: boolean, data?: any, error?: {code: string, message: string}}>} - Standard result object.
|
||||
@@ -93,12 +92,8 @@ export async function initializeProjectDirect(args, log, context = {}) {
|
||||
try {
|
||||
// Always force yes: true when called via MCP to avoid interactive prompts
|
||||
const options = {
|
||||
name: args.projectName,
|
||||
description: args.projectDescription,
|
||||
version: args.projectVersion,
|
||||
author: args.authorName,
|
||||
skipInstall: args.skipInstall,
|
||||
aliases: args.addAliases,
|
||||
skipInstall: args.skipInstall,
|
||||
yes: true // Force yes mode
|
||||
};
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
import { listTasks } from '../../../../scripts/modules/task-manager.js';
|
||||
import { getCachedOrExecute } from '../../tools/utils.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
@@ -14,38 +13,30 @@ import {
|
||||
/**
|
||||
* Direct function wrapper for listTasks with error handling and caching.
|
||||
*
|
||||
* @param {Object} args - Command arguments (projectRoot is expected to be resolved).
|
||||
* @param {Object} args - Command arguments (now expecting tasksJsonPath explicitly).
|
||||
* @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) {
|
||||
let tasksPath;
|
||||
try {
|
||||
// Find the tasks path first - needed for cache key and execution
|
||||
tasksPath = findTasksJsonPath(args, log);
|
||||
} 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: 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
|
||||
// Destructure the explicit tasksJsonPath from args
|
||||
const { tasksJsonPath, status, withSubtasks } = args;
|
||||
|
||||
if (!tasksJsonPath) {
|
||||
log.error('listTasksDirect called without tasksJsonPath');
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'FIND_TASKS_PATH_ERROR', message: error.message },
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// Generate cache key *after* finding tasksPath
|
||||
const statusFilter = args.status || 'all';
|
||||
const withSubtasks = args.withSubtasks || false;
|
||||
const cacheKey = `listTasks:${tasksPath}:${statusFilter}:${withSubtasks}`;
|
||||
// Use the explicit tasksJsonPath for cache key
|
||||
const statusFilter = status || 'all';
|
||||
const withSubtasksFilter = withSubtasks || false;
|
||||
const cacheKey = `listTasks:${tasksJsonPath}:${statusFilter}:${withSubtasksFilter}`;
|
||||
|
||||
// Define the action function to be executed on cache miss
|
||||
const coreListTasksAction = async () => {
|
||||
@@ -54,12 +45,13 @@ export async function listTasksDirect(args, log) {
|
||||
enableSilentMode();
|
||||
|
||||
log.info(
|
||||
`Executing core listTasks function for path: ${tasksPath}, filter: ${statusFilter}, subtasks: ${withSubtasks}`
|
||||
`Executing core listTasks function for path: ${tasksJsonPath}, filter: ${statusFilter}, subtasks: ${withSubtasksFilter}`
|
||||
);
|
||||
// Pass the explicit tasksJsonPath to the core function
|
||||
const resultData = listTasks(
|
||||
tasksPath,
|
||||
tasksJsonPath,
|
||||
statusFilter,
|
||||
withSubtasks,
|
||||
withSubtasksFilter,
|
||||
'json'
|
||||
);
|
||||
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
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
|
||||
@@ -16,28 +15,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) {
|
||||
let tasksPath;
|
||||
try {
|
||||
// Find the tasks path first - needed for cache key and execution
|
||||
tasksPath = findTasksJsonPath(args, log);
|
||||
} catch (error) {
|
||||
log.error(`Tasks file not found: ${error.message}`);
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath } = args;
|
||||
|
||||
if (!tasksJsonPath) {
|
||||
log.error('nextTaskDirect called without tasksJsonPath');
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'FILE_NOT_FOUND_ERROR',
|
||||
message: error.message
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// Generate cache key using task path
|
||||
const cacheKey = `nextTask:${tasksPath}`;
|
||||
// Generate cache key using the provided task path
|
||||
const cacheKey = `nextTask:${tasksJsonPath}`;
|
||||
|
||||
// Define the action function to be executed on cache miss
|
||||
const coreNextTaskAction = async () => {
|
||||
@@ -45,16 +44,17 @@ 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 ${tasksPath}`);
|
||||
log.info(`Finding next task from ${tasksJsonPath}`);
|
||||
|
||||
// Read tasks data
|
||||
const data = readJSON(tasksPath);
|
||||
// Read tasks data using the provided path
|
||||
const data = readJSON(tasksJsonPath);
|
||||
if (!data || !data.tasks) {
|
||||
disableSilentMode(); // Disable before return
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INVALID_TASKS_FILE',
|
||||
message: `No valid tasks found in ${tasksPath}`
|
||||
message: `No valid tasks found in ${tasksJsonPath}`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -5,9 +5,7 @@
|
||||
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import os from 'os'; // Import os module for home directory check
|
||||
import { parsePRD } from '../../../../scripts/modules/task-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
@@ -47,21 +45,9 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
// --- 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
|
||||
// Validate required parameters
|
||||
if (!args.projectRoot) {
|
||||
const errorMessage = 'Project root is required but was not provided';
|
||||
const errorMessage = 'Project root is required for parsePRDDirect';
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
@@ -70,53 +56,32 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
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.`;
|
||||
if (!args.input) {
|
||||
const errorMessage = 'Input file path is required for parsePRDDirect';
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'INVALID_PROJECT_ROOT', message: errorMessage },
|
||||
error: { code: 'MISSING_INPUT_PATH', message: errorMessage },
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// Resolve input path (relative to validated project root)
|
||||
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)
|
||||
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}`;
|
||||
@@ -132,6 +97,18 @@ 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) {
|
||||
@@ -145,8 +122,12 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
// Extract the append flag from args
|
||||
const append = Boolean(args.append) === true;
|
||||
|
||||
// Log key parameters including append flag
|
||||
log.info(
|
||||
`Preparing to parse PRD from ${inputPath} and output to ${outputPath} with ${numTasks} tasks`
|
||||
`Preparing to parse PRD from ${inputPath} and output to ${outputPath} with ${numTasks} tasks, append mode: ${append}`
|
||||
);
|
||||
|
||||
// Create the logger wrapper for proper logging in the core function
|
||||
@@ -178,7 +159,8 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
numTasks,
|
||||
{
|
||||
mcpLog: logWrapper,
|
||||
session
|
||||
session,
|
||||
append
|
||||
},
|
||||
aiClient,
|
||||
modelConfig
|
||||
@@ -188,16 +170,18 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
// to return it to the caller
|
||||
if (fs.existsSync(outputPath)) {
|
||||
const tasksData = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
|
||||
log.info(
|
||||
`Successfully parsed PRD and generated ${tasksData.tasks?.length || 0} tasks`
|
||||
);
|
||||
const actionVerb = append ? 'appended' : 'generated';
|
||||
const message = `Successfully ${actionVerb} ${tasksData.tasks?.length || 0} tasks from PRD`;
|
||||
|
||||
log.info(message);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: `Successfully generated ${tasksData.tasks?.length || 0} tasks from PRD`,
|
||||
message,
|
||||
taskCount: tasksData.tasks?.length || 0,
|
||||
outputPath
|
||||
outputPath,
|
||||
appended: append
|
||||
},
|
||||
fromCache: false // This operation always modifies state and should never be cached
|
||||
};
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import { removeDependency } from '../../../../scripts/modules/dependency-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
@@ -12,19 +11,32 @@ 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;
|
||||
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 (!args.id) {
|
||||
if (!id) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
@@ -34,7 +46,7 @@ export async function removeDependencyDirect(args, log) {
|
||||
};
|
||||
}
|
||||
|
||||
if (!args.dependsOn) {
|
||||
if (!dependsOn) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
@@ -44,18 +56,16 @@ export async function removeDependencyDirect(args, log) {
|
||||
};
|
||||
}
|
||||
|
||||
// Find the tasks.json path
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
// Use provided path
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
// Format IDs for the core function
|
||||
const taskId =
|
||||
args.id.includes && args.id.includes('.')
|
||||
? args.id
|
||||
: parseInt(args.id, 10);
|
||||
id && id.includes && id.includes('.') ? id : parseInt(id, 10);
|
||||
const dependencyId =
|
||||
args.dependsOn.includes && args.dependsOn.includes('.')
|
||||
? args.dependsOn
|
||||
: parseInt(args.dependsOn, 10);
|
||||
dependsOn && dependsOn.includes && dependsOn.includes('.')
|
||||
? dependsOn
|
||||
: parseInt(dependsOn, 10);
|
||||
|
||||
log.info(
|
||||
`Removing dependency: task ${taskId} no longer depends on ${dependencyId}`
|
||||
@@ -64,7 +74,7 @@ export async function removeDependencyDirect(args, log) {
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
// Call the core function
|
||||
// Call the core function using the provided tasksPath
|
||||
await removeDependency(tasksPath, taskId, dependencyId);
|
||||
|
||||
// Restore normal logging
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import { removeSubtask } from '../../../../scripts/modules/task-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
@@ -12,22 +11,37 @@ 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;
|
||||
try {
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
|
||||
|
||||
if (!args.id) {
|
||||
// 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
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
@@ -39,32 +53,34 @@ export async function removeSubtaskDirect(args, log) {
|
||||
}
|
||||
|
||||
// Validate subtask ID format
|
||||
if (!args.id.includes('.')) {
|
||||
if (!id.includes('.')) {
|
||||
disableSilentMode(); // Disable before returning
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INPUT_VALIDATION_ERROR',
|
||||
message: `Invalid subtask ID format: ${args.id}. Expected format: "parentId.subtaskId"`
|
||||
message: `Invalid subtask ID format: ${id}. Expected format: "parentId.subtaskId"`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Find the tasks.json path
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
// Use provided path
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
// Convert convertToTask to a boolean
|
||||
const convertToTask = args.convert === true;
|
||||
const convertToTask = convert === true;
|
||||
|
||||
// Determine if we should generate files
|
||||
const generateFiles = !args.skipGenerate;
|
||||
const generateFiles = !skipGenerate;
|
||||
|
||||
log.info(
|
||||
`Removing subtask ${args.id} (convertToTask: ${convertToTask}, generateFiles: ${generateFiles})`
|
||||
`Removing subtask ${id} (convertToTask: ${convertToTask}, generateFiles: ${generateFiles})`
|
||||
);
|
||||
|
||||
// Use the provided tasksPath
|
||||
const result = await removeSubtask(
|
||||
tasksPath,
|
||||
args.id,
|
||||
id,
|
||||
convertToTask,
|
||||
generateFiles
|
||||
);
|
||||
@@ -77,7 +93,7 @@ export async function removeSubtaskDirect(args, log) {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: `Subtask ${args.id} successfully converted to task #${result.id}`,
|
||||
message: `Subtask ${id} successfully converted to task #${result.id}`,
|
||||
task: result
|
||||
}
|
||||
};
|
||||
@@ -86,7 +102,7 @@ export async function removeSubtaskDirect(args, log) {
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: `Subtask ${args.id} successfully removed`
|
||||
message: `Subtask ${id} successfully removed`
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -3,41 +3,45 @@
|
||||
* Direct function implementation for removing a task
|
||||
*/
|
||||
|
||||
import { removeTask } from '../../../../scripts/modules/task-manager.js';
|
||||
import {
|
||||
removeTask,
|
||||
taskExists
|
||||
} from '../../../../scripts/modules/task-manager.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
disableSilentMode,
|
||||
readJSON
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Direct function wrapper for removeTask with error handling.
|
||||
* Supports removing multiple tasks at once with comma-separated IDs.
|
||||
*
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
|
||||
* @param {string} args.id - The ID(s) of the task(s) or subtask(s) to remove (comma-separated for multiple).
|
||||
* @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;
|
||||
try {
|
||||
// Find the tasks path first
|
||||
let tasksPath;
|
||||
try {
|
||||
tasksPath = findTasksJsonPath(args, log);
|
||||
} catch (error) {
|
||||
log.error(`Tasks file not found: ${error.message}`);
|
||||
// Check if tasksJsonPath was provided
|
||||
if (!tasksJsonPath) {
|
||||
log.error('removeTaskDirect called without tasksJsonPath');
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'FILE_NOT_FOUND_ERROR',
|
||||
message: error.message
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// Validate task ID parameter
|
||||
const taskId = args.id;
|
||||
if (!taskId) {
|
||||
if (!id) {
|
||||
log.error('Task ID is required');
|
||||
return {
|
||||
success: false,
|
||||
@@ -49,46 +53,103 @@ 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 ${tasksPath}`);
|
||||
// Split task IDs if comma-separated
|
||||
const taskIdArray = id.split(',').map((taskId) => taskId.trim());
|
||||
|
||||
try {
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
log.info(
|
||||
`Removing ${taskIdArray.length} task(s) with ID(s): ${taskIdArray.join(', ')} from ${tasksJsonPath}`
|
||||
);
|
||||
|
||||
// Call the core removeTask function
|
||||
const result = await removeTask(tasksPath, taskId);
|
||||
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
|
||||
log.info(`Successfully removed task: ${taskId}`);
|
||||
|
||||
// Return the result
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: result.message,
|
||||
taskId: taskId,
|
||||
tasksPath: tasksPath,
|
||||
removedTask: result.removedTask
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging even if there's an error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Error removing task: ${error.message}`);
|
||||
// Validate all task IDs exist before proceeding
|
||||
const data = readJSON(tasksJsonPath);
|
||||
if (!data || !data.tasks) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: error.code || 'REMOVE_TASK_ERROR',
|
||||
message: error.message || 'Failed to remove task'
|
||||
code: 'INVALID_TASKS_FILE',
|
||||
message: `No valid tasks found in ${tasksJsonPath}`
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
const invalidTasks = taskIdArray.filter(
|
||||
(taskId) => !taskExists(data.tasks, taskId)
|
||||
);
|
||||
|
||||
if (invalidTasks.length > 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INVALID_TASK_ID',
|
||||
message: `The following tasks were not found: ${invalidTasks.join(', ')}`
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// Remove tasks one by one
|
||||
const results = [];
|
||||
|
||||
// Enable silent mode to prevent console logs from interfering with JSON response
|
||||
enableSilentMode();
|
||||
|
||||
try {
|
||||
for (const taskId of taskIdArray) {
|
||||
try {
|
||||
const result = await removeTask(tasksJsonPath, taskId);
|
||||
results.push({
|
||||
taskId,
|
||||
success: true,
|
||||
message: result.message,
|
||||
removedTask: result.removedTask
|
||||
});
|
||||
log.info(`Successfully removed task: ${taskId}`);
|
||||
} catch (error) {
|
||||
results.push({
|
||||
taskId,
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
log.error(`Error removing task ${taskId}: ${error.message}`);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
// Restore normal logging
|
||||
disableSilentMode();
|
||||
}
|
||||
|
||||
// Check if all tasks were successfully removed
|
||||
const successfulRemovals = results.filter((r) => r.success);
|
||||
const failedRemovals = results.filter((r) => !r.success);
|
||||
|
||||
if (successfulRemovals.length === 0) {
|
||||
// All removals failed
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'REMOVE_TASK_ERROR',
|
||||
message: 'Failed to remove any tasks',
|
||||
details: failedRemovals
|
||||
.map((r) => `${r.taskId}: ${r.error}`)
|
||||
.join('; ')
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// At least some tasks were removed successfully
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
totalTasks: taskIdArray.length,
|
||||
successful: successfulRemovals.length,
|
||||
failed: failedRemovals.length,
|
||||
results: results,
|
||||
tasksPath: tasksJsonPath
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
} catch (error) {
|
||||
// Ensure silent mode is disabled even if an outer error occurs
|
||||
disableSilentMode();
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { setTaskStatus } from '../../../../scripts/modules/task-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode,
|
||||
@@ -14,16 +13,29 @@ import {
|
||||
/**
|
||||
* Direct function wrapper for setTaskStatus with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments containing id, status and file path options.
|
||||
* @param {Object} args - Command arguments containing id, status and tasksJsonPath.
|
||||
* @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;
|
||||
try {
|
||||
log.info(`Setting task status with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Check required parameters
|
||||
if (!args.id) {
|
||||
// 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) {
|
||||
const errorMessage =
|
||||
'No task ID specified. Please provide a task ID to update.';
|
||||
log.error(errorMessage);
|
||||
@@ -34,7 +46,7 @@ export async function setTaskStatusDirect(args, log) {
|
||||
};
|
||||
}
|
||||
|
||||
if (!args.status) {
|
||||
if (!status) {
|
||||
const errorMessage =
|
||||
'No status specified. Please provide a new status value.';
|
||||
log.error(errorMessage);
|
||||
@@ -45,32 +57,16 @@ export async function setTaskStatusDirect(args, log) {
|
||||
};
|
||||
}
|
||||
|
||||
// Get tasks file path
|
||||
let tasksPath;
|
||||
try {
|
||||
// The enhanced findTasksJsonPath will now search in parent directories if needed
|
||||
tasksPath = findTasksJsonPath(args, log);
|
||||
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
|
||||
};
|
||||
}
|
||||
// Use the provided path
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
// Execute core setTaskStatus function
|
||||
const taskId = args.id;
|
||||
const newStatus = args.status;
|
||||
const taskId = id;
|
||||
const newStatus = 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
|
||||
@@ -79,19 +75,20 @@ export async function setTaskStatusDirect(args, log) {
|
||||
log.info(`Successfully set task ${taskId} status to ${newStatus}`);
|
||||
|
||||
// Return success data
|
||||
result = {
|
||||
const result = {
|
||||
success: true,
|
||||
data: {
|
||||
message: `Successfully updated task ${taskId} status to "${newStatus}"`,
|
||||
taskId,
|
||||
status: newStatus,
|
||||
tasksPath
|
||||
tasksPath: tasksPath // Return the path used
|
||||
},
|
||||
fromCache: false // This operation always modifies state and should never be cached
|
||||
};
|
||||
return result;
|
||||
} catch (error) {
|
||||
log.error(`Error setting task status: ${error.message}`);
|
||||
result = {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'SET_STATUS_ERROR',
|
||||
@@ -103,8 +100,6 @@ 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()) {
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
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
|
||||
@@ -16,28 +15,29 @@ 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) {
|
||||
let tasksPath;
|
||||
try {
|
||||
// Find the tasks path first - needed for cache key and execution
|
||||
tasksPath = findTasksJsonPath(args, log);
|
||||
} catch (error) {
|
||||
log.error(`Tasks file not found: ${error.message}`);
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, id } = args;
|
||||
|
||||
if (!tasksJsonPath) {
|
||||
log.error('showTaskDirect called without tasksJsonPath');
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'FILE_NOT_FOUND_ERROR',
|
||||
message: error.message
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
},
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
|
||||
// Validate task ID
|
||||
const taskId = args.id;
|
||||
const taskId = id;
|
||||
if (!taskId) {
|
||||
log.error('Task ID is required');
|
||||
return {
|
||||
@@ -50,8 +50,8 @@ export async function showTaskDirect(args, log) {
|
||||
};
|
||||
}
|
||||
|
||||
// Generate cache key using task path and ID
|
||||
const cacheKey = `showTask:${tasksPath}:${taskId}`;
|
||||
// Generate cache key using the provided task path and ID
|
||||
const cacheKey = `showTask:${tasksJsonPath}:${taskId}`;
|
||||
|
||||
// Define the action function to be executed on cache miss
|
||||
const coreShowTaskAction = async () => {
|
||||
@@ -59,16 +59,19 @@ 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 ${tasksPath}`);
|
||||
log.info(
|
||||
`Retrieving task details for ID: ${taskId} from ${tasksJsonPath}`
|
||||
);
|
||||
|
||||
// Read tasks data
|
||||
const data = readJSON(tasksPath);
|
||||
// Read tasks data using the provided path
|
||||
const data = readJSON(tasksJsonPath);
|
||||
if (!data || !data.tasks) {
|
||||
disableSilentMode(); // Disable before returning
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'INVALID_TASKS_FILE',
|
||||
message: `No valid tasks found in ${tasksPath}`
|
||||
message: `No valid tasks found in ${tasksJsonPath}`
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -77,6 +80,7 @@ export async function showTaskDirect(args, log) {
|
||||
const task = findTaskById(data.tasks, taskId);
|
||||
|
||||
if (!task) {
|
||||
disableSilentMode(); // Disable before returning
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
getAnthropicClientForMCP,
|
||||
getPerplexityClientForMCP
|
||||
@@ -17,19 +16,31 @@ import {
|
||||
/**
|
||||
* Direct function wrapper for updateSubtaskById with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments containing id, prompt, useResearch and file path options.
|
||||
* @param {Object} args - Command arguments containing id, prompt, useResearch and tasksJsonPath.
|
||||
* @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;
|
||||
|
||||
try {
|
||||
log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Check required parameters
|
||||
if (!args.id) {
|
||||
// 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) {
|
||||
const errorMessage =
|
||||
'No subtask ID specified. Please provide a subtask ID to update.';
|
||||
log.error(errorMessage);
|
||||
@@ -40,7 +51,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
if (!args.prompt) {
|
||||
if (!prompt) {
|
||||
const errorMessage =
|
||||
'No prompt specified. Please provide a prompt with information to add to the subtask.';
|
||||
log.error(errorMessage);
|
||||
@@ -52,7 +63,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
}
|
||||
|
||||
// Validate subtask ID format
|
||||
const subtaskId = args.id;
|
||||
const subtaskId = 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);
|
||||
@@ -74,24 +85,14 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
// Get tasks file path
|
||||
let tasksPath;
|
||||
try {
|
||||
tasksPath = findTasksJsonPath(args, log);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks file: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'TASKS_FILE_ERROR', message: error.message },
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
// Use the provided path
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
// Get research flag
|
||||
const useResearch = args.research === true;
|
||||
const useResearch = research === true;
|
||||
|
||||
log.info(
|
||||
`Updating subtask with ID ${subtaskIdStr} with prompt "${args.prompt}" and research: ${useResearch}`
|
||||
`Updating subtask with ID ${subtaskIdStr} with prompt "${prompt}" and research: ${useResearch}`
|
||||
);
|
||||
|
||||
// Initialize the appropriate AI client based on research flag
|
||||
@@ -134,7 +135,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
const updatedSubtask = await updateSubtaskById(
|
||||
tasksPath,
|
||||
subtaskIdStr,
|
||||
args.prompt,
|
||||
prompt,
|
||||
useResearch,
|
||||
{
|
||||
session,
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
*/
|
||||
|
||||
import { updateTaskById } from '../../../../scripts/modules/task-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
@@ -17,19 +16,32 @@ import {
|
||||
/**
|
||||
* Direct function wrapper for updateTaskById with error handling.
|
||||
*
|
||||
* @param {Object} args - Command arguments containing id, prompt, useResearch and file path options.
|
||||
* @param {Object} args - Command arguments containing id, prompt, useResearch and tasksJsonPath.
|
||||
* @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;
|
||||
|
||||
try {
|
||||
log.info(`Updating task with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Check required parameters
|
||||
if (!args.id) {
|
||||
// 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) {
|
||||
const errorMessage =
|
||||
'No task ID specified. Please provide a task ID to update.';
|
||||
log.error(errorMessage);
|
||||
@@ -40,7 +52,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
if (!args.prompt) {
|
||||
if (!prompt) {
|
||||
const errorMessage =
|
||||
'No prompt specified. Please provide a prompt with new information for the task update.';
|
||||
log.error(errorMessage);
|
||||
@@ -53,15 +65,15 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
|
||||
// Parse taskId - handle both string and number values
|
||||
let taskId;
|
||||
if (typeof args.id === 'string') {
|
||||
if (typeof id === 'string') {
|
||||
// Handle subtask IDs (e.g., "5.2")
|
||||
if (args.id.includes('.')) {
|
||||
taskId = args.id; // Keep as string for subtask IDs
|
||||
if (id.includes('.')) {
|
||||
taskId = id; // Keep as string for subtask IDs
|
||||
} else {
|
||||
// Parse as integer for main task IDs
|
||||
taskId = parseInt(args.id, 10);
|
||||
taskId = parseInt(id, 10);
|
||||
if (isNaN(taskId)) {
|
||||
const errorMessage = `Invalid task ID: ${args.id}. Task ID must be a positive integer or subtask ID (e.g., "5.2").`;
|
||||
const errorMessage = `Invalid task ID: ${id}. Task ID must be a positive integer or subtask ID (e.g., "5.2").`;
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
@@ -71,24 +83,14 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
taskId = args.id;
|
||||
taskId = id;
|
||||
}
|
||||
|
||||
// Get tasks file path
|
||||
let tasksPath;
|
||||
try {
|
||||
tasksPath = findTasksJsonPath(args, log);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks file: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'TASKS_FILE_ERROR', message: error.message },
|
||||
fromCache: false
|
||||
};
|
||||
}
|
||||
// Use the provided path
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
// Get research flag
|
||||
const useResearch = args.research === true;
|
||||
const useResearch = research === true;
|
||||
|
||||
// Initialize appropriate AI client based on research flag
|
||||
let aiClient;
|
||||
@@ -113,7 +115,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
}
|
||||
|
||||
log.info(
|
||||
`Updating task with ID ${taskId} with prompt "${args.prompt}" and research: ${useResearch}`
|
||||
`Updating task with ID ${taskId} with prompt "${prompt}" and research: ${useResearch}`
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -133,7 +135,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
await updateTaskById(
|
||||
tasksPath,
|
||||
taskId,
|
||||
args.prompt,
|
||||
prompt,
|
||||
useResearch,
|
||||
{
|
||||
mcpLog: logWrapper, // Use our wrapper object that has the expected method structure
|
||||
@@ -149,7 +151,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
data: {
|
||||
message: `Successfully updated task with ID ${taskId} based on the prompt`,
|
||||
taskId,
|
||||
tasksPath,
|
||||
tasksPath: tasksPath, // Return the used path
|
||||
useResearch
|
||||
},
|
||||
fromCache: false // This operation always modifies state and should never be cached
|
||||
|
||||
@@ -8,7 +8,6 @@ import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
getAnthropicClientForMCP,
|
||||
getPerplexityClientForMCP
|
||||
@@ -17,19 +16,31 @@ import {
|
||||
/**
|
||||
* Direct function wrapper for updating tasks based on new context/prompt.
|
||||
*
|
||||
* @param {Object} args - Command arguments containing fromId, prompt, useResearch and file path options.
|
||||
* @param {Object} args - Command arguments containing fromId, prompt, useResearch and tasksJsonPath.
|
||||
* @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;
|
||||
|
||||
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 && args.from === undefined) {
|
||||
if (args.id !== undefined && 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);
|
||||
@@ -46,7 +57,7 @@ export async function updateTasksDirect(args, log, context = {}) {
|
||||
}
|
||||
|
||||
// Check required parameters
|
||||
if (!args.from) {
|
||||
if (!from) {
|
||||
const errorMessage =
|
||||
'No from ID specified. Please provide a task ID to start updating from.';
|
||||
log.error(errorMessage);
|
||||
@@ -57,7 +68,7 @@ export async function updateTasksDirect(args, log, context = {}) {
|
||||
};
|
||||
}
|
||||
|
||||
if (!args.prompt) {
|
||||
if (!prompt) {
|
||||
const errorMessage =
|
||||
'No prompt specified. Please provide a prompt with new context for task updates.';
|
||||
log.error(errorMessage);
|
||||
@@ -70,10 +81,10 @@ export async function updateTasksDirect(args, log, context = {}) {
|
||||
|
||||
// Parse fromId - handle both string and number values
|
||||
let fromId;
|
||||
if (typeof args.from === 'string') {
|
||||
fromId = parseInt(args.from, 10);
|
||||
if (typeof from === 'string') {
|
||||
fromId = parseInt(from, 10);
|
||||
if (isNaN(fromId)) {
|
||||
const errorMessage = `Invalid from ID: ${args.from}. Task ID must be a positive integer.`;
|
||||
const errorMessage = `Invalid from ID: ${from}. Task ID must be a positive integer.`;
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
@@ -82,24 +93,11 @@ export async function updateTasksDirect(args, log, context = {}) {
|
||||
};
|
||||
}
|
||||
} else {
|
||||
fromId = args.from;
|
||||
}
|
||||
|
||||
// Get tasks file path
|
||||
let tasksPath;
|
||||
try {
|
||||
tasksPath = findTasksJsonPath(args, log);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks file: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'TASKS_FILE_ERROR', message: error.message },
|
||||
fromCache: false
|
||||
};
|
||||
fromId = from;
|
||||
}
|
||||
|
||||
// Get research flag
|
||||
const useResearch = args.research === true;
|
||||
const useResearch = research === true;
|
||||
|
||||
// Initialize appropriate AI client based on research flag
|
||||
let aiClient;
|
||||
@@ -124,16 +122,25 @@ export async function updateTasksDirect(args, log, context = {}) {
|
||||
}
|
||||
|
||||
log.info(
|
||||
`Updating tasks from ID ${fromId} with prompt "${args.prompt}" and research: ${useResearch}`
|
||||
`Updating tasks from ID ${fromId} with prompt "${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(tasksPath, fromId, args.prompt, useResearch, {
|
||||
mcpLog: log,
|
||||
await updateTasks(tasksJsonPath, fromId, prompt, useResearch, {
|
||||
mcpLog: logWrapper, // Pass the wrapper instead of the raw log object
|
||||
session
|
||||
});
|
||||
|
||||
@@ -144,7 +151,7 @@ export async function updateTasksDirect(args, log, context = {}) {
|
||||
data: {
|
||||
message: `Successfully updated tasks from ID ${fromId} based on the prompt`,
|
||||
fromId,
|
||||
tasksPath,
|
||||
tasksPath: tasksJsonPath,
|
||||
useResearch
|
||||
},
|
||||
fromCache: false // This operation always modifies state and should never be cached
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
*/
|
||||
|
||||
import { validateDependenciesCommand } from '../../../../scripts/modules/dependency-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
@@ -13,17 +12,30 @@ import fs from 'fs';
|
||||
/**
|
||||
* Validate dependencies in tasks.json
|
||||
* @param {Object} args - Function arguments
|
||||
* @param {string} [args.file] - Path to the tasks file
|
||||
* @param {string} [args.projectRoot] - Project root directory
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
|
||||
*/
|
||||
export async function validateDependenciesDirect(args, log) {
|
||||
try {
|
||||
log.info(`Validating dependencies in tasks...`);
|
||||
// Destructure the explicit tasksJsonPath
|
||||
const { tasksJsonPath } = args;
|
||||
|
||||
// Find the tasks.json path
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
if (!tasksJsonPath) {
|
||||
log.error('validateDependenciesDirect called without tasksJsonPath');
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
log.info(`Validating dependencies in tasks: ${tasksJsonPath}`);
|
||||
|
||||
// Use the provided tasksJsonPath
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
// Verify the file exists
|
||||
if (!fs.existsSync(tasksPath)) {
|
||||
@@ -39,7 +51,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
|
||||
// Call the original command function using the provided tasksPath
|
||||
await validateDependenciesCommand(tasksPath);
|
||||
|
||||
// Restore normal logging
|
||||
|
||||
@@ -291,3 +291,103 @@ function findTasksWithNpmConsideration(startDir, log) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -32,39 +33,52 @@ export function registerAddDependencyTool(server) {
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session, reportProgress }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(
|
||||
`Adding dependency for task ${args.id} to depend on ${args.dependsOn}`
|
||||
);
|
||||
reportProgress({ progress: 0 });
|
||||
|
||||
// Get project root using the utility function
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
// 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}`);
|
||||
// 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.'
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function with the resolved 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
|
||||
const result = await addDependencyDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
// Pass the explicitly resolved path
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
// Pass other relevant args
|
||||
id: args.id,
|
||||
dependsOn: args.dependsOn
|
||||
},
|
||||
log,
|
||||
{ reportProgress, mcpLog: log, session }
|
||||
log
|
||||
// Remove context object
|
||||
);
|
||||
|
||||
reportProgress({ progress: 100 });
|
||||
|
||||
// Log result
|
||||
if (result.success) {
|
||||
log.info(`Successfully added dependency: ${result.data.message}`);
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -57,29 +58,48 @@ export function registerAddSubtaskTool(server) {
|
||||
.describe('Skip regenerating task files'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session, reportProgress }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Adding subtask with args: ${JSON.stringify(args)}`);
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
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 result = await addSubtaskDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
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
|
||||
},
|
||||
log,
|
||||
{ reportProgress, mcpLog: log, session }
|
||||
log
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
|
||||
@@ -12,6 +12,7 @@ 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
|
||||
@@ -58,35 +59,54 @@ export function registerAddTaskTool(server) {
|
||||
.describe('Path to the tasks file (default: tasks/tasks.json)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
),
|
||||
.describe('The directory of the project. Must be an absolute path.'),
|
||||
research: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Whether to use research capabilities for task creation')
|
||||
}),
|
||||
execute: async (args, { log, reportProgress, session }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting add-task with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Get project root from session
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function
|
||||
const result = await addTaskDirect(
|
||||
{
|
||||
...args,
|
||||
projectRoot: rootFolder
|
||||
// Pass the explicitly resolved path
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
// Pass other relevant args
|
||||
prompt: args.prompt,
|
||||
dependencies: args.dependencies,
|
||||
priority: args.priority,
|
||||
research: args.research
|
||||
},
|
||||
log,
|
||||
{ reportProgress, session }
|
||||
{ session }
|
||||
);
|
||||
|
||||
// Return the result
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
getProjectRootFromSession
|
||||
} 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
|
||||
@@ -53,10 +55,7 @@ export function registerAnalyzeTool(server) {
|
||||
.describe('Use Perplexity AI for research-backed complexity analysis'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
@@ -64,17 +63,40 @@ export function registerAnalyzeTool(server) {
|
||||
`Analyzing task complexity with args: ${JSON.stringify(args)}`
|
||||
);
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
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(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
outputPath: outputPath,
|
||||
model: args.model,
|
||||
threshold: args.threshold,
|
||||
research: args.research
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -34,38 +35,53 @@ export function registerClearSubtasksTool(server) {
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
})
|
||||
.refine((data) => data.id || data.all, {
|
||||
message: "Either 'id' or 'all' parameter must be provided",
|
||||
path: ['id', 'all']
|
||||
}),
|
||||
execute: async (args, { log, session, reportProgress }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`);
|
||||
await reportProgress({ progress: 0 });
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await clearSubtasksDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
// Pass the explicitly resolved path
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
// Pass other relevant args
|
||||
id: args.id,
|
||||
all: args.all
|
||||
},
|
||||
log,
|
||||
{ reportProgress, mcpLog: log, session }
|
||||
log
|
||||
// Remove context object as clearSubtasksDirect likely doesn't need session/reportProgress
|
||||
);
|
||||
|
||||
reportProgress({ progress: 100 });
|
||||
|
||||
if (result.success) {
|
||||
log.info(`Subtasks cleared successfully: ${result.data.message}`);
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -28,35 +29,40 @@ export function registerComplexityReportTool(server) {
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session, reportProgress }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(
|
||||
`Getting complexity report with args: ${JSON.stringify(args)}`
|
||||
);
|
||||
// await reportProgress({ progress: 0 });
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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 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(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
// Pass the explicitly resolved path
|
||||
reportPath: reportPath
|
||||
// No other args specific to this tool
|
||||
},
|
||||
log /*, { reportProgress, mcpLog: log, session}*/
|
||||
log
|
||||
);
|
||||
|
||||
// await reportProgress({ progress: 100 });
|
||||
|
||||
if (result.success) {
|
||||
log.info(
|
||||
`Successfully retrieved complexity report${result.fromCache ? ' (from cache)' : ''}`
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -48,26 +49,46 @@ export function registerExpandAllTool(server) {
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Expanding all tasks with args: ${JSON.stringify(args)}`);
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await expandAllTasksDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
// Pass the explicitly resolved path
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
// Pass other relevant args
|
||||
num: args.num,
|
||||
research: args.research,
|
||||
prompt: args.prompt,
|
||||
force: args.force
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
|
||||
@@ -10,6 +10,7 @@ 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';
|
||||
|
||||
@@ -23,10 +24,7 @@ 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
|
||||
.union([z.string(), z.number()])
|
||||
.optional()
|
||||
.describe('Number of subtasks to generate'),
|
||||
num: z.string().optional().describe('Number of subtasks to generate'),
|
||||
research: z
|
||||
.boolean()
|
||||
.optional()
|
||||
@@ -38,42 +36,52 @@ export function registerExpandTaskTool(server) {
|
||||
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.'),
|
||||
force: z.boolean().optional().describe('Force the expansion')
|
||||
}),
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Starting expand-task with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// Get project root from session
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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.'
|
||||
);
|
||||
}
|
||||
|
||||
log.info(`Project root resolved to: ${rootFolder}`);
|
||||
|
||||
// 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}`);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call direct function with only session in the context, not reportProgress
|
||||
// Use the pattern recommended in the MCP guidelines
|
||||
const result = await expandTaskDirect(
|
||||
{
|
||||
...args,
|
||||
projectRoot: rootFolder
|
||||
// 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
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -23,34 +24,42 @@ export function registerFixDependenciesTool(server) {
|
||||
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session, reportProgress }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Fixing dependencies with args: ${JSON.stringify(args)}`);
|
||||
await reportProgress({ progress: 0 });
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
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 result = await fixDependenciesDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
tasksJsonPath: tasksJsonPath
|
||||
},
|
||||
log,
|
||||
{ reportProgress, mcpLog: log, session }
|
||||
log
|
||||
);
|
||||
|
||||
await reportProgress({ progress: 100 });
|
||||
|
||||
if (result.success) {
|
||||
log.info(`Successfully fixed dependencies: ${result.data.message}`);
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,8 @@ 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
|
||||
@@ -28,33 +30,52 @@ export function registerGenerateTool(server) {
|
||||
.describe('Output directory (default: same directory as tasks file)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session, reportProgress }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Generating task files with args: ${JSON.stringify(args)}`);
|
||||
// await reportProgress({ progress: 0 });
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
// 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(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
// Pass the explicitly resolved paths
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
outputDir: outputDir
|
||||
// No other args specific to this tool
|
||||
},
|
||||
log /*, { reportProgress, mcpLog: log, session}*/
|
||||
log
|
||||
);
|
||||
|
||||
// await reportProgress({ progress: 100 });
|
||||
|
||||
if (result.success) {
|
||||
log.info(`Successfully generated task files: ${result.data.message}`);
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -42,12 +43,9 @@ export function registerShowTaskTool(server) {
|
||||
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session, reportProgress }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
// Log the session right at the start of execute
|
||||
log.info(
|
||||
`Session object received in execute: ${JSON.stringify(session)}`
|
||||
@@ -60,26 +58,43 @@ export function registerShowTaskTool(server) {
|
||||
`Session object received in execute: ${JSON.stringify(session)}`
|
||||
); // Use JSON.stringify for better visibility
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
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}`
|
||||
// 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.'
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
// Pass the explicitly resolved path
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
// Pass other relevant args
|
||||
id: args.id
|
||||
},
|
||||
log
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -39,33 +40,47 @@ export function registerListTasksTool(server) {
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: automatically detected from session or CWD)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session, reportProgress }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Getting tasks with filters: ${JSON.stringify(args)}`);
|
||||
// await reportProgress({ progress: 0 });
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await listTasksDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
status: args.status,
|
||||
withSubtasks: args.withSubtasks
|
||||
},
|
||||
log /*, { reportProgress, mcpLog: log, session}*/
|
||||
log
|
||||
);
|
||||
|
||||
// await reportProgress({ progress: 100 });
|
||||
|
||||
log.info(
|
||||
`Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks${result.fromCache ? ' (from cache)' : ''}`
|
||||
);
|
||||
|
||||
@@ -10,32 +10,8 @@ export function registerInitializeProjectTool(server) {
|
||||
server.addTool({
|
||||
name: 'initialize_project',
|
||||
description:
|
||||
"Initializes a new Task Master project structure by calling the core initialization logic. Derives target directory from client session. If project details (name, description, author) are not provided, prompts the user or skips if 'yes' flag is true. DO NOT run without parameters.",
|
||||
'Initializes a new Task Master project structure by calling the core initialization logic. Creates necessary folders and configuration files for Task Master in the current directory.',
|
||||
parameters: z.object({
|
||||
projectName: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'The name for the new project. If not provided, prompt the user for it.'
|
||||
),
|
||||
projectDescription: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'A brief description for the project. If not provided, prompt the user for it.'
|
||||
),
|
||||
projectVersion: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"The initial version for the project (e.g., '0.1.0'). User input not needed unless user requests to override."
|
||||
),
|
||||
authorName: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
"The author's name. User input not needed unless user requests to override."
|
||||
),
|
||||
skipInstall: z
|
||||
.boolean()
|
||||
.optional()
|
||||
@@ -47,15 +23,13 @@ export function registerInitializeProjectTool(server) {
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe(
|
||||
'Add shell aliases (tm, taskmaster) to shell config file. User input not needed.'
|
||||
),
|
||||
.describe('Add shell aliases (tm, taskmaster) to shell config file.'),
|
||||
yes: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.default(true)
|
||||
.describe(
|
||||
"Skip prompts and use default values or provided arguments. Use true if you wish to skip details like the project name, etc. If the project information required for the initialization is not available or provided by the user, prompt if the user wishes to provide them (name, description, author) or skip them. If the user wishes to skip, set the 'yes' flag to true and do not set any other parameters."
|
||||
'Skip prompts and use default values. Always set to true for MCP tools.'
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -24,33 +25,46 @@ export function registerNextTaskTool(server) {
|
||||
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session, reportProgress }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Finding next task with args: ${JSON.stringify(args)}`);
|
||||
// await reportProgress({ progress: 0 });
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await nextTaskDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
// Pass the explicitly resolved path
|
||||
tasksJsonPath: tasksJsonPath
|
||||
// No other args specific to this tool
|
||||
},
|
||||
log /*, { reportProgress, mcpLog: log, session}*/
|
||||
log
|
||||
);
|
||||
|
||||
// await reportProgress({ progress: 100 });
|
||||
|
||||
if (result.success) {
|
||||
log.info(
|
||||
`Successfully found next task: ${result.data?.task?.id || 'No available tasks'}`
|
||||
|
||||
@@ -5,11 +5,16 @@
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
getProjectRootFromSession,
|
||||
handleApiResult,
|
||||
createErrorResponse,
|
||||
getProjectRootFromSession
|
||||
createErrorResponse
|
||||
} 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
|
||||
@@ -23,6 +28,7 @@ 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
|
||||
@@ -35,47 +41,59 @@ export function registerParsePRDTool(server) {
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Output absolute path for tasks.json file (default: tasks/tasks.json)'
|
||||
'Output path for tasks.json file (default: tasks/tasks.json)'
|
||||
),
|
||||
force: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe('Allow overwriting an existing tasks.json file.'),
|
||||
append: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Append new tasks to existing tasks.json instead of overwriting'
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe(
|
||||
'Absolute path to the root directory of the project. Required - ALWAYS SET THIS TO THE PROJECT ROOT DIRECTORY.'
|
||||
)
|
||||
.describe('The directory of the project. Must be absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Parsing PRD with args: ${JSON.stringify(args)}`);
|
||||
|
||||
// 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;
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
// 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'
|
||||
return createErrorResponse(
|
||||
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
|
||||
);
|
||||
rootFolder = getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder) {
|
||||
const errorMessage =
|
||||
'Could not determine project root directory. Please provide projectRoot parameter.';
|
||||
log.error(errorMessage);
|
||||
return createErrorResponse(errorMessage);
|
||||
}
|
||||
}
|
||||
|
||||
log.info(`Using project root: ${rootFolder} for PRD parsing`);
|
||||
// Resolve input (PRD) and output (tasks.json) paths using the utility
|
||||
const { projectRoot, prdPath, tasksJsonPath } = resolveProjectPaths(
|
||||
rootFolder,
|
||||
args,
|
||||
log
|
||||
);
|
||||
|
||||
// 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: rootFolder,
|
||||
...args
|
||||
projectRoot: projectRoot,
|
||||
input: prdPath,
|
||||
output: tasksJsonPath,
|
||||
numTasks: args.numTasks,
|
||||
force: args.force,
|
||||
append: args.append
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -30,35 +31,50 @@ export function registerRemoveDependencyTool(server) {
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session, reportProgress }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(
|
||||
`Removing dependency for task ${args.id} from ${args.dependsOn} with args: ${JSON.stringify(args)}`
|
||||
);
|
||||
// await reportProgress({ progress: 0 });
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await removeDependencyDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
// Pass the explicitly resolved path
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
// Pass other relevant args
|
||||
id: args.id,
|
||||
dependsOn: args.dependsOn
|
||||
},
|
||||
log /*, { reportProgress, mcpLog: log, session}*/
|
||||
log
|
||||
);
|
||||
|
||||
// await reportProgress({ progress: 100 });
|
||||
|
||||
if (result.success) {
|
||||
log.info(`Successfully removed dependency: ${result.data.message}`);
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -43,33 +44,49 @@ export function registerRemoveSubtaskTool(server) {
|
||||
.describe('Skip regenerating task files'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session, reportProgress }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
|
||||
// await reportProgress({ progress: 0 });
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await removeSubtaskDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
// Pass the explicitly resolved path
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
// Pass other relevant args
|
||||
id: args.id,
|
||||
convert: args.convert,
|
||||
skipGenerate: args.skipGenerate
|
||||
},
|
||||
log /*, { reportProgress, mcpLog: log, session}*/
|
||||
log
|
||||
);
|
||||
|
||||
// await reportProgress({ progress: 100 });
|
||||
|
||||
if (result.success) {
|
||||
log.info(`Subtask removed successfully: ${result.data.message}`);
|
||||
} else {
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -22,14 +23,13 @@ export function registerRemoveTaskTool(server) {
|
||||
parameters: z.object({
|
||||
id: z
|
||||
.string()
|
||||
.describe("ID of the task or subtask to remove (e.g., '5' or '5.2')"),
|
||||
.describe(
|
||||
"ID(s) of the task(s) or subtask(s) to remove (e.g., '5' or '5.2' or '5,6,7')"
|
||||
),
|
||||
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
),
|
||||
.describe('The directory of the project. Must be an absolute path.'),
|
||||
confirm: z
|
||||
.boolean()
|
||||
.optional()
|
||||
@@ -37,30 +37,42 @@ export function registerRemoveTaskTool(server) {
|
||||
}),
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Removing task with ID: ${args.id}`);
|
||||
log.info(`Removing task(s) with ID(s): ${args.id}`);
|
||||
|
||||
// Get project root from session
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
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}`
|
||||
// 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.'
|
||||
);
|
||||
}
|
||||
|
||||
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(
|
||||
{
|
||||
id: args.id,
|
||||
file: args.file,
|
||||
projectRoot: rootFolder
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
id: args.id
|
||||
},
|
||||
log
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -33,28 +34,45 @@ export function registerSetTaskStatusTool(server) {
|
||||
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: automatically detected)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Setting status of task(s) ${args.id} to: ${args.status}`);
|
||||
|
||||
// Get project root from session
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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.'
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function with the project 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}`
|
||||
);
|
||||
}
|
||||
|
||||
// Call the direct function with the resolved path
|
||||
const result = await setTaskStatusDirect(
|
||||
{
|
||||
...args,
|
||||
projectRoot: rootFolder
|
||||
// Pass the explicitly resolved path
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
// Pass other relevant args
|
||||
id: args.id,
|
||||
status: args.status
|
||||
},
|
||||
log
|
||||
);
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -19,12 +20,12 @@ export function registerUpdateSubtaskTool(server) {
|
||||
server.addTool({
|
||||
name: 'update_subtask',
|
||||
description:
|
||||
'Appends additional information to a specific subtask without replacing existing content',
|
||||
'Appends timestamped information to a specific subtask without replacing existing content',
|
||||
parameters: z.object({
|
||||
id: z
|
||||
.string()
|
||||
.describe(
|
||||
'ID of the subtask to update in format "parentId.subtaskId" (e.g., "5.2")'
|
||||
'ID of the subtask to update in format "parentId.subtaskId" (e.g., "5.2"). Parent ID is the ID of the task that contains the subtask.'
|
||||
),
|
||||
prompt: z.string().describe('Information to add to the subtask'),
|
||||
research: z
|
||||
@@ -34,26 +35,45 @@ export function registerUpdateSubtaskTool(server) {
|
||||
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await updateSubtaskByIdDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
// Pass the explicitly resolved path
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
// Pass other relevant args
|
||||
id: args.id,
|
||||
prompt: args.prompt,
|
||||
research: args.research
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -23,7 +24,9 @@ export function registerUpdateTaskTool(server) {
|
||||
parameters: z.object({
|
||||
id: z
|
||||
.string()
|
||||
.describe("ID of the task or subtask (e.g., '15', '15.2') to update"),
|
||||
.describe(
|
||||
"ID of the task (e.g., '15') to update. Subtasks are supported using the update-subtask tool."
|
||||
),
|
||||
prompt: z
|
||||
.string()
|
||||
.describe('New information or context to incorporate into the task'),
|
||||
@@ -34,26 +37,45 @@ export function registerUpdateTaskTool(server) {
|
||||
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Updating task with args: ${JSON.stringify(args)}`);
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await updateTaskByIdDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
// Pass the explicitly resolved path
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
// Pass other relevant args
|
||||
id: args.id,
|
||||
prompt: args.prompt,
|
||||
research: args.research
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -19,7 +20,7 @@ export function registerUpdateTool(server) {
|
||||
server.addTool({
|
||||
name: 'update',
|
||||
description:
|
||||
"Update multiple upcoming tasks (with ID >= 'from' ID) based on new context or changes provided in the prompt. Use 'update_task' instead for a single specific task.",
|
||||
"Update multiple upcoming tasks (with ID >= 'from' ID) based on new context or changes provided in the prompt. Use 'update_task' instead for a single specific task or 'update_subtask' for subtasks.",
|
||||
parameters: z.object({
|
||||
from: z
|
||||
.string()
|
||||
@@ -36,26 +37,43 @@ export function registerUpdateTool(server) {
|
||||
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Updating tasks with args: ${JSON.stringify(args)}`);
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
// 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}`
|
||||
);
|
||||
}
|
||||
|
||||
const result = await updateTasksDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
from: args.from,
|
||||
prompt: args.prompt,
|
||||
research: args.research
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
|
||||
@@ -10,6 +10,7 @@ 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
|
||||
@@ -24,34 +25,42 @@ export function registerValidateDependenciesTool(server) {
|
||||
file: z.string().optional().describe('Absolute path to the tasks file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (default: current working directory)'
|
||||
)
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
}),
|
||||
execute: async (args, { log, session, reportProgress }) => {
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Validating dependencies with args: ${JSON.stringify(args)}`);
|
||||
await reportProgress({ progress: 0 });
|
||||
|
||||
let rootFolder = getProjectRootFromSession(session, log);
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
if (!rootFolder && args.projectRoot) {
|
||||
rootFolder = args.projectRoot;
|
||||
log.info(`Using project root from args as fallback: ${rootFolder}`);
|
||||
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 result = await validateDependenciesDirect(
|
||||
{
|
||||
projectRoot: rootFolder,
|
||||
...args
|
||||
tasksJsonPath: tasksJsonPath
|
||||
},
|
||||
log,
|
||||
{ reportProgress, mcpLog: log, session }
|
||||
log
|
||||
);
|
||||
|
||||
await reportProgress({ progress: 100 });
|
||||
|
||||
if (result.success) {
|
||||
log.info(
|
||||
`Successfully validated dependencies: ${result.data.message}`
|
||||
|
||||
16113
package-lock.json
generated
16113
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"name": "task-master-ai",
|
||||
"version": "0.10.1",
|
||||
"version": "0.13.0",
|
||||
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"bin": {
|
||||
"task-master": "bin/task-master.js",
|
||||
"task-master-mcp": "mcp-server/server.js"
|
||||
"task-master-mcp": "mcp-server/server.js",
|
||||
"task-master-ai": "mcp-server/server.js"
|
||||
},
|
||||
"scripts": {
|
||||
"test": "node --experimental-vm-modules node_modules/.bin/jest",
|
||||
|
||||
377
scripts/init.js
377
scripts/init.js
@@ -15,7 +15,6 @@
|
||||
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { execSync } from 'child_process';
|
||||
import readline from 'readline';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname } from 'path';
|
||||
@@ -23,23 +22,7 @@ import chalk from 'chalk';
|
||||
import figlet from 'figlet';
|
||||
import boxen from 'boxen';
|
||||
import gradient from 'gradient-string';
|
||||
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);
|
||||
}
|
||||
import { isSilentMode } from './modules/utils.js';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = dirname(__filename);
|
||||
@@ -195,9 +178,6 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) {
|
||||
|
||||
// Map template names to their actual source paths
|
||||
switch (templateName) {
|
||||
case 'dev.js':
|
||||
sourcePath = path.join(__dirname, 'dev.js');
|
||||
break;
|
||||
case 'scripts_README.md':
|
||||
sourcePath = path.join(__dirname, '..', 'assets', 'scripts_README.md');
|
||||
break;
|
||||
@@ -313,61 +293,8 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle package.json - merge dependencies
|
||||
if (filename === 'package.json') {
|
||||
log('info', `${targetPath} already exists, merging dependencies...`);
|
||||
try {
|
||||
const existingPackageJson = JSON.parse(
|
||||
fs.readFileSync(targetPath, 'utf8')
|
||||
);
|
||||
const newPackageJson = JSON.parse(content);
|
||||
|
||||
// Merge dependencies, preferring existing versions in case of conflicts
|
||||
existingPackageJson.dependencies = {
|
||||
...newPackageJson.dependencies,
|
||||
...existingPackageJson.dependencies
|
||||
};
|
||||
|
||||
// Add our scripts if they don't already exist
|
||||
existingPackageJson.scripts = {
|
||||
...existingPackageJson.scripts,
|
||||
...Object.fromEntries(
|
||||
Object.entries(newPackageJson.scripts).filter(
|
||||
([key]) => !existingPackageJson.scripts[key]
|
||||
)
|
||||
)
|
||||
};
|
||||
|
||||
// Preserve existing type if present
|
||||
if (!existingPackageJson.type && newPackageJson.type) {
|
||||
existingPackageJson.type = newPackageJson.type;
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
targetPath,
|
||||
JSON.stringify(existingPackageJson, null, 2)
|
||||
);
|
||||
log(
|
||||
'success',
|
||||
`Updated ${targetPath} with required dependencies and scripts`
|
||||
);
|
||||
} catch (error) {
|
||||
log('error', `Failed to merge package.json: ${error.message}`);
|
||||
// Fallback to writing a backup of the existing file and creating a new one
|
||||
const backupPath = `${targetPath}.backup-${Date.now()}`;
|
||||
fs.copyFileSync(targetPath, backupPath);
|
||||
log('info', `Created backup of existing package.json at ${backupPath}`);
|
||||
fs.writeFileSync(targetPath, content);
|
||||
log(
|
||||
'warn',
|
||||
`Replaced ${targetPath} with new content (due to JSON parsing error)`
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle README.md - offer to preserve or create a different file
|
||||
if (filename === 'README.md') {
|
||||
if (filename === 'README-task-master.md') {
|
||||
log('info', `${targetPath} already exists`);
|
||||
// Create a separate README file specifically for this project
|
||||
const taskMasterReadmePath = path.join(
|
||||
@@ -377,7 +304,7 @@ function copyTemplateFile(templateName, targetPath, replacements = {}) {
|
||||
fs.writeFileSync(taskMasterReadmePath, content);
|
||||
log(
|
||||
'success',
|
||||
`Created ${taskMasterReadmePath} (preserved original README.md)`
|
||||
`Created ${taskMasterReadmePath} (preserved original README-task-master.md)`
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -408,12 +335,11 @@ async function initializeProject(options = {}) {
|
||||
console.log('===== DEBUG: INITIALIZE PROJECT OPTIONS RECEIVED =====');
|
||||
console.log('Full options object:', JSON.stringify(options));
|
||||
console.log('options.yes:', options.yes);
|
||||
console.log('options.name:', options.name);
|
||||
console.log('==================================================');
|
||||
}
|
||||
|
||||
// Determine if we should skip prompts based on the passed options
|
||||
const skipPrompts = options.yes || (options.name && options.description);
|
||||
const skipPrompts = options.yes;
|
||||
if (!isSilentMode()) {
|
||||
console.log('Skip prompts determined:', skipPrompts);
|
||||
}
|
||||
@@ -423,49 +349,24 @@ async function initializeProject(options = {}) {
|
||||
console.log('SKIPPING PROMPTS - Using defaults or provided values');
|
||||
}
|
||||
|
||||
// Use provided options or defaults
|
||||
const projectName = options.name || 'task-master-project';
|
||||
const projectDescription =
|
||||
options.description || 'A project managed with Task Master AI';
|
||||
const projectVersion = options.version || '0.1.0'; // Default from commands.js or here
|
||||
const authorName = options.author || 'Vibe coder'; // Default if not provided
|
||||
// We no longer need these variables
|
||||
const dryRun = options.dryRun || false;
|
||||
const skipInstall = options.skipInstall || false;
|
||||
const addAliases = options.aliases || false;
|
||||
|
||||
if (dryRun) {
|
||||
log('info', 'DRY RUN MODE: No files will be modified');
|
||||
log(
|
||||
'info',
|
||||
`Would initialize project: ${projectName} (${projectVersion})`
|
||||
);
|
||||
log('info', `Description: ${projectDescription}`);
|
||||
log('info', `Author: ${authorName || 'Not specified'}`);
|
||||
log('info', 'Would initialize Task Master project');
|
||||
log('info', 'Would create/update necessary project files');
|
||||
if (addAliases) {
|
||||
log('info', 'Would add shell aliases for task-master');
|
||||
}
|
||||
if (!skipInstall) {
|
||||
log('info', 'Would install dependencies');
|
||||
}
|
||||
return {
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName,
|
||||
dryRun: true
|
||||
};
|
||||
}
|
||||
|
||||
// Create structure using determined values
|
||||
createProjectStructure(
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName,
|
||||
skipInstall,
|
||||
addAliases
|
||||
);
|
||||
// Create structure using only necessary values
|
||||
createProjectStructure(addAliases);
|
||||
} else {
|
||||
// Prompting logic (only runs if skipPrompts is false)
|
||||
log('info', 'Required options not provided, proceeding with prompts.');
|
||||
@@ -475,41 +376,17 @@ async function initializeProject(options = {}) {
|
||||
});
|
||||
|
||||
try {
|
||||
// Prompt user for input...
|
||||
const projectName = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Enter project name: ')
|
||||
);
|
||||
const projectDescription = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Enter project description: ')
|
||||
);
|
||||
const projectVersionInput = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Enter project version (default: 1.0.0): ')
|
||||
); // Use a default for prompt
|
||||
const authorName = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Enter your name: ')
|
||||
);
|
||||
// Only prompt for shell aliases
|
||||
const addAliasesInput = await promptQuestion(
|
||||
rl,
|
||||
chalk.cyan('Add shell aliases for task-master? (Y/n): ')
|
||||
chalk.cyan(
|
||||
'Add shell aliases for task-master? This lets you type "tm" instead of "task-master" (Y/n): '
|
||||
)
|
||||
);
|
||||
const addAliasesPrompted = addAliasesInput.trim().toLowerCase() !== 'n';
|
||||
const projectVersion = projectVersionInput.trim()
|
||||
? projectVersionInput
|
||||
: '1.0.0';
|
||||
|
||||
// Confirm settings...
|
||||
console.log('\nProject settings:');
|
||||
console.log(chalk.blue('Name:'), chalk.white(projectName));
|
||||
console.log(chalk.blue('Description:'), chalk.white(projectDescription));
|
||||
console.log(chalk.blue('Version:'), chalk.white(projectVersion));
|
||||
console.log(
|
||||
chalk.blue('Author:'),
|
||||
chalk.white(authorName || 'Not specified')
|
||||
);
|
||||
console.log('\nTask Master Project settings:');
|
||||
console.log(
|
||||
chalk.blue(
|
||||
'Add shell aliases (so you can use "tm" instead of "task-master"):'
|
||||
@@ -530,43 +407,23 @@ async function initializeProject(options = {}) {
|
||||
return; // Added return for clarity
|
||||
}
|
||||
|
||||
// Still respect dryRun/skipInstall if passed initially even when prompting
|
||||
// Still respect dryRun if passed initially even when prompting
|
||||
const dryRun = options.dryRun || false;
|
||||
const skipInstall = options.skipInstall || false;
|
||||
|
||||
if (dryRun) {
|
||||
log('info', 'DRY RUN MODE: No files will be modified');
|
||||
log(
|
||||
'info',
|
||||
`Would initialize project: ${projectName} (${projectVersion})`
|
||||
);
|
||||
log('info', `Description: ${projectDescription}`);
|
||||
log('info', `Author: ${authorName || 'Not specified'}`);
|
||||
log('info', 'Would initialize Task Master project');
|
||||
log('info', 'Would create/update necessary project files');
|
||||
if (addAliasesPrompted) {
|
||||
log('info', 'Would add shell aliases for task-master');
|
||||
}
|
||||
if (!skipInstall) {
|
||||
log('info', 'Would install dependencies');
|
||||
}
|
||||
return {
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName,
|
||||
dryRun: true
|
||||
};
|
||||
}
|
||||
|
||||
// Create structure using prompted values, respecting initial options where relevant
|
||||
createProjectStructure(
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName,
|
||||
skipInstall, // Use value from initial options
|
||||
addAliasesPrompted // Use value from prompt
|
||||
);
|
||||
// Create structure using only necessary values
|
||||
createProjectStructure(addAliasesPrompted);
|
||||
} catch (error) {
|
||||
rl.close();
|
||||
log('error', `Error during prompting: ${error.message}`); // Use log function
|
||||
@@ -585,14 +442,7 @@ function promptQuestion(rl, question) {
|
||||
}
|
||||
|
||||
// Function to create the project structure
|
||||
function createProjectStructure(
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName,
|
||||
skipInstall,
|
||||
addAliases
|
||||
) {
|
||||
function createProjectStructure(addAliases) {
|
||||
const targetDir = process.cwd();
|
||||
log('info', `Initializing project in ${targetDir}`);
|
||||
|
||||
@@ -601,112 +451,11 @@ function createProjectStructure(
|
||||
ensureDirectoryExists(path.join(targetDir, 'scripts'));
|
||||
ensureDirectoryExists(path.join(targetDir, 'tasks'));
|
||||
|
||||
// Define our package.json content
|
||||
const packageJson = {
|
||||
name: projectName.toLowerCase().replace(/\s+/g, '-'),
|
||||
version: projectVersion,
|
||||
description: projectDescription,
|
||||
author: authorName,
|
||||
type: 'module',
|
||||
scripts: {
|
||||
dev: 'node scripts/dev.js',
|
||||
list: 'node scripts/dev.js list',
|
||||
generate: 'node scripts/dev.js generate',
|
||||
'parse-prd': 'node scripts/dev.js parse-prd'
|
||||
},
|
||||
dependencies: {
|
||||
'@anthropic-ai/sdk': '^0.39.0',
|
||||
boxen: '^8.0.1',
|
||||
chalk: '^4.1.2',
|
||||
commander: '^11.1.0',
|
||||
'cli-table3': '^0.6.5',
|
||||
cors: '^2.8.5',
|
||||
dotenv: '^16.3.1',
|
||||
express: '^4.21.2',
|
||||
fastmcp: '^1.20.5',
|
||||
figlet: '^1.8.0',
|
||||
'fuse.js': '^7.0.0',
|
||||
'gradient-string': '^3.0.0',
|
||||
helmet: '^8.1.0',
|
||||
inquirer: '^12.5.0',
|
||||
jsonwebtoken: '^9.0.2',
|
||||
'lru-cache': '^10.2.0',
|
||||
openai: '^4.89.0',
|
||||
ora: '^8.2.0'
|
||||
}
|
||||
};
|
||||
|
||||
// Check if package.json exists and merge if it does
|
||||
const packageJsonPath = path.join(targetDir, 'package.json');
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
log('info', 'package.json already exists, merging content...');
|
||||
try {
|
||||
const existingPackageJson = JSON.parse(
|
||||
fs.readFileSync(packageJsonPath, 'utf8')
|
||||
);
|
||||
|
||||
// Preserve existing fields but add our required ones
|
||||
const mergedPackageJson = {
|
||||
...existingPackageJson,
|
||||
scripts: {
|
||||
...existingPackageJson.scripts,
|
||||
...Object.fromEntries(
|
||||
Object.entries(packageJson.scripts).filter(
|
||||
([key]) =>
|
||||
!existingPackageJson.scripts ||
|
||||
!existingPackageJson.scripts[key]
|
||||
)
|
||||
)
|
||||
},
|
||||
dependencies: {
|
||||
...(existingPackageJson.dependencies || {}),
|
||||
...Object.fromEntries(
|
||||
Object.entries(packageJson.dependencies).filter(
|
||||
([key]) =>
|
||||
!existingPackageJson.dependencies ||
|
||||
!existingPackageJson.dependencies[key]
|
||||
)
|
||||
)
|
||||
}
|
||||
};
|
||||
|
||||
// Ensure type is set if not already present
|
||||
if (!mergedPackageJson.type && packageJson.type) {
|
||||
mergedPackageJson.type = packageJson.type;
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
packageJsonPath,
|
||||
JSON.stringify(mergedPackageJson, null, 2)
|
||||
);
|
||||
log('success', 'Updated package.json with required fields');
|
||||
} catch (error) {
|
||||
log('error', `Failed to merge package.json: ${error.message}`);
|
||||
// Create a backup before potentially modifying
|
||||
const backupPath = `${packageJsonPath}.backup-${Date.now()}`;
|
||||
fs.copyFileSync(packageJsonPath, backupPath);
|
||||
log('info', `Created backup of existing package.json at ${backupPath}`);
|
||||
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
||||
log(
|
||||
'warn',
|
||||
'Created new package.json (backup of original file was created)'
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// If package.json doesn't exist, create it
|
||||
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
|
||||
log('success', 'Created package.json');
|
||||
}
|
||||
|
||||
// Setup MCP configuration for integration with Cursor
|
||||
setupMCPConfiguration(targetDir, packageJson.name);
|
||||
setupMCPConfiguration(targetDir);
|
||||
|
||||
// Copy template files with replacements
|
||||
const replacements = {
|
||||
projectName,
|
||||
projectDescription,
|
||||
projectVersion,
|
||||
authorName,
|
||||
year: new Date().getFullYear()
|
||||
};
|
||||
|
||||
@@ -747,15 +496,6 @@ function createProjectStructure(
|
||||
// Copy .windsurfrules
|
||||
copyTemplateFile('windsurfrules', path.join(targetDir, '.windsurfrules'));
|
||||
|
||||
// Copy scripts/dev.js
|
||||
copyTemplateFile('dev.js', path.join(targetDir, 'scripts', 'dev.js'));
|
||||
|
||||
// Copy scripts/README.md
|
||||
copyTemplateFile(
|
||||
'scripts_README.md',
|
||||
path.join(targetDir, 'scripts', 'README.md')
|
||||
);
|
||||
|
||||
// Copy example_prd.txt
|
||||
copyTemplateFile(
|
||||
'example_prd.txt',
|
||||
@@ -765,43 +505,13 @@ function createProjectStructure(
|
||||
// Create main README.md
|
||||
copyTemplateFile(
|
||||
'README-task-master.md',
|
||||
path.join(targetDir, 'README.md'),
|
||||
path.join(targetDir, 'README-task-master.md'),
|
||||
replacements
|
||||
);
|
||||
|
||||
// Initialize git repository if git is available
|
||||
try {
|
||||
if (!fs.existsSync(path.join(targetDir, '.git'))) {
|
||||
log('info', 'Initializing git repository...');
|
||||
execSync('git init', { stdio: 'ignore' });
|
||||
log('success', 'Git repository initialized');
|
||||
}
|
||||
} catch (error) {
|
||||
log('warn', 'Git not available, skipping repository initialization');
|
||||
}
|
||||
|
||||
// Run npm install automatically
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(chalk.cyan('Installing dependencies...'), {
|
||||
padding: 0.5,
|
||||
margin: 0.5,
|
||||
borderStyle: 'round',
|
||||
borderColor: 'blue'
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
if (!skipInstall) {
|
||||
execSync('npm install', { stdio: 'inherit', cwd: targetDir });
|
||||
log('success', 'Dependencies installed successfully!');
|
||||
} else {
|
||||
log('info', 'Dependencies installation skipped');
|
||||
}
|
||||
} catch (error) {
|
||||
log('error', 'Failed to install dependencies:', error.message);
|
||||
log('error', 'Please run npm install manually');
|
||||
// Add shell aliases if requested
|
||||
if (addAliases) {
|
||||
addShellAliases();
|
||||
}
|
||||
|
||||
// Display success message
|
||||
@@ -823,11 +533,6 @@ function createProjectStructure(
|
||||
);
|
||||
}
|
||||
|
||||
// Add shell aliases if requested
|
||||
if (addAliases) {
|
||||
addShellAliases();
|
||||
}
|
||||
|
||||
// Display next steps in a nice box
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
@@ -896,7 +601,7 @@ function createProjectStructure(
|
||||
}
|
||||
|
||||
// Function to setup MCP configuration for Cursor integration
|
||||
function setupMCPConfiguration(targetDir, projectName) {
|
||||
function setupMCPConfiguration(targetDir) {
|
||||
const mcpDirPath = path.join(targetDir, '.cursor');
|
||||
const mcpJsonPath = path.join(mcpDirPath, 'mcp.json');
|
||||
|
||||
@@ -909,15 +614,15 @@ function setupMCPConfiguration(targetDir, projectName) {
|
||||
const newMCPServer = {
|
||||
'task-master-ai': {
|
||||
command: 'npx',
|
||||
args: ['-y', '--package', 'task-master-ai', 'task-master-mcp'],
|
||||
args: ['-y', 'task-master-mcp'],
|
||||
env: {
|
||||
ANTHROPIC_API_KEY: '%ANTHROPIC_API_KEY%',
|
||||
PERPLEXITY_API_KEY: '%PERPLEXITY_API_KEY%',
|
||||
ANTHROPIC_API_KEY: 'YOUR_ANTHROPIC_API_KEY',
|
||||
PERPLEXITY_API_KEY: 'YOUR_PERPLEXITY_API_KEY',
|
||||
MODEL: 'claude-3-7-sonnet-20250219',
|
||||
PERPLEXITY_MODEL: 'sonar-pro',
|
||||
MAX_TOKENS: 64000,
|
||||
TEMPERATURE: 0.3,
|
||||
DEFAULT_SUBTASKS: 5,
|
||||
MAX_TOKENS: '64000',
|
||||
TEMPERATURE: '0.2',
|
||||
DEFAULT_SUBTASKS: '5',
|
||||
DEFAULT_PRIORITY: 'medium'
|
||||
}
|
||||
}
|
||||
@@ -925,7 +630,10 @@ function setupMCPConfiguration(targetDir, projectName) {
|
||||
|
||||
// Check if mcp.json already exists
|
||||
if (fs.existsSync(mcpJsonPath)) {
|
||||
log('info', 'MCP configuration file already exists, updating...');
|
||||
log(
|
||||
'info',
|
||||
'MCP configuration file already exists, checking for existing task-master-mcp...'
|
||||
);
|
||||
try {
|
||||
// Read existing config
|
||||
const mcpConfig = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf8'));
|
||||
@@ -935,6 +643,23 @@ 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'];
|
||||
|
||||
@@ -164,10 +164,21 @@ async function callClaude(
|
||||
log('info', 'Calling Claude...');
|
||||
|
||||
// Build the system prompt
|
||||
const systemPrompt = `You are an AI assistant helping to break down a Product Requirements Document (PRD) into a set of sequential development tasks.
|
||||
Your goal is to create ${numTasks} well-structured, actionable development tasks based on the PRD provided.
|
||||
const systemPrompt = `You are an AI assistant tasked with breaking down a Product Requirements Document (PRD) into a set of sequential development tasks. Your goal is to create exactly <num_tasks>${numTasks}</num_tasks> well-structured, actionable development tasks based on the PRD provided.
|
||||
|
||||
First, carefully read and analyze the attached PRD
|
||||
|
||||
Before creating the task list, work through the following steps inside <prd_breakdown> tags in your thinking block:
|
||||
|
||||
1. List the key components of the PRD
|
||||
2. Identify the main features and functionalities described
|
||||
3. Note any specific technical requirements or constraints mentioned
|
||||
4. Outline a high-level sequence of tasks that would be needed to implement the PRD
|
||||
|
||||
Consider dependencies, maintainability, and the fact that you don't have access to any existing codebase. Balance between providing detailed task descriptions and maintaining a high-level perspective.
|
||||
|
||||
After your breakdown, create a JSON object containing an array of tasks and a metadata object. Each task should follow this structure:
|
||||
|
||||
Each task should follow this JSON structure:
|
||||
{
|
||||
"id": number,
|
||||
"title": string,
|
||||
@@ -179,39 +190,46 @@ Each task should follow this JSON structure:
|
||||
"testStrategy": string (validation approach)
|
||||
}
|
||||
|
||||
Guidelines:
|
||||
1. Create exactly ${numTasks} tasks, numbered from 1 to ${numTasks}
|
||||
2. Each task should be atomic and focused on a single responsibility
|
||||
3. Order tasks logically - consider dependencies and implementation sequence
|
||||
4. Early tasks should focus on setup, core functionality first, then advanced features
|
||||
5. Include clear validation/testing approach for each task
|
||||
6. Set appropriate dependency IDs (a task can only depend on tasks with lower IDs)
|
||||
7. Assign priority (high/medium/low) based on criticality and dependency order
|
||||
8. Include detailed implementation guidance in the "details" field
|
||||
9. If the PRD contains specific requirements for libraries, database schemas, frameworks, tech stacks, or any other implementation details, STRICTLY ADHERE to these requirements in your task breakdown and do not discard them under any circumstance
|
||||
10. Focus on filling in any gaps left by the PRD or areas that aren't fully specified, while preserving all explicit requirements
|
||||
11. Always aim to provide the most direct path to implementation, avoiding over-engineering or roundabout approaches
|
||||
Guidelines for creating tasks:
|
||||
1. Number tasks from 1 to <num_tasks>${numTasks}</num_tasks>.
|
||||
2. Make each task atomic and focused on a single responsibility.
|
||||
3. Order tasks logically, considering dependencies and implementation sequence.
|
||||
4. Start with setup and core functionality, then move to advanced features.
|
||||
5. Provide a clear validation/testing approach for each task.
|
||||
6. Set appropriate dependency IDs (tasks can only depend on lower-numbered tasks).
|
||||
7. Assign priority based on criticality and dependency order.
|
||||
8. Include detailed implementation guidance in the "details" field.
|
||||
9. Strictly adhere to any specific requirements for libraries, database schemas, frameworks, tech stacks, or other implementation details mentioned in the PRD.
|
||||
10. Fill in gaps left by the PRD while preserving all explicit requirements.
|
||||
11. Provide the most direct path to implementation, avoiding over-engineering.
|
||||
|
||||
The final output should be valid JSON with this structure:
|
||||
|
||||
Expected output format:
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "Setup Project Repository",
|
||||
"description": "...",
|
||||
...
|
||||
"title": "Example Task Title",
|
||||
"description": "Brief description of the task",
|
||||
"status": "pending",
|
||||
"dependencies": [0],
|
||||
"priority": "high",
|
||||
"details": "Detailed implementation guidance",
|
||||
"testStrategy": "Approach for validating this task"
|
||||
},
|
||||
...
|
||||
// ... more tasks ...
|
||||
],
|
||||
"metadata": {
|
||||
"projectName": "PRD Implementation",
|
||||
"totalTasks": ${numTasks},
|
||||
"sourceFile": "${prdPath}",
|
||||
"totalTasks": <num_tasks>${numTasks}</num_tasks>,
|
||||
"sourceFile": "<prd_path>${prdPath}</prd_path>",
|
||||
"generatedAt": "YYYY-MM-DD"
|
||||
}
|
||||
}
|
||||
|
||||
Important: Your response must be valid JSON only, with no additional explanation or comments.`;
|
||||
Remember to provide comprehensive task details that are LLM-friendly, consider dependencies and maintainability carefully, and keep in mind that you don't have the existing codebase as context. Aim for a balance between detailed guidance and high-level planning.
|
||||
|
||||
Your response should be valid JSON only, with no additional explanation or comments. Do not duplicate or rehash any of the work you did in the prd_breakdown section in your final output.`;
|
||||
|
||||
// Use streaming request to handle large responses and show progress
|
||||
return await handleStreamingRequest(
|
||||
|
||||
@@ -88,6 +88,10 @@ function registerCommands(programInstance) {
|
||||
.option('-o, --output <file>', 'Output file path', 'tasks/tasks.json')
|
||||
.option('-n, --num-tasks <number>', 'Number of tasks to generate', '10')
|
||||
.option('-f, --force', 'Skip confirmation when overwriting existing tasks')
|
||||
.option(
|
||||
'--append',
|
||||
'Append new tasks to existing tasks.json instead of overwriting'
|
||||
)
|
||||
.action(async (file, options) => {
|
||||
// Use input option if file argument not provided
|
||||
const inputFile = file || options.input;
|
||||
@@ -95,10 +99,11 @@ function registerCommands(programInstance) {
|
||||
const numTasks = parseInt(options.numTasks, 10);
|
||||
const outputPath = options.output;
|
||||
const force = options.force || false;
|
||||
const append = options.append || false;
|
||||
|
||||
// Helper function to check if tasks.json exists and confirm overwrite
|
||||
async function confirmOverwriteIfNeeded() {
|
||||
if (fs.existsSync(outputPath) && !force) {
|
||||
if (fs.existsSync(outputPath) && !force && !append) {
|
||||
const shouldContinue = await confirmTaskOverwrite(outputPath);
|
||||
if (!shouldContinue) {
|
||||
console.log(chalk.yellow('Operation cancelled by user.'));
|
||||
@@ -117,7 +122,7 @@ function registerCommands(programInstance) {
|
||||
if (!(await confirmOverwriteIfNeeded())) return;
|
||||
|
||||
console.log(chalk.blue(`Generating ${numTasks} tasks...`));
|
||||
await parsePRD(defaultPrdPath, outputPath, numTasks);
|
||||
await parsePRD(defaultPrdPath, outputPath, numTasks, { append });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -138,17 +143,21 @@ function registerCommands(programInstance) {
|
||||
' -i, --input <file> Path to the PRD file (alternative to positional argument)\n' +
|
||||
' -o, --output <file> Output file path (default: "tasks/tasks.json")\n' +
|
||||
' -n, --num-tasks <number> Number of tasks to generate (default: 10)\n' +
|
||||
' -f, --force Skip confirmation when overwriting existing tasks\n\n' +
|
||||
' -f, --force Skip confirmation when overwriting existing tasks\n' +
|
||||
' --append Append new tasks to existing tasks.json instead of overwriting\n\n' +
|
||||
chalk.cyan('Example:') +
|
||||
'\n' +
|
||||
' task-master parse-prd requirements.txt --num-tasks 15\n' +
|
||||
' task-master parse-prd --input=requirements.txt\n' +
|
||||
' task-master parse-prd --force\n\n' +
|
||||
' task-master parse-prd --force\n' +
|
||||
' task-master parse-prd requirements_v2.txt --append\n\n' +
|
||||
chalk.yellow('Note: This command will:') +
|
||||
'\n' +
|
||||
' 1. Look for a PRD file at scripts/prd.txt by default\n' +
|
||||
' 2. Use the file specified by --input or positional argument if provided\n' +
|
||||
' 3. Generate tasks from the PRD and overwrite any existing tasks.json file',
|
||||
' 3. Generate tasks from the PRD and either:\n' +
|
||||
' - Overwrite any existing tasks.json file (default)\n' +
|
||||
' - Append to existing tasks.json if --append is used',
|
||||
{ padding: 1, borderColor: 'blue', borderStyle: 'round' }
|
||||
)
|
||||
);
|
||||
@@ -160,8 +169,11 @@ function registerCommands(programInstance) {
|
||||
|
||||
console.log(chalk.blue(`Parsing PRD file: ${inputFile}`));
|
||||
console.log(chalk.blue(`Generating ${numTasks} tasks...`));
|
||||
if (append) {
|
||||
console.log(chalk.blue('Appending to existing tasks...'));
|
||||
}
|
||||
|
||||
await parsePRD(inputFile, outputPath, numTasks);
|
||||
await parsePRD(inputFile, outputPath, numTasks, { append });
|
||||
});
|
||||
|
||||
// update command
|
||||
@@ -1374,18 +1386,18 @@ function registerCommands(programInstance) {
|
||||
// remove-task command
|
||||
programInstance
|
||||
.command('remove-task')
|
||||
.description('Remove a task or subtask permanently')
|
||||
.description('Remove one or more tasks or subtasks permanently')
|
||||
.option(
|
||||
'-i, --id <id>',
|
||||
'ID of the task or subtask to remove (e.g., "5" or "5.2")'
|
||||
'ID(s) of the task(s) or subtask(s) to remove (e.g., "5" or "5.2" or "5,6,7")'
|
||||
)
|
||||
.option('-f, --file <file>', 'Path to the tasks file', 'tasks/tasks.json')
|
||||
.option('-y, --yes', 'Skip confirmation prompt', false)
|
||||
.action(async (options) => {
|
||||
const tasksPath = options.file;
|
||||
const taskId = options.id;
|
||||
const taskIds = options.id;
|
||||
|
||||
if (!taskId) {
|
||||
if (!taskIds) {
|
||||
console.error(chalk.red('Error: Task ID is required'));
|
||||
console.error(
|
||||
chalk.yellow('Usage: task-master remove-task --id=<taskId>')
|
||||
@@ -1394,7 +1406,7 @@ function registerCommands(programInstance) {
|
||||
}
|
||||
|
||||
try {
|
||||
// Check if the task exists
|
||||
// Check if the tasks file exists and is valid
|
||||
const data = readJSON(tasksPath);
|
||||
if (!data || !data.tasks) {
|
||||
console.error(
|
||||
@@ -1403,75 +1415,89 @@ function registerCommands(programInstance) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (!taskExists(data.tasks, taskId)) {
|
||||
console.error(chalk.red(`Error: Task with ID ${taskId} not found`));
|
||||
// Split task IDs if comma-separated
|
||||
const taskIdArray = taskIds.split(',').map((id) => id.trim());
|
||||
|
||||
// Validate all task IDs exist before proceeding
|
||||
const invalidTasks = taskIdArray.filter(
|
||||
(id) => !taskExists(data.tasks, id)
|
||||
);
|
||||
if (invalidTasks.length > 0) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
`Error: The following tasks were not found: ${invalidTasks.join(', ')}`
|
||||
)
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Load task for display
|
||||
const task = findTaskById(data.tasks, taskId);
|
||||
|
||||
// Skip confirmation if --yes flag is provided
|
||||
if (!options.yes) {
|
||||
// Display task information
|
||||
// Display tasks to be removed
|
||||
console.log();
|
||||
console.log(
|
||||
chalk.red.bold(
|
||||
'⚠️ WARNING: This will permanently delete the following task:'
|
||||
'⚠️ WARNING: This will permanently delete the following tasks:'
|
||||
)
|
||||
);
|
||||
console.log();
|
||||
|
||||
if (typeof taskId === 'string' && taskId.includes('.')) {
|
||||
// It's a subtask
|
||||
const [parentId, subtaskId] = taskId.split('.');
|
||||
console.log(chalk.white.bold(`Subtask ${taskId}: ${task.title}`));
|
||||
console.log(
|
||||
chalk.gray(
|
||||
`Parent Task: ${task.parentTask.id} - ${task.parentTask.title}`
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// It's a main task
|
||||
console.log(chalk.white.bold(`Task ${taskId}: ${task.title}`));
|
||||
for (const taskId of taskIdArray) {
|
||||
const task = findTaskById(data.tasks, taskId);
|
||||
|
||||
// Show if it has subtasks
|
||||
if (task.subtasks && task.subtasks.length > 0) {
|
||||
if (typeof taskId === 'string' && taskId.includes('.')) {
|
||||
// It's a subtask
|
||||
const [parentId, subtaskId] = taskId.split('.');
|
||||
console.log(chalk.white.bold(`Subtask ${taskId}: ${task.title}`));
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`⚠️ This task has ${task.subtasks.length} subtasks that will also be deleted!`
|
||||
chalk.gray(
|
||||
`Parent Task: ${task.parentTask.id} - ${task.parentTask.title}`
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
// It's a main task
|
||||
console.log(chalk.white.bold(`Task ${taskId}: ${task.title}`));
|
||||
|
||||
// Show if other tasks depend on it
|
||||
const dependentTasks = data.tasks.filter(
|
||||
(t) =>
|
||||
t.dependencies && t.dependencies.includes(parseInt(taskId, 10))
|
||||
);
|
||||
// Show if it has subtasks
|
||||
if (task.subtasks && task.subtasks.length > 0) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`⚠️ This task has ${task.subtasks.length} subtasks that will also be deleted!`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (dependentTasks.length > 0) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`⚠️ Warning: ${dependentTasks.length} other tasks depend on this task!`
|
||||
)
|
||||
// Show if other tasks depend on it
|
||||
const dependentTasks = data.tasks.filter(
|
||||
(t) =>
|
||||
t.dependencies &&
|
||||
t.dependencies.includes(parseInt(taskId, 10))
|
||||
);
|
||||
console.log(chalk.yellow('These dependencies will be removed:'));
|
||||
dependentTasks.forEach((t) => {
|
||||
console.log(chalk.yellow(` - Task ${t.id}: ${t.title}`));
|
||||
});
|
||||
|
||||
if (dependentTasks.length > 0) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`⚠️ Warning: ${dependentTasks.length} other tasks depend on this task!`
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
chalk.yellow('These dependencies will be removed:')
|
||||
);
|
||||
dependentTasks.forEach((t) => {
|
||||
console.log(chalk.yellow(` - Task ${t.id}: ${t.title}`));
|
||||
});
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
|
||||
console.log();
|
||||
|
||||
// Prompt for confirmation
|
||||
const { confirm } = await inquirer.prompt([
|
||||
{
|
||||
type: 'confirm',
|
||||
name: 'confirm',
|
||||
message: chalk.red.bold(
|
||||
'Are you sure you want to permanently delete this task?'
|
||||
`Are you sure you want to permanently delete ${taskIdArray.length > 1 ? 'these tasks' : 'this task'}?`
|
||||
),
|
||||
default: false
|
||||
}
|
||||
@@ -1483,31 +1509,72 @@ function registerCommands(programInstance) {
|
||||
}
|
||||
}
|
||||
|
||||
const indicator = startLoadingIndicator('Removing task...');
|
||||
const indicator = startLoadingIndicator('Removing tasks...');
|
||||
|
||||
// Remove the task
|
||||
const result = await removeTask(tasksPath, taskId);
|
||||
// Remove each task
|
||||
const results = [];
|
||||
for (const taskId of taskIdArray) {
|
||||
try {
|
||||
const result = await removeTask(tasksPath, taskId);
|
||||
results.push({ taskId, success: true, ...result });
|
||||
} catch (error) {
|
||||
results.push({ taskId, success: false, error: error.message });
|
||||
}
|
||||
}
|
||||
|
||||
stopLoadingIndicator(indicator);
|
||||
|
||||
// Display success message with appropriate color based on task or subtask
|
||||
if (typeof taskId === 'string' && taskId.includes('.')) {
|
||||
// It was a subtask
|
||||
// Display results
|
||||
const successfulRemovals = results.filter((r) => r.success);
|
||||
const failedRemovals = results.filter((r) => !r.success);
|
||||
|
||||
if (successfulRemovals.length > 0) {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`Subtask ${taskId} has been successfully removed`),
|
||||
{ padding: 1, borderColor: 'green', borderStyle: 'round' }
|
||||
chalk.green(
|
||||
`Successfully removed ${successfulRemovals.length} task${successfulRemovals.length > 1 ? 's' : ''}`
|
||||
) +
|
||||
'\n\n' +
|
||||
successfulRemovals
|
||||
.map((r) =>
|
||||
chalk.white(
|
||||
`✓ ${r.taskId.includes('.') ? 'Subtask' : 'Task'} ${r.taskId}`
|
||||
)
|
||||
)
|
||||
.join('\n'),
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// It was a main task
|
||||
}
|
||||
|
||||
if (failedRemovals.length > 0) {
|
||||
console.log(
|
||||
boxen(chalk.green(`Task ${taskId} has been successfully removed`), {
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round'
|
||||
})
|
||||
boxen(
|
||||
chalk.red(
|
||||
`Failed to remove ${failedRemovals.length} task${failedRemovals.length > 1 ? 's' : ''}`
|
||||
) +
|
||||
'\n\n' +
|
||||
failedRemovals
|
||||
.map((r) => chalk.white(`✗ ${r.taskId}: ${r.error}`))
|
||||
.join('\n'),
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'red',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Exit with error if any removals failed
|
||||
if (successfulRemovals.length === 0) {
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(
|
||||
|
||||
@@ -14,7 +14,8 @@ import {
|
||||
writeJSON,
|
||||
taskExists,
|
||||
formatTaskId,
|
||||
findCycles
|
||||
findCycles,
|
||||
isSilentMode
|
||||
} from './utils.js';
|
||||
|
||||
import { displayBanner } from './ui.js';
|
||||
@@ -184,18 +185,20 @@ async function addDependency(tasksPath, taskId, dependencyId) {
|
||||
);
|
||||
|
||||
// Display a more visually appealing success message
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`Successfully added dependency:\n\n`) +
|
||||
`Task ${chalk.bold(formattedTaskId)} now depends on ${chalk.bold(formattedDependencyId)}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`Successfully added dependency:\n\n`) +
|
||||
`Task ${chalk.bold(formattedTaskId)} now depends on ${chalk.bold(formattedDependencyId)}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Generate updated task files
|
||||
await generateTaskFiles(tasksPath, 'tasks');
|
||||
@@ -320,19 +323,21 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
|
||||
`Removed dependency: Task ${formattedTaskId} no longer depends on ${formattedDependencyId}`
|
||||
);
|
||||
|
||||
// Display a more visually appealing success message
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`Successfully removed dependency:\n\n`) +
|
||||
`Task ${chalk.bold(formattedTaskId)} no longer depends on ${chalk.bold(formattedDependencyId)}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
if (!isSilentMode()) {
|
||||
// Display a more visually appealing success message
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`Successfully removed dependency:\n\n`) +
|
||||
`Task ${chalk.bold(formattedTaskId)} no longer depends on ${chalk.bold(formattedDependencyId)}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Regenerate task files
|
||||
await generateTaskFiles(tasksPath, 'tasks');
|
||||
@@ -553,8 +558,11 @@ function cleanupSubtaskDependencies(tasksData) {
|
||||
* Validate dependencies in task files
|
||||
* @param {string} tasksPath - Path to tasks.json
|
||||
*/
|
||||
async function validateDependenciesCommand(tasksPath) {
|
||||
displayBanner();
|
||||
async function validateDependenciesCommand(tasksPath, options = {}) {
|
||||
// Only display banner if not in silent mode
|
||||
if (!isSilentMode()) {
|
||||
displayBanner();
|
||||
}
|
||||
|
||||
log('info', 'Checking for invalid dependencies in task files...');
|
||||
|
||||
@@ -659,31 +667,33 @@ async function validateDependenciesCommand(tasksPath) {
|
||||
if (changesDetected) {
|
||||
log('success', 'Invalid dependencies were removed from tasks.json');
|
||||
|
||||
// Show detailed stats in a nice box
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`Dependency Validation Results:\n\n`) +
|
||||
`${chalk.cyan('Tasks checked:')} ${taskCount}\n` +
|
||||
`${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` +
|
||||
`${chalk.cyan('Non-existent dependencies removed:')} ${stats.nonExistentDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Self-dependencies removed:')} ${stats.selfDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Tasks fixed:')} ${stats.tasksFixed}\n` +
|
||||
`${chalk.cyan('Subtasks fixed:')} ${stats.subtasksFixed}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
// Show detailed stats in a nice box - only if not in silent mode
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`Dependency Validation Results:\n\n`) +
|
||||
`${chalk.cyan('Tasks checked:')} ${taskCount}\n` +
|
||||
`${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` +
|
||||
`${chalk.cyan('Non-existent dependencies removed:')} ${stats.nonExistentDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Self-dependencies removed:')} ${stats.selfDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Tasks fixed:')} ${stats.tasksFixed}\n` +
|
||||
`${chalk.cyan('Subtasks fixed:')} ${stats.subtasksFixed}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// Show all warnings in a collapsible list if there are many
|
||||
if (warnings.length > 0) {
|
||||
console.log(chalk.yellow('\nDetailed fixes:'));
|
||||
warnings.forEach((warning) => {
|
||||
console.log(` ${warning}`);
|
||||
});
|
||||
// Show all warnings in a collapsible list if there are many
|
||||
if (warnings.length > 0) {
|
||||
console.log(chalk.yellow('\nDetailed fixes:'));
|
||||
warnings.forEach((warning) => {
|
||||
console.log(` ${warning}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Regenerate task files to reflect the changes
|
||||
@@ -695,21 +705,23 @@ async function validateDependenciesCommand(tasksPath) {
|
||||
'No invalid dependencies found - all dependencies are valid'
|
||||
);
|
||||
|
||||
// Show validation summary
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`All Dependencies Are Valid\n\n`) +
|
||||
`${chalk.cyan('Tasks checked:')} ${taskCount}\n` +
|
||||
`${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` +
|
||||
`${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
// Show validation summary - only if not in silent mode
|
||||
if (!isSilentMode()) {
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`All Dependencies Are Valid\n\n`) +
|
||||
`${chalk.cyan('Tasks checked:')} ${taskCount}\n` +
|
||||
`${chalk.cyan('Subtasks checked:')} ${subtaskCount}\n` +
|
||||
`${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log('error', 'Error validating dependencies:', error);
|
||||
@@ -747,9 +759,13 @@ 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) {
|
||||
displayBanner();
|
||||
async function fixDependenciesCommand(tasksPath, options = {}) {
|
||||
// Only display banner if not in silent mode
|
||||
if (!isSilentMode()) {
|
||||
displayBanner();
|
||||
}
|
||||
|
||||
log('info', 'Checking for and fixing invalid dependencies in tasks.json...');
|
||||
|
||||
@@ -1086,42 +1102,47 @@ async function fixDependenciesCommand(tasksPath) {
|
||||
stats.duplicateDependenciesRemoved +
|
||||
stats.circularDependenciesFixed;
|
||||
|
||||
if (totalFixedAll > 0) {
|
||||
log('success', `Fixed ${totalFixedAll} dependency issues in total!`);
|
||||
if (!isSilentMode()) {
|
||||
if (totalFixedAll > 0) {
|
||||
log('success', `Fixed ${totalFixedAll} dependency issues in total!`);
|
||||
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`Dependency Fixes Summary:\n\n`) +
|
||||
`${chalk.cyan('Invalid dependencies removed:')} ${stats.nonExistentDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Self-dependencies removed:')} ${stats.selfDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Duplicate dependencies removed:')} ${stats.duplicateDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Circular dependencies fixed:')} ${stats.circularDependenciesFixed}\n\n` +
|
||||
`${chalk.cyan('Tasks fixed:')} ${stats.tasksFixed}\n` +
|
||||
`${chalk.cyan('Subtasks fixed:')} ${stats.subtasksFixed}\n`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
log('success', 'No dependency issues found - all dependencies are valid');
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`Dependency Fixes Summary:\n\n`) +
|
||||
`${chalk.cyan('Invalid dependencies removed:')} ${stats.nonExistentDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Self-dependencies removed:')} ${stats.selfDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Duplicate dependencies removed:')} ${stats.duplicateDependenciesRemoved}\n` +
|
||||
`${chalk.cyan('Circular dependencies fixed:')} ${stats.circularDependenciesFixed}\n\n` +
|
||||
`${chalk.cyan('Tasks fixed:')} ${stats.tasksFixed}\n` +
|
||||
`${chalk.cyan('Subtasks fixed:')} ${stats.subtasksFixed}\n`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
} else {
|
||||
log(
|
||||
'success',
|
||||
'No dependency issues found - all dependencies are valid'
|
||||
);
|
||||
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`All Dependencies Are Valid\n\n`) +
|
||||
`${chalk.cyan('Tasks checked:')} ${data.tasks.length}\n` +
|
||||
`${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(`All Dependencies Are Valid\n\n`) +
|
||||
`${chalk.cyan('Tasks checked:')} ${data.tasks.length}\n` +
|
||||
`${chalk.cyan('Total dependencies verified:')} ${countAllDependencies(data.tasks)}`,
|
||||
{
|
||||
padding: 1,
|
||||
borderColor: 'green',
|
||||
borderStyle: 'round',
|
||||
margin: { top: 1, bottom: 1 }
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
log('error', 'Error in fix-dependencies command:', error);
|
||||
|
||||
@@ -106,7 +106,7 @@ async function parsePRD(
|
||||
aiClient = null,
|
||||
modelConfig = null
|
||||
) {
|
||||
const { reportProgress, mcpLog, session } = options;
|
||||
const { reportProgress, mcpLog, session, append } = options;
|
||||
|
||||
// Determine output format based on mcpLog presence (simplification)
|
||||
const outputFormat = mcpLog ? 'json' : 'text';
|
||||
@@ -127,8 +127,30 @@ async function parsePRD(
|
||||
// Read the PRD content
|
||||
const prdContent = fs.readFileSync(prdPath, 'utf8');
|
||||
|
||||
// If appending and tasks.json exists, read existing tasks first
|
||||
let existingTasks = { tasks: [] };
|
||||
let lastTaskId = 0;
|
||||
if (append && fs.existsSync(tasksPath)) {
|
||||
try {
|
||||
existingTasks = readJSON(tasksPath);
|
||||
if (existingTasks.tasks?.length) {
|
||||
// Find the highest task ID
|
||||
lastTaskId = existingTasks.tasks.reduce((maxId, task) => {
|
||||
const mainId = parseInt(task.id.toString().split('.')[0], 10) || 0;
|
||||
return Math.max(maxId, mainId);
|
||||
}, 0);
|
||||
}
|
||||
} catch (error) {
|
||||
report(
|
||||
`Warning: Could not read existing tasks file: ${error.message}`,
|
||||
'warn'
|
||||
);
|
||||
existingTasks = { tasks: [] };
|
||||
}
|
||||
}
|
||||
|
||||
// Call Claude to generate tasks, passing the provided AI client if available
|
||||
const tasksData = await callClaude(
|
||||
const newTasksData = await callClaude(
|
||||
prdContent,
|
||||
prdPath,
|
||||
numTasks,
|
||||
@@ -138,15 +160,33 @@ async function parsePRD(
|
||||
modelConfig
|
||||
);
|
||||
|
||||
// Update task IDs if appending
|
||||
if (append && lastTaskId > 0) {
|
||||
report(`Updating task IDs to continue from ID ${lastTaskId}`, 'info');
|
||||
newTasksData.tasks.forEach((task, index) => {
|
||||
task.id = lastTaskId + index + 1;
|
||||
});
|
||||
}
|
||||
|
||||
// Merge tasks if appending
|
||||
const tasksData = append
|
||||
? {
|
||||
...existingTasks,
|
||||
tasks: [...existingTasks.tasks, ...newTasksData.tasks]
|
||||
}
|
||||
: newTasksData;
|
||||
|
||||
// Create the directory if it doesn't exist
|
||||
const tasksDir = path.dirname(tasksPath);
|
||||
if (!fs.existsSync(tasksDir)) {
|
||||
fs.mkdirSync(tasksDir, { recursive: true });
|
||||
}
|
||||
|
||||
// Write the tasks to the file
|
||||
writeJSON(tasksPath, tasksData);
|
||||
const actionVerb = append ? 'appended' : 'generated';
|
||||
report(
|
||||
`Successfully generated ${tasksData.tasks.length} tasks from PRD`,
|
||||
`Successfully ${actionVerb} ${newTasksData.tasks.length} tasks from PRD`,
|
||||
'success'
|
||||
);
|
||||
report(`Tasks saved to: ${tasksPath}`, 'info');
|
||||
@@ -166,7 +206,7 @@ async function parsePRD(
|
||||
console.log(
|
||||
boxen(
|
||||
chalk.green(
|
||||
`Successfully generated ${tasksData.tasks.length} tasks from PRD`
|
||||
`Successfully ${actionVerb} ${newTasksData.tasks.length} tasks from PRD`
|
||||
),
|
||||
{ padding: 1, borderColor: 'green', borderStyle: 'round' }
|
||||
)
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
async function updateSubtaskById(tasksPath, subtaskId, prompt, useResearch = false) {
|
||||
let loadingIndicator = null;
|
||||
try {
|
||||
log('info', `Updating subtask ${subtaskId} with prompt: "${prompt}"`);
|
||||
|
||||
// Validate subtask ID format
|
||||
if (!subtaskId || typeof subtaskId !== 'string' || !subtaskId.includes('.')) {
|
||||
throw new Error(`Invalid subtask ID format: ${subtaskId}. Subtask ID must be in format "parentId.subtaskId"`);
|
||||
}
|
||||
|
||||
// Validate prompt
|
||||
if (!prompt || typeof prompt !== 'string' || prompt.trim() === '') {
|
||||
throw new Error('Prompt cannot be empty. Please provide context for the subtask update.');
|
||||
}
|
||||
|
||||
// Prepare for fallback handling
|
||||
let claudeOverloaded = false;
|
||||
|
||||
// Validate tasks file exists
|
||||
if (!fs.existsSync(tasksPath)) {
|
||||
throw new Error(`Tasks file not found at path: ${tasksPath}`);
|
||||
}
|
||||
|
||||
// Read the tasks file
|
||||
const data = readJSON(tasksPath);
|
||||
// ... rest of the function
|
||||
} catch (error) {
|
||||
// Handle errors
|
||||
console.error(`Error updating subtask: ${error.message}`);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
@@ -1,39 +1,73 @@
|
||||
# Task ID: 60
|
||||
# Title: Implement isValidTaskId Utility Function
|
||||
# Title: Implement Mentor System with Round-Table Discussion Feature
|
||||
# Status: pending
|
||||
# Dependencies: None
|
||||
# Priority: medium
|
||||
# Description: Create a utility function that validates whether a given string conforms to the project's task ID format specification.
|
||||
# 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:
|
||||
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:
|
||||
Implement a comprehensive mentor system with the following features:
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
The function should be placed in the utilities directory and properly exported. Include JSDoc comments for clear documentation of parameters and return values.
|
||||
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.
|
||||
|
||||
# Test Strategy:
|
||||
Testing should include the following cases:
|
||||
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
|
||||
|
||||
1. Valid task IDs:
|
||||
- 'TASK-1'
|
||||
- 'TASK-123'
|
||||
- 'TASK-9999'
|
||||
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
|
||||
|
||||
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)
|
||||
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
|
||||
|
||||
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#').
|
||||
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
|
||||
|
||||
@@ -2729,10 +2729,10 @@
|
||||
},
|
||||
{
|
||||
"id": 60,
|
||||
"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#').",
|
||||
"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",
|
||||
"status": "pending",
|
||||
"dependencies": [],
|
||||
"priority": "medium"
|
||||
|
||||
2636
tasks/tasks.json.bak
2636
tasks/tasks.json.bak
File diff suppressed because one or more lines are too long
@@ -1,14 +1,14 @@
|
||||
{
|
||||
"tasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"dependencies": [],
|
||||
"subtasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"dependencies": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
"tasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"dependencies": [],
|
||||
"subtasks": [
|
||||
{
|
||||
"id": 1,
|
||||
"dependencies": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -131,7 +131,7 @@ jest.mock('../../../scripts/modules/utils.js', () => ({
|
||||
enableSilentMode: mockEnableSilentMode,
|
||||
disableSilentMode: mockDisableSilentMode,
|
||||
CONFIG: {
|
||||
model: 'claude-3-sonnet-20240229',
|
||||
model: 'claude-3-7-sonnet-20250219',
|
||||
maxTokens: 64000,
|
||||
temperature: 0.2,
|
||||
defaultSubtasks: 5
|
||||
|
||||
@@ -7,10 +7,10 @@
|
||||
// Mock environment variables
|
||||
process.env.MODEL = 'sonar-pro';
|
||||
process.env.MAX_TOKENS = '64000';
|
||||
process.env.TEMPERATURE = '0.4';
|
||||
process.env.TEMPERATURE = '0.2';
|
||||
process.env.DEBUG = 'false';
|
||||
process.env.LOG_LEVEL = 'error'; // Set to error to reduce noise in tests
|
||||
process.env.DEFAULT_SUBTASKS = '3';
|
||||
process.env.DEFAULT_SUBTASKS = '5';
|
||||
process.env.DEFAULT_PRIORITY = 'medium';
|
||||
process.env.PROJECT_NAME = 'Test Project';
|
||||
process.env.PROJECT_VERSION = '1.0.0';
|
||||
|
||||
@@ -199,16 +199,35 @@ describe('Commands Module', () => {
|
||||
// Use input option if file argument not provided
|
||||
const inputFile = file || options.input;
|
||||
const defaultPrdPath = 'scripts/prd.txt';
|
||||
const append = options.append || false;
|
||||
const force = options.force || false;
|
||||
const outputPath = options.output || 'tasks/tasks.json';
|
||||
|
||||
// Mock confirmOverwriteIfNeeded function to test overwrite behavior
|
||||
const mockConfirmOverwrite = jest.fn().mockResolvedValue(true);
|
||||
|
||||
// Helper function to check if tasks.json exists and confirm overwrite
|
||||
async function confirmOverwriteIfNeeded() {
|
||||
if (fs.existsSync(outputPath) && !force && !append) {
|
||||
return mockConfirmOverwrite();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// If no input file specified, check for default PRD location
|
||||
if (!inputFile) {
|
||||
if (fs.existsSync(defaultPrdPath)) {
|
||||
console.log(chalk.blue(`Using default PRD file: ${defaultPrdPath}`));
|
||||
const numTasks = parseInt(options.numTasks, 10);
|
||||
const outputPath = options.output;
|
||||
|
||||
// Check if we need to confirm overwrite
|
||||
if (!(await confirmOverwriteIfNeeded())) return;
|
||||
|
||||
console.log(chalk.blue(`Generating ${numTasks} tasks...`));
|
||||
await mockParsePRD(defaultPrdPath, outputPath, numTasks);
|
||||
if (append) {
|
||||
console.log(chalk.blue('Appending to existing tasks...'));
|
||||
}
|
||||
await mockParsePRD(defaultPrdPath, outputPath, numTasks, { append });
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -221,12 +240,20 @@ describe('Commands Module', () => {
|
||||
}
|
||||
|
||||
const numTasks = parseInt(options.numTasks, 10);
|
||||
const outputPath = options.output;
|
||||
|
||||
// Check if we need to confirm overwrite
|
||||
if (!(await confirmOverwriteIfNeeded())) return;
|
||||
|
||||
console.log(chalk.blue(`Parsing PRD file: ${inputFile}`));
|
||||
console.log(chalk.blue(`Generating ${numTasks} tasks...`));
|
||||
if (append) {
|
||||
console.log(chalk.blue('Appending to existing tasks...'));
|
||||
}
|
||||
|
||||
await mockParsePRD(inputFile, outputPath, numTasks);
|
||||
await mockParsePRD(inputFile, outputPath, numTasks, { append });
|
||||
|
||||
// Return mock for testing
|
||||
return { mockConfirmOverwrite };
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
@@ -252,7 +279,8 @@ describe('Commands Module', () => {
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(
|
||||
'scripts/prd.txt',
|
||||
'tasks/tasks.json',
|
||||
10 // Default value from command definition
|
||||
10, // Default value from command definition
|
||||
{ append: false }
|
||||
);
|
||||
});
|
||||
|
||||
@@ -290,7 +318,8 @@ describe('Commands Module', () => {
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(
|
||||
testFile,
|
||||
'tasks/tasks.json',
|
||||
10
|
||||
10,
|
||||
{ append: false }
|
||||
);
|
||||
expect(mockExistsSync).not.toHaveBeenCalledWith('scripts/prd.txt');
|
||||
});
|
||||
@@ -313,7 +342,8 @@ describe('Commands Module', () => {
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(
|
||||
testFile,
|
||||
'tasks/tasks.json',
|
||||
10
|
||||
10,
|
||||
{ append: false }
|
||||
);
|
||||
expect(mockExistsSync).not.toHaveBeenCalledWith('scripts/prd.txt');
|
||||
});
|
||||
@@ -331,7 +361,126 @@ describe('Commands Module', () => {
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(testFile, outputFile, numTasks);
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(
|
||||
testFile,
|
||||
outputFile,
|
||||
numTasks,
|
||||
{ append: false }
|
||||
);
|
||||
});
|
||||
|
||||
test('should pass append flag to parsePRD when provided', async () => {
|
||||
// Arrange
|
||||
const testFile = 'test/prd.txt';
|
||||
|
||||
// Act - call the handler directly with append flag
|
||||
await parsePrdAction(testFile, {
|
||||
numTasks: '10',
|
||||
output: 'tasks/tasks.json',
|
||||
append: true
|
||||
});
|
||||
|
||||
// Assert
|
||||
expect(mockConsoleLog).toHaveBeenCalledWith(
|
||||
expect.stringContaining('Appending to existing tasks')
|
||||
);
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(
|
||||
testFile,
|
||||
'tasks/tasks.json',
|
||||
10,
|
||||
{ append: true }
|
||||
);
|
||||
});
|
||||
|
||||
test('should bypass confirmation when append flag is true and tasks.json exists', async () => {
|
||||
// Arrange
|
||||
const testFile = 'test/prd.txt';
|
||||
const outputFile = 'tasks/tasks.json';
|
||||
|
||||
// Mock that tasks.json exists
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === outputFile) return true;
|
||||
if (path === testFile) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// Act - call the handler with append flag
|
||||
const { mockConfirmOverwrite } =
|
||||
(await parsePrdAction(testFile, {
|
||||
numTasks: '10',
|
||||
output: outputFile,
|
||||
append: true
|
||||
})) || {};
|
||||
|
||||
// Assert - confirm overwrite should not be called with append flag
|
||||
expect(mockConfirmOverwrite).not.toHaveBeenCalled();
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(testFile, outputFile, 10, {
|
||||
append: true
|
||||
});
|
||||
|
||||
// Reset mock implementation
|
||||
mockExistsSync.mockReset();
|
||||
});
|
||||
|
||||
test('should prompt for confirmation when append flag is false and tasks.json exists', async () => {
|
||||
// Arrange
|
||||
const testFile = 'test/prd.txt';
|
||||
const outputFile = 'tasks/tasks.json';
|
||||
|
||||
// Mock that tasks.json exists
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === outputFile) return true;
|
||||
if (path === testFile) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// Act - call the handler without append flag
|
||||
const { mockConfirmOverwrite } =
|
||||
(await parsePrdAction(testFile, {
|
||||
numTasks: '10',
|
||||
output: outputFile
|
||||
// append: false (default)
|
||||
})) || {};
|
||||
|
||||
// Assert - confirm overwrite should be called without append flag
|
||||
expect(mockConfirmOverwrite).toHaveBeenCalled();
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(testFile, outputFile, 10, {
|
||||
append: false
|
||||
});
|
||||
|
||||
// Reset mock implementation
|
||||
mockExistsSync.mockReset();
|
||||
});
|
||||
|
||||
test('should bypass confirmation when force flag is true, regardless of append flag', async () => {
|
||||
// Arrange
|
||||
const testFile = 'test/prd.txt';
|
||||
const outputFile = 'tasks/tasks.json';
|
||||
|
||||
// Mock that tasks.json exists
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === outputFile) return true;
|
||||
if (path === testFile) return true;
|
||||
return false;
|
||||
});
|
||||
|
||||
// Act - call the handler with force flag
|
||||
const { mockConfirmOverwrite } =
|
||||
(await parsePrdAction(testFile, {
|
||||
numTasks: '10',
|
||||
output: outputFile,
|
||||
force: true,
|
||||
append: false
|
||||
})) || {};
|
||||
|
||||
// Assert - confirm overwrite should not be called with force flag
|
||||
expect(mockConfirmOverwrite).not.toHaveBeenCalled();
|
||||
expect(mockParsePRD).toHaveBeenCalledWith(testFile, outputFile, 10, {
|
||||
append: false
|
||||
});
|
||||
|
||||
// Reset mock implementation
|
||||
mockExistsSync.mockReset();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -134,33 +134,59 @@ jest.mock('../../scripts/modules/task-manager.js', () => {
|
||||
});
|
||||
|
||||
// Create a simplified version of parsePRD for testing
|
||||
const testParsePRD = async (prdPath, outputPath, numTasks) => {
|
||||
const testParsePRD = async (prdPath, outputPath, numTasks, options = {}) => {
|
||||
const { append = false } = options;
|
||||
try {
|
||||
// Handle existing tasks when append flag is true
|
||||
let existingTasks = { tasks: [] };
|
||||
let lastTaskId = 0;
|
||||
|
||||
// Check if the output file already exists
|
||||
if (mockExistsSync(outputPath)) {
|
||||
const confirmOverwrite = await mockPromptYesNo(
|
||||
`Warning: ${outputPath} already exists. Overwrite?`,
|
||||
false
|
||||
);
|
||||
if (append) {
|
||||
// Simulate reading existing tasks.json
|
||||
existingTasks = {
|
||||
tasks: [
|
||||
{ id: 1, title: 'Existing Task 1', status: 'done' },
|
||||
{ id: 2, title: 'Existing Task 2', status: 'pending' }
|
||||
]
|
||||
};
|
||||
lastTaskId = 2; // Highest existing ID
|
||||
} else {
|
||||
const confirmOverwrite = await mockPromptYesNo(
|
||||
`Warning: ${outputPath} already exists. Overwrite?`,
|
||||
false
|
||||
);
|
||||
|
||||
if (!confirmOverwrite) {
|
||||
console.log(`Operation cancelled. ${outputPath} was not modified.`);
|
||||
return null;
|
||||
if (!confirmOverwrite) {
|
||||
console.log(`Operation cancelled. ${outputPath} was not modified.`);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const prdContent = mockReadFileSync(prdPath, 'utf8');
|
||||
const tasks = await mockCallClaude(prdContent, prdPath, numTasks);
|
||||
// Modify mockCallClaude to accept lastTaskId parameter
|
||||
let newTasks = await mockCallClaude(prdContent, prdPath, numTasks);
|
||||
|
||||
// Merge tasks if appending
|
||||
const tasksData = append
|
||||
? {
|
||||
...existingTasks,
|
||||
tasks: [...existingTasks.tasks, ...newTasks.tasks]
|
||||
}
|
||||
: newTasks;
|
||||
|
||||
const dir = mockDirname(outputPath);
|
||||
|
||||
if (!mockExistsSync(dir)) {
|
||||
mockMkdirSync(dir, { recursive: true });
|
||||
}
|
||||
|
||||
mockWriteJSON(outputPath, tasks);
|
||||
mockWriteJSON(outputPath, tasksData);
|
||||
await mockGenerateTaskFiles(outputPath, dir);
|
||||
|
||||
return tasks;
|
||||
return tasksData;
|
||||
} catch (error) {
|
||||
console.error(`Error parsing PRD: ${error.message}`);
|
||||
process.exit(1);
|
||||
@@ -628,6 +654,27 @@ describe('Task Manager Module', () => {
|
||||
// Mock the sample PRD content
|
||||
const samplePRDContent = '# Sample PRD for Testing';
|
||||
|
||||
// Mock existing tasks for append test
|
||||
const existingTasks = {
|
||||
tasks: [
|
||||
{ id: 1, title: 'Existing Task 1', status: 'done' },
|
||||
{ id: 2, title: 'Existing Task 2', status: 'pending' }
|
||||
]
|
||||
};
|
||||
|
||||
// Mock new tasks with continuing IDs for append test
|
||||
const newTasksWithContinuedIds = {
|
||||
tasks: [
|
||||
{ id: 3, title: 'New Task 3' },
|
||||
{ id: 4, title: 'New Task 4' }
|
||||
]
|
||||
};
|
||||
|
||||
// Mock merged tasks for append test
|
||||
const mergedTasks = {
|
||||
tasks: [...existingTasks.tasks, ...newTasksWithContinuedIds.tasks]
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
// Reset all mocks
|
||||
jest.clearAllMocks();
|
||||
@@ -811,6 +858,66 @@ describe('Task Manager Module', () => {
|
||||
sampleClaudeResponse
|
||||
);
|
||||
});
|
||||
|
||||
test('should append new tasks when append option is true', async () => {
|
||||
// Setup mocks to simulate tasks.json already exists
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === 'tasks/tasks.json') return true; // Output file exists
|
||||
if (path === 'tasks') return true; // Directory exists
|
||||
return false;
|
||||
});
|
||||
|
||||
// Mock for reading existing tasks
|
||||
mockReadJSON.mockReturnValue(existingTasks);
|
||||
// mockReadJSON = jest.fn().mockReturnValue(existingTasks);
|
||||
|
||||
// Mock callClaude to return new tasks with continuing IDs
|
||||
mockCallClaude.mockResolvedValueOnce(newTasksWithContinuedIds);
|
||||
|
||||
// Call the function with append option
|
||||
const result = await testParsePRD(
|
||||
'path/to/prd.txt',
|
||||
'tasks/tasks.json',
|
||||
2,
|
||||
{ append: true }
|
||||
);
|
||||
|
||||
// Verify prompt was NOT called (no confirmation needed for append)
|
||||
expect(mockPromptYesNo).not.toHaveBeenCalled();
|
||||
|
||||
// Verify the file was written with merged tasks
|
||||
expect(mockWriteJSON).toHaveBeenCalledWith(
|
||||
'tasks/tasks.json',
|
||||
expect.objectContaining({
|
||||
tasks: expect.arrayContaining([
|
||||
expect.objectContaining({ id: 1 }),
|
||||
expect.objectContaining({ id: 2 }),
|
||||
expect.objectContaining({ id: 3 }),
|
||||
expect.objectContaining({ id: 4 })
|
||||
])
|
||||
})
|
||||
);
|
||||
|
||||
// Verify the result contains merged tasks
|
||||
expect(result.tasks.length).toBe(4);
|
||||
});
|
||||
|
||||
test('should skip prompt and not overwrite when append is true', async () => {
|
||||
// Setup mocks to simulate tasks.json already exists
|
||||
mockExistsSync.mockImplementation((path) => {
|
||||
if (path === 'tasks/tasks.json') return true; // Output file exists
|
||||
if (path === 'tasks') return true; // Directory exists
|
||||
return false;
|
||||
});
|
||||
|
||||
// Call the function with append option
|
||||
await testParsePRD('path/to/prd.txt', 'tasks/tasks.json', 3, {
|
||||
append: true
|
||||
});
|
||||
|
||||
// Verify prompt was NOT called with append flag
|
||||
expect(mockPromptYesNo).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe.skip('updateTasks function', () => {
|
||||
|
||||
Reference in New Issue
Block a user