Compare commits

..

3 Commits

Author SHA1 Message Date
Ralph Khreish
c9aa9faf59 fix: projectRoot duplicate .taskmaster directory 2025-06-03 15:00:46 +02:00
Ralph Khreish
9b4168bb4e Fix: MCP log errors (#648) 2025-06-03 01:09:29 +02:00
Ralph Khreish
ad612763ff fix: bedrock set model and other fixes (#641) 2025-06-02 14:44:35 +02:00
58 changed files with 12320 additions and 12149 deletions

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Fix bedrock issues

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Fix MCP tool calls logging errors

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': minor
---
Add AWS bedrock support

View File

@@ -0,0 +1,13 @@
---
'task-master-ai': minor
---
# Add Google Vertex AI Provider Integration
- Implemented `VertexAIProvider` class extending BaseAIProvider
- Added authentication and configuration handling for Vertex AI
- Updated configuration manager with Vertex-specific getters
- Modified AI services unified system to integrate the provider
- Added documentation for Vertex AI setup and configuration
- Updated environment variable examples for Vertex AI support
- Implemented specialized error handling for Vertex-specific issues

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': minor
---
Add support for Azure

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Update rules for new directory structure

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": minor
---
Increased minimum required node version to > 18 (was > 14)

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Fix bug in expand_all mcp tool

View File

@@ -0,0 +1,7 @@
---
"task-master-ai": patch
---
Fix double .taskmaster directory paths in file resolution utilities
- Closes #636

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': minor
---
Renamed baseUrl to baseURL

18
.changeset/pre.json Normal file
View File

@@ -0,0 +1,18 @@
{
"mode": "exit",
"tag": "rc",
"initialVersions": {
"task-master-ai": "0.15.0"
},
"changesets": [
"hungry-geese-work",
"itchy-taxes-sip",
"lemon-apes-sort",
"new-colts-flow",
"plain-bottles-stand",
"shaggy-rice-exist",
"sharp-flies-call",
"spotty-buttons-walk",
"tidy-seals-rule"
]
}

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': patch
---
Fix max_tokens error when trying to use claude-sonnet-4 and claude-opus-4

View File

@@ -0,0 +1,7 @@
---
'task-master-ai': minor
---
Add TASK_MASTER_PROJECT_ROOT env variable supported in mcp.json and .env for project root resolution
- Some users were having issues where the MCP wasn't able to detect the location of their project root, you can now set the `TASK_MASTER_PROJECT_ROOT` environment variable to the root of your project.

View File

@@ -0,0 +1,31 @@
---
"task-master-ai": minor
---
Consolidate Task Master files into unified .taskmaster directory structure
This release introduces a new consolidated directory structure that organizes all Task Master files under a single `.taskmaster/` directory for better project organization and cleaner workspace management.
**New Directory Structure:**
- `.taskmaster/tasks/` - Task files (previously `tasks/`)
- `.taskmaster/docs/` - Documentation including PRD files (previously `scripts/`)
- `.taskmaster/reports/` - Complexity analysis reports (previously `scripts/`)
- `.taskmaster/templates/` - Template files like example PRD
- `.taskmaster/config.json` - Configuration (previously `.taskmasterconfig`)
**Migration & Backward Compatibility:**
- Existing projects continue to work with legacy file locations
- New projects use the consolidated structure automatically
- Run `task-master migrate` to move existing projects to the new structure
- All CLI commands and MCP tools automatically detect and use appropriate file locations
**Benefits:**
- Cleaner project root with Task Master files organized in one location
- Reduced file scatter across multiple directories
- Improved project navigation and maintenance
- Consistent file organization across all Task Master projects
This change maintains full backward compatibility while providing a migration path to the improved structure.

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Fix MCP crashing after certain commands due to console logs

View File

@@ -0,0 +1,5 @@
---
'task-master-ai': patch
---
Fix add-task MCP command causing an error

View File

@@ -45,7 +45,7 @@ This document provides a detailed reference for interacting with Taskmaster, cov
* **Description:** `Parse a Product Requirements Document, PRD, or text file with Taskmaster to automatically generate an initial set of tasks in tasks.json.` * **Description:** `Parse a Product Requirements Document, PRD, or text file with Taskmaster to automatically generate an initial set of tasks in tasks.json.`
* **Key Parameters/Options:** * **Key Parameters/Options:**
* `input`: `Path to your PRD or requirements text file that Taskmaster should parse for tasks.` (CLI: `[file]` positional or `-i, --input <file>`) * `input`: `Path to your PRD or requirements text file that Taskmaster should parse for tasks.` (CLI: `[file]` positional or `-i, --input <file>`)
* `output`: `Specify where Taskmaster should save the generated 'tasks.json' file. Defaults to 'tasks/tasks.json'.` (CLI: `-o, --output <file>`) * `output`: `Specify where Taskmaster should save the generated 'tasks.json' file. Defaults to '.taskmaster/tasks/tasks.json'.` (CLI: `-o, --output <file>`)
* `numTasks`: `Approximate number of top-level tasks Taskmaster should aim to generate from the document.` (CLI: `-n, --num-tasks <number>`) * `numTasks`: `Approximate number of top-level tasks Taskmaster should aim to generate from the document.` (CLI: `-n, --num-tasks <number>`)
* `force`: `Use this to allow Taskmaster to overwrite an existing 'tasks.json' without asking for confirmation.` (CLI: `-f, --force`) * `force`: `Use this to allow Taskmaster to overwrite an existing 'tasks.json' without asking for confirmation.` (CLI: `-f, --force`)
* **Usage:** Useful for bootstrapping a project from an existing requirements document. * **Usage:** Useful for bootstrapping a project from an existing requirements document.

View File

@@ -26,6 +26,7 @@
"defaultPriority": "medium", "defaultPriority": "medium",
"projectName": "Taskmaster", "projectName": "Taskmaster",
"ollamaBaseURL": "http://localhost:11434/api", "ollamaBaseURL": "http://localhost:11434/api",
"bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
"userId": "1234567890", "userId": "1234567890",
"azureBaseURL": "https://your-endpoint.azure.com/" "azureBaseURL": "https://your-endpoint.azure.com/"
} }

View File

@@ -1,32 +0,0 @@
{
"models": {
"main": {
"provider": "anthropic",
"modelId": "claude-sonnet-4-20250514",
"maxTokens": 50000,
"temperature": 0.2
},
"research": {
"provider": "perplexity",
"modelId": "sonar-pro",
"maxTokens": 8700,
"temperature": 0.1
},
"fallback": {
"provider": "anthropic",
"modelId": "claude-3-7-sonnet-20250219",
"maxTokens": 128000,
"temperature": 0.2
}
},
"global": {
"logLevel": "info",
"debug": false,
"defaultSubtasks": 5,
"defaultPriority": "medium",
"projectName": "Taskmaster",
"ollamaBaseURL": "http://localhost:11434/api",
"userId": "1234567890",
"azureBaseURL": "https://your-endpoint.azure.com/"
}
}

View File

@@ -1,65 +1,5 @@
# task-master-ai # task-master-ai
## 0.16.0
### Minor Changes
- [#607](https://github.com/eyaltoledano/claude-task-master/pull/607) [`6a8a68e`](https://github.com/eyaltoledano/claude-task-master/commit/6a8a68e1a3f34dcdf40b355b4602a08d291f8e38) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add AWS bedrock support
- [#607](https://github.com/eyaltoledano/claude-task-master/pull/607) [`6a8a68e`](https://github.com/eyaltoledano/claude-task-master/commit/6a8a68e1a3f34dcdf40b355b4602a08d291f8e38) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - # Add Google Vertex AI Provider Integration
- Implemented `VertexAIProvider` class extending BaseAIProvider
- Added authentication and configuration handling for Vertex AI
- Updated configuration manager with Vertex-specific getters
- Modified AI services unified system to integrate the provider
- Added documentation for Vertex AI setup and configuration
- Updated environment variable examples for Vertex AI support
- Implemented specialized error handling for Vertex-specific issues
- [#607](https://github.com/eyaltoledano/claude-task-master/pull/607) [`6a8a68e`](https://github.com/eyaltoledano/claude-task-master/commit/6a8a68e1a3f34dcdf40b355b4602a08d291f8e38) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add support for Azure
- [#612](https://github.com/eyaltoledano/claude-task-master/pull/612) [`669b744`](https://github.com/eyaltoledano/claude-task-master/commit/669b744ced454116a7b29de6c58b4b8da977186a) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Increased minimum required node version to > 18 (was > 14)
- [#607](https://github.com/eyaltoledano/claude-task-master/pull/607) [`6a8a68e`](https://github.com/eyaltoledano/claude-task-master/commit/6a8a68e1a3f34dcdf40b355b4602a08d291f8e38) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Renamed baseUrl to baseURL
- [#604](https://github.com/eyaltoledano/claude-task-master/pull/604) [`80735f9`](https://github.com/eyaltoledano/claude-task-master/commit/80735f9e60c7dda7207e169697f8ac07b6733634) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Add TASK_MASTER_PROJECT_ROOT env variable supported in mcp.json and .env for project root resolution
- Some users were having issues where the MCP wasn't able to detect the location of their project root, you can now set the `TASK_MASTER_PROJECT_ROOT` environment variable to the root of your project.
- [#619](https://github.com/eyaltoledano/claude-task-master/pull/619) [`3f64202`](https://github.com/eyaltoledano/claude-task-master/commit/3f64202c9feef83f2bf383c79e4367d337c37e20) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Consolidate Task Master files into unified .taskmaster directory structure
This release introduces a new consolidated directory structure that organizes all Task Master files under a single `.taskmaster/` directory for better project organization and cleaner workspace management.
**New Directory Structure:**
- `.taskmaster/tasks/` - Task files (previously `tasks/`)
- `.taskmaster/docs/` - Documentation including PRD files (previously `scripts/`)
- `.taskmaster/reports/` - Complexity analysis reports (previously `scripts/`)
- `.taskmaster/templates/` - Template files like example PRD
- `.taskmaster/config.json` - Configuration (previously `.taskmasterconfig`)
**Migration & Backward Compatibility:**
- Existing projects continue to work with legacy file locations
- New projects use the consolidated structure automatically
- Run `task-master migrate` to move existing projects to the new structure
- All CLI commands and MCP tools automatically detect and use appropriate file locations
**Benefits:**
- Cleaner project root with Task Master files organized in one location
- Reduced file scatter across multiple directories
- Improved project navigation and maintenance
- Consistent file organization across all Task Master projects
This change maintains full backward compatibility while providing a migration path to the improved structure.
### Patch Changes
- [#607](https://github.com/eyaltoledano/claude-task-master/pull/607) [`6a8a68e`](https://github.com/eyaltoledano/claude-task-master/commit/6a8a68e1a3f34dcdf40b355b4602a08d291f8e38) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix max_tokens error when trying to use claude-sonnet-4 and claude-opus-4
- [#625](https://github.com/eyaltoledano/claude-task-master/pull/625) [`2d520de`](https://github.com/eyaltoledano/claude-task-master/commit/2d520de2694da3efe537b475ca52baf3c869edda) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix add-task MCP command causing an error
## 0.16.0-rc.0 ## 0.16.0-rc.0
### Minor Changes ### Minor Changes

View File

@@ -56,17 +56,21 @@ task-master generate # Update task markd
``` ```
project/ project/
├── tasks/ ├── .taskmaster/
│ ├── tasks.json # Main task database │ ├── tasks/ # Task files directory
│ ├── task-1.md # Individual task files │ ├── tasks.json # Main task database
── task-2.md │ ├── task-1.md # Individual task files
├── scripts/ │ │ └── task-2.md
│ ├── prd.txt # Product requirements │ ├── docs/ # Documentation directory
└── task-complexity-report.json │ ├── prd.txt # Product requirements
│ ├── reports/ # Analysis reports directory
│ │ └── task-complexity-report.json
│ ├── templates/ # Template files
│ │ └── example_prd.txt # Example PRD template
│ └── config.json # AI models & settings
├── .claude/ ├── .claude/
│ ├── settings.json # Claude Code configuration │ ├── settings.json # Claude Code configuration
│ └── commands/ # Custom slash commands │ └── commands/ # Custom slash commands
├── .taskmasterconfig # AI models & settings
├── .env # API keys ├── .env # API keys
├── .mcp.json # MCP configuration ├── .mcp.json # MCP configuration
└── CLAUDE.md # This file - auto-loaded by Claude Code └── CLAUDE.md # This file - auto-loaded by Claude Code
@@ -384,7 +388,7 @@ These commands make AI calls and may take up to a minute:
### File Management ### File Management
- Never manually edit `tasks.json` - use commands instead - Never manually edit `tasks.json` - use commands instead
- Never manually edit `.taskmasterconfig` - use `task-master models` - Never manually edit `.taskmaster/config.json` - use `task-master models`
- Task markdown files in `tasks/` are auto-generated - Task markdown files in `tasks/` are auto-generated
- Run `task-master generate` after manual changes to tasks.json - Run `task-master generate` after manual changes to tasks.json

View File

@@ -26,6 +26,7 @@
"defaultPriority": "medium", "defaultPriority": "medium",
"projectName": "Taskmaster", "projectName": "Taskmaster",
"ollamaBaseURL": "http://localhost:11434/api", "ollamaBaseURL": "http://localhost:11434/api",
"azureOpenaiBaseURL": "https://your-endpoint.openai.azure.com/" "azureOpenaiBaseURL": "https://your-endpoint.openai.azure.com/",
"bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com"
} }
} }

View File

@@ -5,5 +5,5 @@ OPENAI_API_KEY="your_openai_api_key_here" # Optional, for OpenAI/Ope
GOOGLE_API_KEY="your_google_api_key_here" # Optional, for Google Gemini models. GOOGLE_API_KEY="your_google_api_key_here" # Optional, for Google Gemini models.
MISTRAL_API_KEY="your_mistral_key_here" # Optional, for Mistral AI models. MISTRAL_API_KEY="your_mistral_key_here" # Optional, for Mistral AI models.
XAI_API_KEY="YOUR_XAI_KEY_HERE" # Optional, for xAI AI models. XAI_API_KEY="YOUR_XAI_KEY_HERE" # Optional, for xAI AI models.
AZURE_OPENAI_API_KEY="your_azure_key_here" # Optional, for Azure OpenAI models (requires endpoint in .taskmasterconfig). AZURE_OPENAI_API_KEY="your_azure_key_here" # Optional, for Azure OpenAI models (requires endpoint in .taskmaster/config.json).
OLLAMA_API_KEY="your_ollama_api_key_here" # Optional: For remote Ollama servers that require authentication. OLLAMA_API_KEY="your_ollama_api_key_here" # Optional: For remote Ollama servers that require authentication.

View File

@@ -20,7 +20,7 @@ In an AI-driven development process—particularly with tools like [Cursor](http
Task Master configuration is now managed through two primary methods: Task Master configuration is now managed through two primary methods:
1. **`.taskmasterconfig` File (Project Root - Primary)** 1. **`.taskmaster/config.json` File (Project Root - Primary)**
- Stores AI model selections (`main`, `research`, `fallback`), model parameters (`maxTokens`, `temperature`), `logLevel`, `defaultSubtasks`, `defaultPriority`, `projectName`, etc. - Stores AI model selections (`main`, `research`, `fallback`), model parameters (`maxTokens`, `temperature`), `logLevel`, `defaultSubtasks`, `defaultPriority`, `projectName`, etc.
- Managed using the `task-master models --setup` command or the `models` MCP tool. - Managed using the `task-master models --setup` command or the `models` MCP tool.
@@ -192,7 +192,7 @@ Notes:
## AI Integration (Updated) ## AI Integration (Updated)
- The script now uses a unified AI service layer (`ai-services-unified.js`). - The script now uses a unified AI service layer (`ai-services-unified.js`).
- Model selection (e.g., Claude vs. Perplexity for `--research`) is determined by the configuration in `.taskmasterconfig` based on the requested `role` (`main` or `research`). - Model selection (e.g., Claude vs. Perplexity for `--research`) is determined by the configuration in `.taskmaster/config.json` based on the requested `role` (`main` or `research`).
- API keys are automatically resolved from your `.env` file (for CLI) or MCP session environment. - API keys are automatically resolved from your `.env` file (for CLI) or MCP session environment.
- To use the research capabilities (e.g., `expand --research`), ensure you have: - To use the research capabilities (e.g., `expand --research`), ensure you have:
1. Configured a model for the `research` role using `task-master models --setup` (Perplexity models are recommended). 1. Configured a model for the `research` role using `task-master models --setup` (Perplexity models are recommended).

View File

@@ -28,8 +28,7 @@ export async function complexityReportDirect(args, log) {
log.error('complexityReportDirect called without reportPath'); log.error('complexityReportDirect called without reportPath');
return { return {
success: false, success: false,
error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' }, error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' }
fromCache: false
}; };
} }
@@ -111,8 +110,7 @@ export async function complexityReportDirect(args, log) {
error: { error: {
code: 'UNEXPECTED_ERROR', code: 'UNEXPECTED_ERROR',
message: error.message message: error.message
}, }
fromCache: false
}; };
} }
} }

View File

@@ -60,7 +60,8 @@ export async function expandAllTasksDirect(args, log, context = {}) {
useResearch, useResearch,
additionalContext, additionalContext,
forceFlag, forceFlag,
{ session, mcpLog, projectRoot } { session, mcpLog, projectRoot },
'json'
); );
// Core function now returns a summary object including the *aggregated* telemetryData // Core function now returns a summary object including the *aggregated* telemetryData

View File

@@ -29,7 +29,7 @@ import { createLogWrapper } from '../../tools/utils.js';
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @param {Object} context - Context object containing session * @param {Object} context - Context object containing session
* @param {Object} [context.session] - MCP Session object * @param {Object} [context.session] - MCP Session object
* @returns {Promise<Object>} - Task expansion result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean } * @returns {Promise<Object>} - Task expansion result { success: boolean, data?: any, error?: { code: string, message: string } }
*/ */
export async function expandTaskDirect(args, log, context = {}) { export async function expandTaskDirect(args, log, context = {}) {
const { session } = context; // Extract session const { session } = context; // Extract session
@@ -54,8 +54,7 @@ export async function expandTaskDirect(args, log, context = {}) {
error: { error: {
code: 'MISSING_ARGUMENT', code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required' message: 'tasksJsonPath is required'
}, }
fromCache: false
}; };
} }
@@ -73,8 +72,7 @@ export async function expandTaskDirect(args, log, context = {}) {
error: { error: {
code: 'INPUT_VALIDATION_ERROR', code: 'INPUT_VALIDATION_ERROR',
message: 'Task ID is required' message: 'Task ID is required'
}, }
fromCache: false
}; };
} }
@@ -105,8 +103,7 @@ export async function expandTaskDirect(args, log, context = {}) {
error: { error: {
code: 'INVALID_TASKS_FILE', code: 'INVALID_TASKS_FILE',
message: `No valid tasks found in ${tasksPath}. readJSON returned: ${JSON.stringify(data)}` message: `No valid tasks found in ${tasksPath}. readJSON returned: ${JSON.stringify(data)}`
}, }
fromCache: false
}; };
} }
@@ -121,8 +118,7 @@ export async function expandTaskDirect(args, log, context = {}) {
error: { error: {
code: 'TASK_NOT_FOUND', code: 'TASK_NOT_FOUND',
message: `Task with ID ${taskId} not found` message: `Task with ID ${taskId} not found`
}, }
fromCache: false
}; };
} }
@@ -133,8 +129,7 @@ export async function expandTaskDirect(args, log, context = {}) {
error: { error: {
code: 'TASK_COMPLETED', code: 'TASK_COMPLETED',
message: `Task ${taskId} is already marked as ${task.status} and cannot be expanded` message: `Task ${taskId} is already marked as ${task.status} and cannot be expanded`
}, }
fromCache: false
}; };
} }
@@ -151,8 +146,7 @@ export async function expandTaskDirect(args, log, context = {}) {
task, task,
subtasksAdded: 0, subtasksAdded: 0,
hasExistingSubtasks hasExistingSubtasks
}, }
fromCache: false
}; };
} }
@@ -232,8 +226,7 @@ export async function expandTaskDirect(args, log, context = {}) {
subtasksAdded, subtasksAdded,
hasExistingSubtasks, hasExistingSubtasks,
telemetryData: coreResult.telemetryData telemetryData: coreResult.telemetryData
}, }
fromCache: false
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging even if there's an error // Make sure to restore normal logging even if there's an error
@@ -245,8 +238,7 @@ export async function expandTaskDirect(args, log, context = {}) {
error: { error: {
code: 'CORE_FUNCTION_ERROR', code: 'CORE_FUNCTION_ERROR',
message: error.message || 'Failed to expand task' message: error.message || 'Failed to expand task'
}, }
fromCache: false
}; };
} }
} catch (error) { } catch (error) {
@@ -256,8 +248,7 @@ export async function expandTaskDirect(args, log, context = {}) {
error: { error: {
code: 'CORE_FUNCTION_ERROR', code: 'CORE_FUNCTION_ERROR',
message: error.message || 'Failed to expand task' message: error.message || 'Failed to expand task'
}, }
fromCache: false
}; };
} }
} }

View File

@@ -28,8 +28,7 @@ export async function generateTaskFilesDirect(args, log) {
log.error(errorMessage); log.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage }, error: { code: 'MISSING_ARGUMENT', message: errorMessage }
fromCache: false
}; };
} }
if (!outputDir) { if (!outputDir) {
@@ -37,8 +36,7 @@ export async function generateTaskFilesDirect(args, log) {
log.error(errorMessage); log.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage }, error: { code: 'MISSING_ARGUMENT', message: errorMessage }
fromCache: false
}; };
} }
@@ -65,8 +63,7 @@ export async function generateTaskFilesDirect(args, log) {
log.error(`Error in generateTaskFiles: ${genError.message}`); log.error(`Error in generateTaskFiles: ${genError.message}`);
return { return {
success: false, success: false,
error: { code: 'GENERATE_FILES_ERROR', message: genError.message }, error: { code: 'GENERATE_FILES_ERROR', message: genError.message }
fromCache: false
}; };
} }
@@ -79,8 +76,7 @@ export async function generateTaskFilesDirect(args, log) {
outputDir: resolvedOutputDir, outputDir: resolvedOutputDir,
taskFiles: taskFiles:
'Individual task files have been generated in the output directory' 'Individual task files have been generated in the output directory'
}, }
fromCache: false // This operation always modifies state and should never be cached
}; };
} catch (error) { } catch (error) {
// Make sure to restore normal logging if an outer error occurs // Make sure to restore normal logging if an outer error occurs
@@ -92,8 +88,7 @@ export async function generateTaskFilesDirect(args, log) {
error: { error: {
code: 'GENERATE_TASKS_ERROR', code: 'GENERATE_TASKS_ERROR',
message: error.message || 'Unknown error generating task files' message: error.message || 'Unknown error generating task files'
}, }
fromCache: false
}; };
} }
} }

View File

@@ -41,8 +41,7 @@ export async function initializeProjectDirect(args, log, context = {}) {
code: 'INVALID_TARGET_DIRECTORY', code: 'INVALID_TARGET_DIRECTORY',
message: `Cannot initialize project: Invalid target directory '${targetDirectory}' received. Please ensure a valid workspace/folder is open or specified.`, message: `Cannot initialize project: Invalid target directory '${targetDirectory}' received. Please ensure a valid workspace/folder is open or specified.`,
details: `Received args.projectRoot: ${args.projectRoot}` // Show what was received details: `Received args.projectRoot: ${args.projectRoot}` // Show what was received
}, }
fromCache: false
}; };
} }
@@ -97,8 +96,8 @@ export async function initializeProjectDirect(args, log, context = {}) {
} }
if (success) { if (success) {
return { success: true, data: resultData, fromCache: false }; return { success: true, data: resultData };
} else { } else {
return { success: false, error: errorResult, fromCache: false }; return { success: false, error: errorResult };
} }
} }

View File

@@ -14,7 +14,7 @@ import {
* *
* @param {Object} args - Command arguments (now expecting tasksJsonPath explicitly). * @param {Object} args - Command arguments (now expecting tasksJsonPath explicitly).
* @param {Object} log - Logger object. * @param {Object} log - Logger object.
* @returns {Promise<Object>} - Task list result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }. * @returns {Promise<Object>} - Task list result { success: boolean, data?: any, error?: { code: string, message: string } }.
*/ */
export async function listTasksDirect(args, log) { export async function listTasksDirect(args, log) {
// Destructure the explicit tasksJsonPath from args // Destructure the explicit tasksJsonPath from args
@@ -27,8 +27,7 @@ export async function listTasksDirect(args, log) {
error: { error: {
code: 'MISSING_ARGUMENT', code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required' message: 'tasksJsonPath is required'
}, }
fromCache: false
}; };
} }

View File

@@ -19,7 +19,7 @@ import {
* @param {Object} args - Command arguments * @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @returns {Promise<Object>} - Next task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean } * @returns {Promise<Object>} - Next task result { success: boolean, data?: any, error?: { code: string, message: string } }
*/ */
export async function nextTaskDirect(args, log) { export async function nextTaskDirect(args, log) {
// Destructure expected args // Destructure expected args
@@ -32,8 +32,7 @@ export async function nextTaskDirect(args, log) {
error: { error: {
code: 'MISSING_ARGUMENT', code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required' message: 'tasksJsonPath is required'
}, }
fromCache: false
}; };
} }
@@ -121,7 +120,7 @@ export async function nextTaskDirect(args, log) {
// Use the caching utility // Use the caching utility
try { try {
const result = await coreNextTaskAction(); const result = await coreNextTaskAction();
log.info(`nextTaskDirect completed.`); log.info('nextTaskDirect completed.');
return result; return result;
} catch (error) { } catch (error) {
log.error(`Unexpected error during nextTask: ${error.message}`); log.error(`Unexpected error during nextTask: ${error.message}`);
@@ -130,8 +129,7 @@ export async function nextTaskDirect(args, log) {
error: { error: {
code: 'UNEXPECTED_ERROR', code: 'UNEXPECTED_ERROR',
message: error.message message: error.message
}, }
fromCache: false
}; };
} }
} }

View File

@@ -77,7 +77,7 @@ export async function parsePRDDirect(args, log, context = {}) {
? path.isAbsolute(outputArg) ? path.isAbsolute(outputArg)
? outputArg ? outputArg
: path.resolve(projectRoot, outputArg) : path.resolve(projectRoot, outputArg)
: resolveProjectPath(TASKMASTER_TASKS_FILE, session) || : resolveProjectPath(TASKMASTER_TASKS_FILE, args) ||
path.resolve(projectRoot, TASKMASTER_TASKS_FILE); path.resolve(projectRoot, TASKMASTER_TASKS_FILE);
// Check if input file exists // Check if input file exists

View File

@@ -21,7 +21,7 @@ import {
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file. * @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 {string} args.id - The ID(s) of the task(s) or subtask(s) to remove (comma-separated for multiple).
* @param {Object} log - Logger object * @param {Object} log - Logger object
* @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: false } * @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string } }
*/ */
export async function removeTaskDirect(args, log) { export async function removeTaskDirect(args, log) {
// Destructure expected args // Destructure expected args
@@ -35,8 +35,7 @@ export async function removeTaskDirect(args, log) {
error: { error: {
code: 'MISSING_ARGUMENT', code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required' message: 'tasksJsonPath is required'
}, }
fromCache: false
}; };
} }
@@ -48,8 +47,7 @@ export async function removeTaskDirect(args, log) {
error: { error: {
code: 'INPUT_VALIDATION_ERROR', code: 'INPUT_VALIDATION_ERROR',
message: 'Task ID is required' message: 'Task ID is required'
}, }
fromCache: false
}; };
} }
@@ -68,8 +66,7 @@ export async function removeTaskDirect(args, log) {
error: { error: {
code: 'INVALID_TASKS_FILE', code: 'INVALID_TASKS_FILE',
message: `No valid tasks found in ${tasksJsonPath}` message: `No valid tasks found in ${tasksJsonPath}`
}, }
fromCache: false
}; };
} }
@@ -83,8 +80,7 @@ export async function removeTaskDirect(args, log) {
error: { error: {
code: 'INVALID_TASK_ID', code: 'INVALID_TASK_ID',
message: `The following tasks were not found: ${invalidTasks.join(', ')}` message: `The following tasks were not found: ${invalidTasks.join(', ')}`
}, }
fromCache: false
}; };
} }
@@ -133,8 +129,7 @@ export async function removeTaskDirect(args, log) {
details: failedRemovals details: failedRemovals
.map((r) => `${r.taskId}: ${r.error}`) .map((r) => `${r.taskId}: ${r.error}`)
.join('; ') .join('; ')
}, }
fromCache: false
}; };
} }
@@ -147,8 +142,7 @@ export async function removeTaskDirect(args, log) {
failed: failedRemovals.length, failed: failedRemovals.length,
results: results, results: results,
tasksPath: tasksJsonPath tasksPath: tasksJsonPath
}, }
fromCache: false
}; };
} catch (error) { } catch (error) {
// Ensure silent mode is disabled even if an outer error occurs // Ensure silent mode is disabled even if an outer error occurs
@@ -161,8 +155,7 @@ export async function removeTaskDirect(args, log) {
error: { error: {
code: 'UNEXPECTED_ERROR', code: 'UNEXPECTED_ERROR',
message: error.message message: error.message
}, }
fromCache: false
}; };
} }
} }

View File

@@ -29,8 +29,7 @@ export async function setTaskStatusDirect(args, log) {
log.error(errorMessage); log.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage }, error: { code: 'MISSING_ARGUMENT', message: errorMessage }
fromCache: false
}; };
} }
@@ -41,8 +40,7 @@ export async function setTaskStatusDirect(args, log) {
log.error(errorMessage); log.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'MISSING_TASK_ID', message: errorMessage }, error: { code: 'MISSING_TASK_ID', message: errorMessage }
fromCache: false
}; };
} }
@@ -52,8 +50,7 @@ export async function setTaskStatusDirect(args, log) {
log.error(errorMessage); log.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'MISSING_STATUS', message: errorMessage }, error: { code: 'MISSING_STATUS', message: errorMessage }
fromCache: false
}; };
} }
@@ -82,8 +79,7 @@ export async function setTaskStatusDirect(args, log) {
taskId, taskId,
status: newStatus, status: newStatus,
tasksPath: tasksPath // Return the path used tasksPath: tasksPath // Return the path used
}, }
fromCache: false // This operation always modifies state and should never be cached
}; };
// If the task was completed, attempt to fetch the next task // If the task was completed, attempt to fetch the next task
@@ -126,8 +122,7 @@ export async function setTaskStatusDirect(args, log) {
error: { error: {
code: 'SET_STATUS_ERROR', code: 'SET_STATUS_ERROR',
message: error.message || 'Unknown error setting task status' message: error.message || 'Unknown error setting task status'
}, }
fromCache: false
}; };
} finally { } finally {
// ALWAYS restore normal logging in finally block // ALWAYS restore normal logging in finally block
@@ -145,8 +140,7 @@ export async function setTaskStatusDirect(args, log) {
error: { error: {
code: 'SET_STATUS_ERROR', code: 'SET_STATUS_ERROR',
message: error.message || 'Unknown error setting task status' message: error.message || 'Unknown error setting task status'
}, }
fromCache: false
}; };
} }
} }

View File

@@ -42,8 +42,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
logWrapper.error(errorMessage); logWrapper.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage }, error: { code: 'MISSING_ARGUMENT', message: errorMessage }
fromCache: false
}; };
} }
@@ -54,8 +53,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
logWrapper.error(errorMessage); logWrapper.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'INVALID_SUBTASK_ID', message: errorMessage }, error: { code: 'INVALID_SUBTASK_ID', message: errorMessage }
fromCache: false
}; };
} }
@@ -65,8 +63,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
logWrapper.error(errorMessage); logWrapper.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'MISSING_PROMPT', message: errorMessage }, error: { code: 'MISSING_PROMPT', message: errorMessage }
fromCache: false
}; };
} }
@@ -77,8 +74,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
log.error(errorMessage); log.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'INVALID_SUBTASK_ID_TYPE', message: errorMessage }, error: { code: 'INVALID_SUBTASK_ID_TYPE', message: errorMessage }
fromCache: false
}; };
} }
@@ -88,8 +84,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
log.error(errorMessage); log.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'INVALID_SUBTASK_ID_FORMAT', message: errorMessage }, error: { code: 'INVALID_SUBTASK_ID_FORMAT', message: errorMessage }
fromCache: false
}; };
} }
@@ -128,8 +123,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
logWrapper.error(message); logWrapper.error(message);
return { return {
success: false, success: false,
error: { code: 'SUBTASK_NOT_FOUND', message: message }, error: { code: 'SUBTASK_NOT_FOUND', message: message }
fromCache: false
}; };
} }
@@ -146,8 +140,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
tasksPath, tasksPath,
useResearch, useResearch,
telemetryData: coreResult.telemetryData telemetryData: coreResult.telemetryData
}, }
fromCache: false
}; };
} catch (error) { } catch (error) {
logWrapper.error(`Error updating subtask by ID: ${error.message}`); logWrapper.error(`Error updating subtask by ID: ${error.message}`);
@@ -156,8 +149,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
error: { error: {
code: 'UPDATE_SUBTASK_CORE_ERROR', code: 'UPDATE_SUBTASK_CORE_ERROR',
message: error.message || 'Unknown error updating subtask' message: error.message || 'Unknown error updating subtask'
}, }
fromCache: false
}; };
} finally { } finally {
if (!wasSilent && isSilentMode()) { if (!wasSilent && isSilentMode()) {
@@ -174,8 +166,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
error: { error: {
code: 'DIRECT_FUNCTION_SETUP_ERROR', code: 'DIRECT_FUNCTION_SETUP_ERROR',
message: error.message || 'Unknown setup error' message: error.message || 'Unknown setup error'
}, }
fromCache: false
}; };
} }
} }

View File

@@ -42,8 +42,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
logWrapper.error(errorMessage); logWrapper.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage }, error: { code: 'MISSING_ARGUMENT', message: errorMessage }
fromCache: false
}; };
} }
@@ -54,8 +53,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
logWrapper.error(errorMessage); logWrapper.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'MISSING_TASK_ID', message: errorMessage }, error: { code: 'MISSING_TASK_ID', message: errorMessage }
fromCache: false
}; };
} }
@@ -65,8 +63,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
logWrapper.error(errorMessage); logWrapper.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'MISSING_PROMPT', message: errorMessage }, error: { code: 'MISSING_PROMPT', message: errorMessage }
fromCache: false
}; };
} }
@@ -84,8 +81,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
logWrapper.error(errorMessage); logWrapper.error(errorMessage);
return { return {
success: false, success: false,
error: { code: 'INVALID_TASK_ID', message: errorMessage }, error: { code: 'INVALID_TASK_ID', message: errorMessage }
fromCache: false
}; };
} }
} }
@@ -137,8 +133,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
taskId: taskId, taskId: taskId,
updated: false, updated: false,
telemetryData: coreResult?.telemetryData telemetryData: coreResult?.telemetryData
}, }
fromCache: false
}; };
} }
@@ -155,8 +150,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
updated: true, updated: true,
updatedTask: coreResult.updatedTask, updatedTask: coreResult.updatedTask,
telemetryData: coreResult.telemetryData telemetryData: coreResult.telemetryData
}, }
fromCache: false
}; };
} catch (error) { } catch (error) {
logWrapper.error(`Error updating task by ID: ${error.message}`); logWrapper.error(`Error updating task by ID: ${error.message}`);
@@ -165,8 +159,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
error: { error: {
code: 'UPDATE_TASK_CORE_ERROR', code: 'UPDATE_TASK_CORE_ERROR',
message: error.message || 'Unknown error updating task' message: error.message || 'Unknown error updating task'
}, }
fromCache: false
}; };
} finally { } finally {
if (!wasSilent && isSilentMode()) { if (!wasSilent && isSilentMode()) {
@@ -181,8 +174,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
error: { error: {
code: 'DIRECT_FUNCTION_SETUP_ERROR', code: 'DIRECT_FUNCTION_SETUP_ERROR',
message: error.message || 'Unknown setup error' message: error.message || 'Unknown setup error'
}, }
fromCache: false
}; };
} }
} }

View File

@@ -1,10 +1,10 @@
import path from 'path'; import path from 'path';
import fs from 'fs';
import { import {
findTasksPath as coreFindTasksPath, findTasksPath as coreFindTasksPath,
findPRDPath as coreFindPrdPath, findPRDPath as coreFindPrdPath,
findComplexityReportPath as coreFindComplexityReportPath, findComplexityReportPath as coreFindComplexityReportPath,
findProjectRoot as coreFindProjectRoot findProjectRoot as coreFindProjectRoot,
normalizeProjectRoot
} from '../../../../src/utils/path-utils.js'; } from '../../../../src/utils/path-utils.js';
import { PROJECT_MARKERS } from '../../../../src/constants/paths.js'; import { PROJECT_MARKERS } from '../../../../src/constants/paths.js';
@@ -13,22 +13,22 @@ import { PROJECT_MARKERS } from '../../../../src/constants/paths.js';
* This module handles session-specific path resolution for the MCP server * This module handles session-specific path resolution for the MCP server
*/ */
/**
* Silent logger for MCP context to prevent console output
*/
const silentLogger = {
info: () => {},
warn: () => {},
error: () => {},
debug: () => {},
success: () => {}
};
/** /**
* Cache for last found project root to improve performance * Cache for last found project root to improve performance
*/ */
export const lastFoundProjectRoot = null; export const lastFoundProjectRoot = null;
/**
* Find tasks.json file with MCP support
* @param {string} [explicitPath] - Explicit path to tasks.json (highest priority)
* @param {Object} [args] - Arguments object for context
* @param {Object} [log] - Logger object to prevent console logging
* @returns {string|null} - Resolved path to tasks.json or null if not found
*/
export function findTasksPathCore(explicitPath, args = null, log = null) {
return coreFindTasksPath(explicitPath, args, log);
}
/** /**
* Find PRD file with MCP support * Find PRD file with MCP support
* @param {string} [explicitPath] - Explicit path to PRD file (highest priority) * @param {string} [explicitPath] - Explicit path to PRD file (highest priority)
@@ -36,25 +36,10 @@ export function findTasksPathCore(explicitPath, args = null, log = null) {
* @param {Object} [log] - Logger object to prevent console logging * @param {Object} [log] - Logger object to prevent console logging
* @returns {string|null} - Resolved path to PRD file or null if not found * @returns {string|null} - Resolved path to PRD file or null if not found
*/ */
export function findPrdPath(explicitPath, args = null, log = null) { export function findPrdPath(explicitPath, args = null, log = silentLogger) {
return coreFindPrdPath(explicitPath, args, log); return coreFindPrdPath(explicitPath, args, log);
} }
/**
* Find complexity report file with MCP support
* @param {string} [explicitPath] - Explicit path to complexity report (highest priority)
* @param {Object} [args] - Arguments object for context
* @param {Object} [log] - Logger object to prevent console logging
* @returns {string|null} - Resolved path to complexity report or null if not found
*/
export function findComplexityReportPathCore(
explicitPath,
args = null,
log = null
) {
return coreFindComplexityReportPath(explicitPath, args, log);
}
/** /**
* Resolve tasks.json path from arguments * Resolve tasks.json path from arguments
* Prioritizes explicit path parameter, then uses fallback logic * Prioritizes explicit path parameter, then uses fallback logic
@@ -62,22 +47,27 @@ export function findComplexityReportPathCore(
* @param {Object} [log] - Logger object to prevent console logging * @param {Object} [log] - Logger object to prevent console logging
* @returns {string|null} - Resolved path to tasks.json or null if not found * @returns {string|null} - Resolved path to tasks.json or null if not found
*/ */
export function resolveTasksPath(args, log = null) { export function resolveTasksPath(args, log = silentLogger) {
// Get explicit path from args.file if provided // Get explicit path from args.file if provided
const explicitPath = args?.file; const explicitPath = args?.file;
const projectRoot = args?.projectRoot; const rawProjectRoot = args?.projectRoot;
// If explicit path is provided and absolute, use it directly // If explicit path is provided and absolute, use it directly
if (explicitPath && path.isAbsolute(explicitPath)) { if (explicitPath && path.isAbsolute(explicitPath)) {
return explicitPath; return explicitPath;
} }
// If explicit path is relative, resolve it relative to projectRoot // Normalize project root if provided
const projectRoot = rawProjectRoot
? normalizeProjectRoot(rawProjectRoot)
: null;
// If explicit path is relative, resolve it relative to normalized projectRoot
if (explicitPath && projectRoot) { if (explicitPath && projectRoot) {
return path.resolve(projectRoot, explicitPath); return path.resolve(projectRoot, explicitPath);
} }
// Use core findTasksPath with explicit path and projectRoot context // Use core findTasksPath with explicit path and normalized projectRoot context
if (projectRoot) { if (projectRoot) {
return coreFindTasksPath(explicitPath, { projectRoot }, log); return coreFindTasksPath(explicitPath, { projectRoot }, log);
} }
@@ -92,22 +82,27 @@ export function resolveTasksPath(args, log = null) {
* @param {Object} [log] - Logger object to prevent console logging * @param {Object} [log] - Logger object to prevent console logging
* @returns {string|null} - Resolved path to PRD file or null if not found * @returns {string|null} - Resolved path to PRD file or null if not found
*/ */
export function resolvePrdPath(args, log = null) { export function resolvePrdPath(args, log = silentLogger) {
// Get explicit path from args.input if provided // Get explicit path from args.input if provided
const explicitPath = args?.input; const explicitPath = args?.input;
const projectRoot = args?.projectRoot; const rawProjectRoot = args?.projectRoot;
// If explicit path is provided and absolute, use it directly // If explicit path is provided and absolute, use it directly
if (explicitPath && path.isAbsolute(explicitPath)) { if (explicitPath && path.isAbsolute(explicitPath)) {
return explicitPath; return explicitPath;
} }
// If explicit path is relative, resolve it relative to projectRoot // Normalize project root if provided
const projectRoot = rawProjectRoot
? normalizeProjectRoot(rawProjectRoot)
: null;
// If explicit path is relative, resolve it relative to normalized projectRoot
if (explicitPath && projectRoot) { if (explicitPath && projectRoot) {
return path.resolve(projectRoot, explicitPath); return path.resolve(projectRoot, explicitPath);
} }
// Use core findPRDPath with explicit path and projectRoot context // Use core findPRDPath with explicit path and normalized projectRoot context
if (projectRoot) { if (projectRoot) {
return coreFindPrdPath(explicitPath, { projectRoot }, log); return coreFindPrdPath(explicitPath, { projectRoot }, log);
} }
@@ -122,22 +117,27 @@ export function resolvePrdPath(args, log = null) {
* @param {Object} [log] - Logger object to prevent console logging * @param {Object} [log] - Logger object to prevent console logging
* @returns {string|null} - Resolved path to complexity report or null if not found * @returns {string|null} - Resolved path to complexity report or null if not found
*/ */
export function resolveComplexityReportPath(args, log = null) { export function resolveComplexityReportPath(args, log = silentLogger) {
// Get explicit path from args.complexityReport if provided // Get explicit path from args.complexityReport if provided
const explicitPath = args?.complexityReport; const explicitPath = args?.complexityReport;
const projectRoot = args?.projectRoot; const rawProjectRoot = args?.projectRoot;
// If explicit path is provided and absolute, use it directly // If explicit path is provided and absolute, use it directly
if (explicitPath && path.isAbsolute(explicitPath)) { if (explicitPath && path.isAbsolute(explicitPath)) {
return explicitPath; return explicitPath;
} }
// If explicit path is relative, resolve it relative to projectRoot // Normalize project root if provided
const projectRoot = rawProjectRoot
? normalizeProjectRoot(rawProjectRoot)
: null;
// If explicit path is relative, resolve it relative to normalized projectRoot
if (explicitPath && projectRoot) { if (explicitPath && projectRoot) {
return path.resolve(projectRoot, explicitPath); return path.resolve(projectRoot, explicitPath);
} }
// Use core findComplexityReportPath with explicit path and projectRoot context // Use core findComplexityReportPath with explicit path and normalized projectRoot context
if (projectRoot) { if (projectRoot) {
return coreFindComplexityReportPath(explicitPath, { projectRoot }, log); return coreFindComplexityReportPath(explicitPath, { projectRoot }, log);
} }
@@ -158,13 +158,16 @@ export function resolveProjectPath(relativePath, args) {
throw new Error('projectRoot is required in args to resolve project paths'); throw new Error('projectRoot is required in args to resolve project paths');
} }
// Normalize the project root to prevent double .taskmaster paths
const projectRoot = normalizeProjectRoot(args.projectRoot);
// If already absolute, return as-is // If already absolute, return as-is
if (path.isAbsolute(relativePath)) { if (path.isAbsolute(relativePath)) {
return relativePath; return relativePath;
} }
// Resolve relative to projectRoot // Resolve relative to normalized projectRoot
return path.resolve(args.projectRoot, relativePath); return path.resolve(projectRoot, relativePath);
} }
/** /**
@@ -184,7 +187,7 @@ export function findProjectRoot(startDir) {
* @param {Object} [log] - Log function to prevent console logging * @param {Object} [log] - Log function to prevent console logging
* @returns {string|null} - Resolved path to tasks.json or null if not found * @returns {string|null} - Resolved path to tasks.json or null if not found
*/ */
export function findTasksPath(args, log = null) { export function findTasksPath(args, log = silentLogger) {
return resolveTasksPath(args, log); return resolveTasksPath(args, log);
} }
@@ -194,7 +197,7 @@ export function findTasksPath(args, log = null) {
* @param {Object} [log] - Log function to prevent console logging * @param {Object} [log] - Log function to prevent console logging
* @returns {string|null} - Resolved path to complexity report or null if not found * @returns {string|null} - Resolved path to complexity report or null if not found
*/ */
export function findComplexityReportPath(args, log = null) { export function findComplexityReportPath(args, log = silentLogger) {
return resolveComplexityReportPath(args, log); return resolveComplexityReportPath(args, log);
} }
@@ -205,7 +208,7 @@ export function findComplexityReportPath(args, log = null) {
* @param {Object} [log] - Logger object to prevent console logging * @param {Object} [log] - Logger object to prevent console logging
* @returns {string|null} - Resolved path to PRD file or null if not found * @returns {string|null} - Resolved path to PRD file or null if not found
*/ */
export function findPRDPath(explicitPath, args = null, log = null) { export function findPRDPath(explicitPath, args = null, log = silentLogger) {
return findPrdPath(explicitPath, args, log); return findPrdPath(explicitPath, args, log);
} }

View File

@@ -11,7 +11,7 @@ import {
} from './utils.js'; } from './utils.js';
import { complexityReportDirect } from '../core/task-master-core.js'; import { complexityReportDirect } from '../core/task-master-core.js';
import { COMPLEXITY_REPORT_FILE } from '../../../src/constants/paths.js'; import { COMPLEXITY_REPORT_FILE } from '../../../src/constants/paths.js';
import path from 'path'; import { findComplexityReportPath } from '../core/utils/path-utils.js';
/** /**
* Register the complexityReport tool with the MCP server * Register the complexityReport tool with the MCP server
@@ -38,10 +38,18 @@ export function registerComplexityReportTool(server) {
`Getting complexity report with args: ${JSON.stringify(args)}` `Getting complexity report with args: ${JSON.stringify(args)}`
); );
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot) const pathArgs = {
const reportPath = args.file projectRoot: args.projectRoot,
? path.resolve(args.projectRoot, args.file) complexityReport: args.file
: path.resolve(args.projectRoot, COMPLEXITY_REPORT_FILE); };
const reportPath = findComplexityReportPath(pathArgs, log);
if (!reportPath) {
return createErrorResponse(
'No complexity report found. Run task-master analyze-complexity first.'
);
}
const result = await complexityReportDirect( const result = await complexityReportDirect(
{ {
@@ -51,9 +59,7 @@ export function registerComplexityReportTool(server) {
); );
if (result.success) { if (result.success) {
log.info( log.info('Successfully retrieved complexity report');
`Successfully retrieved complexity report${result.fromCache ? ' (from cache)' : ''}`
);
} else { } else {
log.error( log.error(
`Failed to retrieve complexity report: ${result.error.message}` `Failed to retrieve complexity report: ${result.error.message}`

View File

@@ -116,9 +116,7 @@ export function registerShowTaskTool(server) {
); );
if (result.success) { if (result.success) {
log.info( log.info(`Successfully retrieved task details for ID: ${args.id}`);
`Successfully retrieved task details for ID: ${args.id}${result.fromCache ? ' (from cache)' : ''}`
);
} else { } else {
log.error(`Failed to get task: ${result.error.message}`); log.error(`Failed to get task: ${result.error.message}`);
} }

View File

@@ -58,7 +58,7 @@ export function registerListTasksTool(server) {
// Resolve the path to tasks.json using new path utilities // Resolve the path to tasks.json using new path utilities
let tasksJsonPath; let tasksJsonPath;
try { try {
tasksJsonPath = resolveTasksPath(args, session); tasksJsonPath = resolveTasksPath(args, log);
} catch (error) { } catch (error) {
log.error(`Error finding tasks.json: ${error.message}`); log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse( return createErrorResponse(
@@ -87,7 +87,7 @@ export function registerListTasksTool(server) {
); );
log.info( log.info(
`Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks${result.fromCache ? ' (from cache)' : ''}` `Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks`
); );
return handleApiResult(result, log, 'Error getting tasks'); return handleApiResult(result, log, 'Error getting tasks');
} catch (error) { } catch (error) {

View File

@@ -47,7 +47,6 @@ export function registerModelsTool(server) {
), ),
projectRoot: z projectRoot: z
.string() .string()
.optional()
.describe('The directory of the project. Must be an absolute path.'), .describe('The directory of the project. Must be an absolute path.'),
openrouter: z openrouter: z
.boolean() .boolean()

View File

@@ -34,7 +34,6 @@ export function registerMoveTaskTool(server) {
file: z.string().optional().describe('Custom path to tasks.json file'), file: z.string().optional().describe('Custom path to tasks.json file'),
projectRoot: z projectRoot: z
.string() .string()
.optional()
.describe( .describe(
'Root directory of the project (typically derived from session)' 'Root directory of the project (typically derived from session)'
) )
@@ -95,13 +94,16 @@ export function registerMoveTaskTool(server) {
} }
} }
return { return handleApiResult(
{
success: true, success: true,
data: { data: {
moves: results, moves: results,
message: `Successfully moved ${results.length} tasks` message: `Successfully moved ${results.length} tasks`
} }
}; },
log
);
} else { } else {
// Moving a single task // Moving a single task
return handleApiResult( return handleApiResult(

View File

@@ -32,7 +32,6 @@ export function registerParsePRDTool(server) {
.describe('Absolute path to the PRD document file (.txt, .md, etc.)'), .describe('Absolute path to the PRD document file (.txt, .md, etc.)'),
projectRoot: z projectRoot: z
.string() .string()
.optional()
.describe('The directory of the project. Must be an absolute path.'), .describe('The directory of the project. Must be an absolute path.'),
output: z output: z
.string() .string()

View File

@@ -7,6 +7,7 @@ import { spawnSync } from 'child_process';
import path from 'path'; import path from 'path';
import fs from 'fs'; import fs from 'fs';
import { contextManager } from '../core/context-manager.js'; // Import the singleton import { contextManager } from '../core/context-manager.js'; // Import the singleton
import { fileURLToPath } from 'url';
// Import path utilities to ensure consistent path resolution // Import path utilities to ensure consistent path resolution
import { import {
@@ -14,6 +15,50 @@ import {
PROJECT_MARKERS PROJECT_MARKERS
} from '../core/utils/path-utils.js'; } from '../core/utils/path-utils.js';
const __filename = fileURLToPath(import.meta.url);
// Cache for version info to avoid repeated file reads
let cachedVersionInfo = null;
/**
* Get version information from package.json
* @returns {Object} Version information
*/
function getVersionInfo() {
// Return cached version if available
if (cachedVersionInfo) {
return cachedVersionInfo;
}
try {
// Navigate to the project root from the tools directory
const packageJsonPath = path.join(
path.dirname(__filename),
'../../../package.json'
);
if (fs.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
cachedVersionInfo = {
version: packageJson.version,
name: packageJson.name
};
return cachedVersionInfo;
}
cachedVersionInfo = {
version: 'unknown',
name: 'task-master-ai'
};
return cachedVersionInfo;
} catch (error) {
// Fallback version info if package.json can't be read
cachedVersionInfo = {
version: 'unknown',
name: 'task-master-ai'
};
return cachedVersionInfo;
}
}
/** /**
* Get normalized project root path * Get normalized project root path
* @param {string|undefined} projectRootRaw - Raw project root from arguments * @param {string|undefined} projectRootRaw - Raw project root from arguments
@@ -199,17 +244,19 @@ function getProjectRootFromSession(session, log) {
* @param {Function} processFunction - Optional function to process successful result data * @param {Function} processFunction - Optional function to process successful result data
* @returns {Object} - Standardized MCP response object * @returns {Object} - Standardized MCP response object
*/ */
function handleApiResult( async function handleApiResult(
result, result,
log, log,
errorPrefix = 'API error', errorPrefix = 'API error',
processFunction = processMCPResponseData processFunction = processMCPResponseData
) { ) {
// Get version info for every response
const versionInfo = getVersionInfo();
if (!result.success) { if (!result.success) {
const errorMsg = result.error?.message || `Unknown ${errorPrefix}`; const errorMsg = result.error?.message || `Unknown ${errorPrefix}`;
// Include cache status in error logs log.error(`${errorPrefix}: ${errorMsg}`);
log.error(`${errorPrefix}: ${errorMsg}. From cache: ${result.fromCache}`); // Keep logging cache status on error return createErrorResponse(errorMsg, versionInfo);
return createErrorResponse(errorMsg);
} }
// Process the result data if needed // Process the result data if needed
@@ -217,16 +264,14 @@ function handleApiResult(
? processFunction(result.data) ? processFunction(result.data)
: result.data; : result.data;
// Log success including cache status log.info('Successfully completed operation');
log.info(`Successfully completed operation. From cache: ${result.fromCache}`); // Add success log with cache status
// Create the response payload including the fromCache flag // Create the response payload including version info
const responsePayload = { const responsePayload = {
fromCache: result.fromCache, // Get the flag from the original 'result' data: processedData,
data: processedData // Nest the processed data under a 'data' key version: versionInfo
}; };
// Pass this combined payload to createContentResponse
return createContentResponse(responsePayload); return createContentResponse(responsePayload);
} }
@@ -320,8 +365,8 @@ function executeTaskMasterCommand(
* @param {Function} options.actionFn - The async function to execute if the cache misses. * @param {Function} options.actionFn - The async function to execute if the cache misses.
* Should return an object like { success: boolean, data?: any, error?: { code: string, message: string } }. * Should return an object like { success: boolean, data?: any, error?: { code: string, message: string } }.
* @param {Object} options.log - The logger instance. * @param {Object} options.log - The logger instance.
* @returns {Promise<Object>} - An object containing the result, indicating if it was from cache. * @returns {Promise<Object>} - An object containing the result.
* Format: { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean } * Format: { success: boolean, data?: any, error?: { code: string, message: string } }
*/ */
async function getCachedOrExecute({ cacheKey, actionFn, log }) { async function getCachedOrExecute({ cacheKey, actionFn, log }) {
// Check cache first // Check cache first
@@ -329,11 +374,7 @@ async function getCachedOrExecute({ cacheKey, actionFn, log }) {
if (cachedResult !== undefined) { if (cachedResult !== undefined) {
log.info(`Cache hit for key: ${cacheKey}`); log.info(`Cache hit for key: ${cacheKey}`);
// Return the cached data in the same structure as a fresh result return cachedResult;
return {
...cachedResult, // Spread the cached result to maintain its structure
fromCache: true // Just add the fromCache flag
};
} }
log.info(`Cache miss for key: ${cacheKey}. Executing action function.`); log.info(`Cache miss for key: ${cacheKey}. Executing action function.`);
@@ -341,12 +382,10 @@ async function getCachedOrExecute({ cacheKey, actionFn, log }) {
// Execute the action function if cache missed // Execute the action function if cache missed
const result = await actionFn(); const result = await actionFn();
// If the action was successful, cache the result (but without fromCache flag) // If the action was successful, cache the result
if (result.success && result.data !== undefined) { if (result.success && result.data !== undefined) {
log.info(`Action successful. Caching result for key: ${cacheKey}`); log.info(`Action successful. Caching result for key: ${cacheKey}`);
// Cache the entire result structure (minus the fromCache flag) contextManager.setCachedData(cacheKey, result);
const { fromCache, ...resultToCache } = result;
contextManager.setCachedData(cacheKey, resultToCache);
} else if (!result.success) { } else if (!result.success) {
log.warn( log.warn(
`Action failed for cache key ${cacheKey}. Result not cached. Error: ${result.error?.message}` `Action failed for cache key ${cacheKey}. Result not cached. Error: ${result.error?.message}`
@@ -357,11 +396,7 @@ async function getCachedOrExecute({ cacheKey, actionFn, log }) {
); );
} }
// Return the fresh result, indicating it wasn't from cache return result;
return {
...result,
fromCache: false
};
} }
/** /**
@@ -460,14 +495,22 @@ function createContentResponse(content) {
/** /**
* Creates error response for tools * Creates error response for tools
* @param {string} errorMessage - Error message to include in response * @param {string} errorMessage - Error message to include in response
* @param {Object} [versionInfo] - Optional version information object
* @returns {Object} - Error content response object in FastMCP format * @returns {Object} - Error content response object in FastMCP format
*/ */
function createErrorResponse(errorMessage) { function createErrorResponse(errorMessage, versionInfo) {
// Provide fallback version info if not provided
if (!versionInfo) {
versionInfo = getVersionInfo();
}
return { return {
content: [ content: [
{ {
type: 'text', type: 'text',
text: `Error: ${errorMessage}` text: `Error: ${errorMessage}
Version: ${versionInfo.version}
Name: ${versionInfo.name}`
} }
], ],
isError: true isError: true

6
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "task-master-ai", "name": "task-master-ai",
"version": "0.16.0", "version": "0.15.0",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "task-master-ai", "name": "task-master-ai",
"version": "0.16.0", "version": "0.15.0",
"license": "MIT WITH Commons-Clause", "license": "MIT WITH Commons-Clause",
"dependencies": { "dependencies": {
"@ai-sdk/amazon-bedrock": "^2.2.9", "@ai-sdk/amazon-bedrock": "^2.2.9",
@@ -64,7 +64,7 @@
"tsx": "^4.16.2" "tsx": "^4.16.2"
}, },
"engines": { "engines": {
"node": ">=18.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/@ai-sdk/amazon-bedrock": { "node_modules/@ai-sdk/amazon-bedrock": {

View File

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

View File

@@ -509,9 +509,9 @@ function createProjectStructure(addAliases, dryRun, options) {
replacements replacements
); );
// Copy .taskmasterconfig with project name to NEW location // Copy config.json with project name to NEW location
copyTemplateFile( copyTemplateFile(
'.taskmasterconfig', 'config.json',
path.join(targetDir, TASKMASTER_CONFIG_FILE), path.join(targetDir, TASKMASTER_CONFIG_FILE),
{ {
...replacements ...replacements

View File

@@ -22,6 +22,7 @@ import {
isApiKeySet, isApiKeySet,
getOllamaBaseURL, getOllamaBaseURL,
getAzureBaseURL, getAzureBaseURL,
getBedrockBaseURL,
getVertexProjectId, getVertexProjectId,
getVertexLocation getVertexLocation
} from './config-manager.js'; } from './config-manager.js';
@@ -410,6 +411,10 @@ async function _unifiedServiceRunner(serviceType, params) {
// For Ollama, use the global Ollama base URL if role-specific URL is not configured // For Ollama, use the global Ollama base URL if role-specific URL is not configured
baseURL = getOllamaBaseURL(effectiveProjectRoot); baseURL = getOllamaBaseURL(effectiveProjectRoot);
log('debug', `Using global Ollama base URL: ${baseURL}`); log('debug', `Using global Ollama base URL: ${baseURL}`);
} else if (providerName?.toLowerCase() === 'bedrock' && !baseURL) {
// For Bedrock, use the global Bedrock base URL if role-specific URL is not configured
baseURL = getBedrockBaseURL(effectiveProjectRoot);
log('debug', `Using global Bedrock base URL: ${baseURL}`);
} }
// Get AI parameters for the current role // Get AI parameters for the current role

View File

@@ -61,7 +61,8 @@ const DEFAULTS = {
defaultSubtasks: 5, defaultSubtasks: 5,
defaultPriority: 'medium', defaultPriority: 'medium',
projectName: 'Task Master', projectName: 'Task Master',
ollamaBaseURL: 'http://localhost:11434/api' ollamaBaseURL: 'http://localhost:11434/api',
bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com'
} }
}; };
@@ -382,6 +383,11 @@ function getAzureBaseURL(explicitRoot = null) {
return getGlobalConfig(explicitRoot).azureBaseURL; return getGlobalConfig(explicitRoot).azureBaseURL;
} }
function getBedrockBaseURL(explicitRoot = null) {
// Directly return value from config
return getGlobalConfig(explicitRoot).bedrockBaseURL;
}
/** /**
* Gets the Google Cloud project ID for Vertex AI from configuration * Gets the Google Cloud project ID for Vertex AI from configuration
* @param {string|null} explicitRoot - Optional explicit path to the project root. * @param {string|null} explicitRoot - Optional explicit path to the project root.
@@ -779,6 +785,7 @@ export {
getProjectName, getProjectName,
getOllamaBaseURL, getOllamaBaseURL,
getAzureBaseURL, getAzureBaseURL,
getBedrockBaseURL,
getParametersForRole, getParametersForRole,
getUserId, getUserId,
// API Key Checkers (still relevant) // API Key Checkers (still relevant)

View File

@@ -482,6 +482,11 @@ async function setModel(role, modelId, options = {}) {
`Model ID "${modelId}" not found in the Ollama instance. Please verify the model is pulled and available. You can check available models with: curl ${tagsUrl}` `Model ID "${modelId}" not found in the Ollama instance. Please verify the model is pulled and available. You can check available models with: curl ${tagsUrl}`
); );
} }
} else if (providerHint === 'bedrock') {
// Set provider without model validation since Bedrock models are managed by AWS
determinedProvider = 'bedrock';
warningMessage = `Warning: Custom Bedrock model '${modelId}' set. Please ensure the model ID is valid and accessible in your AWS account.`;
report('warn', warningMessage);
} else { } else {
// Invalid provider hint - should not happen // Invalid provider hint - should not happen
throw new Error(`Invalid provider hint received: ${providerHint}`); throw new Error(`Invalid provider hint received: ${providerHint}`);

View File

@@ -24,7 +24,10 @@ import {
} from './task-manager.js'; } from './task-manager.js';
import { getProjectName, getDefaultSubtasks } from './config-manager.js'; import { getProjectName, getDefaultSubtasks } from './config-manager.js';
import { TASK_STATUS_OPTIONS } from '../../src/constants/task-status.js'; import { TASK_STATUS_OPTIONS } from '../../src/constants/task-status.js';
import { TASKMASTER_TASKS_FILE } from '../../src/constants/paths.js'; import {
TASKMASTER_CONFIG_FILE,
TASKMASTER_TASKS_FILE
} from '../../src/constants/paths.js';
import { getTaskMasterVersion } from '../../src/utils/getVersion.js'; import { getTaskMasterVersion } from '../../src/utils/getVersion.js';
// Create a color gradient for the banner // Create a color gradient for the banner
@@ -687,7 +690,7 @@ function displayHelp() {
configTable.push( configTable.push(
[ [
`${chalk.yellow('.taskmasterconfig')}${chalk.reset('')}`, `${chalk.yellow(TASKMASTER_CONFIG_FILE)}${chalk.reset('')}`,
`${chalk.white('AI model configuration file (project root)')}${chalk.reset('')}`, `${chalk.white('AI model configuration file (project root)')}${chalk.reset('')}`,
`${chalk.dim('Managed by models cmd')}${chalk.reset('')}` `${chalk.dim('Managed by models cmd')}${chalk.reset('')}`
], ],
@@ -1851,7 +1854,7 @@ function displayApiKeyStatus(statusReport) {
console.log(table.toString()); console.log(table.toString());
console.log( console.log(
chalk.gray( chalk.gray(
' Note: Some providers (e.g., Azure, Ollama) may require additional endpoint configuration in .taskmasterconfig.' ` Note: Some providers (e.g., Azure, Ollama) may require additional endpoint configuration in ${TASKMASTER_CONFIG_FILE}.`
) )
); );
} }

View File

@@ -155,8 +155,17 @@ function log(level, ...args) {
return; return;
} }
// Get log level dynamically from config-manager // GUARD: Prevent circular dependency during config loading
const configLevel = getLogLevel() || 'info'; // Use getter // Use a simple fallback log level instead of calling getLogLevel()
let configLevel = 'info'; // Default fallback
try {
// Only try to get config level if we're not in the middle of config loading
configLevel = getLogLevel() || 'info';
} catch (error) {
// If getLogLevel() fails (likely due to circular dependency),
// use default 'info' level and continue
configLevel = 'info';
}
// Use text prefixes instead of emojis // Use text prefixes instead of emojis
const prefixes = { const prefixes = {
@@ -190,8 +199,17 @@ function log(level, ...args) {
* @returns {Object|null} Parsed JSON data or null if error occurs * @returns {Object|null} Parsed JSON data or null if error occurs
*/ */
function readJSON(filepath) { function readJSON(filepath) {
// Get debug flag dynamically from config-manager // GUARD: Prevent circular dependency during config loading
const isDebug = getDebugFlag(); let isDebug = false; // Default fallback
try {
// Only try to get debug flag if we're not in the middle of config loading
isDebug = getDebugFlag();
} catch (error) {
// If getDebugFlag() fails (likely due to circular dependency),
// use default false and continue
isDebug = false;
}
try { try {
const rawData = fs.readFileSync(filepath, 'utf8'); const rawData = fs.readFileSync(filepath, 'utf8');
return JSON.parse(rawData); return JSON.parse(rawData);
@@ -212,8 +230,17 @@ function readJSON(filepath) {
* @param {Object} data - Data to write * @param {Object} data - Data to write
*/ */
function writeJSON(filepath, data) { function writeJSON(filepath, data) {
// Get debug flag dynamically from config-manager // GUARD: Prevent circular dependency during config loading
const isDebug = getDebugFlag(); let isDebug = false; // Default fallback
try {
// Only try to get debug flag if we're not in the middle of config loading
isDebug = getDebugFlag();
} catch (error) {
// If getDebugFlag() fails (likely due to circular dependency),
// use default false and continue
isDebug = false;
}
try { try {
const dir = path.dirname(filepath); const dir = path.dirname(filepath);
if (!fs.existsSync(dir)) { if (!fs.existsSync(dir)) {
@@ -246,8 +273,17 @@ function sanitizePrompt(prompt) {
* @returns {Object|null} The parsed complexity report or null if not found * @returns {Object|null} The parsed complexity report or null if not found
*/ */
function readComplexityReport(customPath = null) { function readComplexityReport(customPath = null) {
// Get debug flag dynamically from config-manager // GUARD: Prevent circular dependency during config loading
const isDebug = getDebugFlag(); let isDebug = false; // Default fallback
try {
// Only try to get debug flag if we're not in the middle of config loading
isDebug = getDebugFlag();
} catch (error) {
// If getDebugFlag() fails (likely due to circular dependency),
// use default false and continue
isDebug = false;
}
try { try {
let reportPath; let reportPath;
if (customPath) { if (customPath) {

31
src/utils/logger-utils.js Normal file
View File

@@ -0,0 +1,31 @@
/**
* Logger utility functions for Task Master
* Provides standardized logging patterns for both CLI and utility contexts
*/
import { log as utilLog } from '../../scripts/modules/utils.js';
/**
* Creates a standard logger object that wraps the utility log function
* This provides a consistent logger interface across different parts of the application
* @returns {Object} A logger object with standard logging methods (info, warn, error, debug, success)
*/
export function createStandardLogger() {
return {
info: (msg, ...args) => utilLog('info', msg, ...args),
warn: (msg, ...args) => utilLog('warn', msg, ...args),
error: (msg, ...args) => utilLog('error', msg, ...args),
debug: (msg, ...args) => utilLog('debug', msg, ...args),
success: (msg, ...args) => utilLog('success', msg, ...args)
};
}
/**
* Creates a logger using either the provided logger or a default standard logger
* This is the recommended pattern for functions that accept an optional logger parameter
* @param {Object|null} providedLogger - Optional logger object passed from caller
* @returns {Object} A logger object with standard logging methods
*/
export function getLoggerOrDefault(providedLogger = null) {
return providedLogger || createStandardLogger();
}

View File

@@ -14,6 +14,33 @@ import {
TASKMASTER_CONFIG_FILE, TASKMASTER_CONFIG_FILE,
LEGACY_CONFIG_FILE LEGACY_CONFIG_FILE
} from '../constants/paths.js'; } from '../constants/paths.js';
import { getLoggerOrDefault } from './logger-utils.js';
/**
* Normalize project root to ensure it doesn't end with .taskmaster
* This prevents double .taskmaster paths when using constants that include .taskmaster
* @param {string} projectRoot - The project root path to normalize
* @returns {string} - Normalized project root path
*/
export function normalizeProjectRoot(projectRoot) {
if (!projectRoot) return projectRoot;
// Split the path into segments
const segments = projectRoot.split(path.sep);
// Find the index of .taskmaster segment
const taskmasterIndex = segments.findIndex(
(segment) => segment === '.taskmaster'
);
if (taskmasterIndex !== -1) {
// If .taskmaster is found, return everything up to but not including .taskmaster
const normalizedSegments = segments.slice(0, taskmasterIndex);
return normalizedSegments.join(path.sep) || path.sep;
}
return projectRoot;
}
/** /**
* Find the project root directory by looking for project markers * Find the project root directory by looking for project markers
@@ -59,7 +86,8 @@ export function findProjectRoot(startDir = process.cwd()) {
* @returns {string|null} - Resolved tasks.json path or null if not found * @returns {string|null} - Resolved tasks.json path or null if not found
*/ */
export function findTasksPath(explicitPath = null, args = null, log = null) { export function findTasksPath(explicitPath = null, args = null, log = null) {
const logger = log || console; // Use the passed logger if available, otherwise use the default logger
const logger = getLoggerOrDefault(log);
// 1. If explicit path is provided, use it (highest priority) // 1. If explicit path is provided, use it (highest priority)
if (explicitPath) { if (explicitPath) {
@@ -78,14 +106,17 @@ export function findTasksPath(explicitPath = null, args = null, log = null) {
} }
// 2. Try to get project root from args (MCP) or find it // 2. Try to get project root from args (MCP) or find it
const projectRoot = args?.projectRoot || findProjectRoot(); const rawProjectRoot = args?.projectRoot || findProjectRoot();
if (!projectRoot) { if (!rawProjectRoot) {
logger.warn?.('Could not determine project root directory'); logger.warn?.('Could not determine project root directory');
return null; return null;
} }
// 3. Check possible locations in order of preference // 3. Normalize project root to prevent double .taskmaster paths
const projectRoot = normalizeProjectRoot(rawProjectRoot);
// 4. Check possible locations in order of preference
const possiblePaths = [ const possiblePaths = [
path.join(projectRoot, TASKMASTER_TASKS_FILE), // .taskmaster/tasks/tasks.json (NEW) path.join(projectRoot, TASKMASTER_TASKS_FILE), // .taskmaster/tasks/tasks.json (NEW)
path.join(projectRoot, 'tasks.json'), // tasks.json in root (LEGACY) path.join(projectRoot, 'tasks.json'), // tasks.json in root (LEGACY)
@@ -130,7 +161,7 @@ export function findTasksPath(explicitPath = null, args = null, log = null) {
* @returns {string|null} - Resolved PRD document path or null if not found * @returns {string|null} - Resolved PRD document path or null if not found
*/ */
export function findPRDPath(explicitPath = null, args = null, log = null) { export function findPRDPath(explicitPath = null, args = null, log = null) {
const logger = log || console; const logger = getLoggerOrDefault(log);
// 1. If explicit path is provided, use it (highest priority) // 1. If explicit path is provided, use it (highest priority)
if (explicitPath) { if (explicitPath) {
@@ -149,14 +180,17 @@ export function findPRDPath(explicitPath = null, args = null, log = null) {
} }
// 2. Try to get project root from args (MCP) or find it // 2. Try to get project root from args (MCP) or find it
const projectRoot = args?.projectRoot || findProjectRoot(); const rawProjectRoot = args?.projectRoot || findProjectRoot();
if (!projectRoot) { if (!rawProjectRoot) {
logger.warn?.('Could not determine project root directory'); logger.warn?.('Could not determine project root directory');
return null; return null;
} }
// 3. Check possible locations in order of preference // 3. Normalize project root to prevent double .taskmaster paths
const projectRoot = normalizeProjectRoot(rawProjectRoot);
// 4. Check possible locations in order of preference
const locations = [ const locations = [
TASKMASTER_DOCS_DIR, // .taskmaster/docs/ (NEW) TASKMASTER_DOCS_DIR, // .taskmaster/docs/ (NEW)
'scripts/', // Legacy location 'scripts/', // Legacy location
@@ -199,7 +233,7 @@ export function findComplexityReportPath(
args = null, args = null,
log = null log = null
) { ) {
const logger = log || console; const logger = getLoggerOrDefault(log);
// 1. If explicit path is provided, use it (highest priority) // 1. If explicit path is provided, use it (highest priority)
if (explicitPath) { if (explicitPath) {
@@ -218,14 +252,17 @@ export function findComplexityReportPath(
} }
// 2. Try to get project root from args (MCP) or find it // 2. Try to get project root from args (MCP) or find it
const projectRoot = args?.projectRoot || findProjectRoot(); const rawProjectRoot = args?.projectRoot || findProjectRoot();
if (!projectRoot) { if (!rawProjectRoot) {
logger.warn?.('Could not determine project root directory'); logger.warn?.('Could not determine project root directory');
return null; return null;
} }
// 3. Check possible locations in order of preference // 3. Normalize project root to prevent double .taskmaster paths
const projectRoot = normalizeProjectRoot(rawProjectRoot);
// 4. Check possible locations in order of preference
const locations = [ const locations = [
TASKMASTER_REPORTS_DIR, // .taskmaster/reports/ (NEW) TASKMASTER_REPORTS_DIR, // .taskmaster/reports/ (NEW)
'scripts/', // Legacy location 'scripts/', // Legacy location
@@ -268,7 +305,7 @@ export function resolveTasksOutputPath(
args = null, args = null,
log = null log = null
) { ) {
const logger = log || console; const logger = getLoggerOrDefault(log);
// 1. If explicit path is provided, use it // 1. If explicit path is provided, use it
if (explicitPath) { if (explicitPath) {
@@ -281,9 +318,13 @@ export function resolveTasksOutputPath(
} }
// 2. Try to get project root from args (MCP) or find it // 2. Try to get project root from args (MCP) or find it
const projectRoot = args?.projectRoot || findProjectRoot() || process.cwd(); const rawProjectRoot =
args?.projectRoot || findProjectRoot() || process.cwd();
// 3. Use new .taskmaster structure by default // 3. Normalize project root to prevent double .taskmaster paths
const projectRoot = normalizeProjectRoot(rawProjectRoot);
// 4. Use new .taskmaster structure by default
const defaultPath = path.join(projectRoot, TASKMASTER_TASKS_FILE); const defaultPath = path.join(projectRoot, TASKMASTER_TASKS_FILE);
logger.info?.(`Using default output path: ${defaultPath}`); logger.info?.(`Using default output path: ${defaultPath}`);
@@ -309,7 +350,7 @@ export function resolveComplexityReportOutputPath(
args = null, args = null,
log = null log = null
) { ) {
const logger = log || console; const logger = getLoggerOrDefault(log);
// 1. If explicit path is provided, use it // 1. If explicit path is provided, use it
if (explicitPath) { if (explicitPath) {
@@ -324,9 +365,13 @@ export function resolveComplexityReportOutputPath(
} }
// 2. Try to get project root from args (MCP) or find it // 2. Try to get project root from args (MCP) or find it
const projectRoot = args?.projectRoot || findProjectRoot() || process.cwd(); const rawProjectRoot =
args?.projectRoot || findProjectRoot() || process.cwd();
// 3. Use new .taskmaster structure by default // 3. Normalize project root to prevent double .taskmaster paths
const projectRoot = normalizeProjectRoot(rawProjectRoot);
// 4. Use new .taskmaster structure by default
const defaultPath = path.join(projectRoot, COMPLEXITY_REPORT_FILE); const defaultPath = path.join(projectRoot, COMPLEXITY_REPORT_FILE);
logger.info?.(`Using default complexity report output path: ${defaultPath}`); logger.info?.(`Using default complexity report output path: ${defaultPath}`);
@@ -348,7 +393,7 @@ export function resolveComplexityReportOutputPath(
* @returns {string|null} - Resolved config file path or null if not found * @returns {string|null} - Resolved config file path or null if not found
*/ */
export function findConfigPath(explicitPath = null, args = null, log = null) { export function findConfigPath(explicitPath = null, args = null, log = null) {
const logger = log || console; const logger = getLoggerOrDefault(log);
// 1. If explicit path is provided, use it (highest priority) // 1. If explicit path is provided, use it (highest priority)
if (explicitPath) { if (explicitPath) {
@@ -367,14 +412,17 @@ export function findConfigPath(explicitPath = null, args = null, log = null) {
} }
// 2. Try to get project root from args (MCP) or find it // 2. Try to get project root from args (MCP) or find it
const projectRoot = args?.projectRoot || findProjectRoot(); const rawProjectRoot = args?.projectRoot || findProjectRoot();
if (!projectRoot) { if (!rawProjectRoot) {
logger.warn?.('Could not determine project root directory'); logger.warn?.('Could not determine project root directory');
return null; return null;
} }
// 3. Check possible locations in order of preference // 3. Normalize project root to prevent double .taskmaster paths
const projectRoot = normalizeProjectRoot(rawProjectRoot);
// 4. Check possible locations in order of preference
const possiblePaths = [ const possiblePaths = [
path.join(projectRoot, TASKMASTER_CONFIG_FILE), // NEW location path.join(projectRoot, TASKMASTER_CONFIG_FILE), // NEW location
path.join(projectRoot, LEGACY_CONFIG_FILE) // LEGACY location path.join(projectRoot, LEGACY_CONFIG_FILE) // LEGACY location
@@ -382,12 +430,6 @@ export function findConfigPath(explicitPath = null, args = null, log = null) {
for (const configPath of possiblePaths) { for (const configPath of possiblePaths) {
if (fs.existsSync(configPath)) { if (fs.existsSync(configPath)) {
try {
logger.info?.(`Found config file at: ${configPath}`);
} catch (error) {
// Silently handle logging errors during testing
}
// Issue deprecation warning for legacy paths // Issue deprecation warning for legacy paths
if (configPath?.endsWith(LEGACY_CONFIG_FILE)) { if (configPath?.endsWith(LEGACY_CONFIG_FILE)) {
logger.warn?.( logger.warn?.(

View File

@@ -253,8 +253,7 @@ describe('MCP Server Direct Functions', () => {
error: { error: {
code: 'FILE_NOT_FOUND_ERROR', code: 'FILE_NOT_FOUND_ERROR',
message: 'Tasks file not found' message: 'Tasks file not found'
}, }
fromCache: false
}; };
} }
@@ -288,8 +287,7 @@ describe('MCP Server Direct Functions', () => {
.length, .length,
pending: tasksData.filter((t) => t.status === 'pending').length pending: tasksData.filter((t) => t.status === 'pending').length
} }
}, }
fromCache: false
}; };
} }
@@ -305,8 +303,7 @@ describe('MCP Server Direct Functions', () => {
total: tasksData.length, total: tasksData.length,
filtered: filteredTasks.length filtered: filteredTasks.length
} }
}, }
fromCache: false
}; };
} }
@@ -320,8 +317,7 @@ describe('MCP Server Direct Functions', () => {
stats: { stats: {
total: tasksData.length total: tasksData.length
} }
}, }
fromCache: false
}; };
} }
@@ -441,8 +437,7 @@ describe('MCP Server Direct Functions', () => {
error: { error: {
code: 'INPUT_VALIDATION_ERROR', code: 'INPUT_VALIDATION_ERROR',
message: 'Task ID is required' message: 'Task ID is required'
}, }
fromCache: false
}; };
} }
@@ -454,8 +449,7 @@ describe('MCP Server Direct Functions', () => {
error: { error: {
code: 'TASK_NOT_FOUND', code: 'TASK_NOT_FOUND',
message: `Task with ID ${args.id} not found` message: `Task with ID ${args.id} not found`
}, }
fromCache: false
}; };
} }
@@ -469,8 +463,7 @@ describe('MCP Server Direct Functions', () => {
error: { error: {
code: 'TASK_COMPLETED', code: 'TASK_COMPLETED',
message: `Task ${args.id} is already marked as done and cannot be expanded` message: `Task ${args.id} is already marked as done and cannot be expanded`
}, }
fromCache: false
}; };
} }
@@ -495,8 +488,7 @@ describe('MCP Server Direct Functions', () => {
task: expandedTask, task: expandedTask,
subtasksAdded: expandedTask.subtasks.length, subtasksAdded: expandedTask.subtasks.length,
hasExistingSubtasks: false hasExistingSubtasks: false
}, }
fromCache: false
}; };
} }

View File

@@ -43,6 +43,7 @@ const mockGetBaseUrlForRole = jest.fn();
const mockGetAllProviders = jest.fn(); const mockGetAllProviders = jest.fn();
const mockGetOllamaBaseURL = jest.fn(); const mockGetOllamaBaseURL = jest.fn();
const mockGetAzureBaseURL = jest.fn(); const mockGetAzureBaseURL = jest.fn();
const mockGetBedrockBaseURL = jest.fn();
const mockGetVertexProjectId = jest.fn(); const mockGetVertexProjectId = jest.fn();
const mockGetVertexLocation = jest.fn(); const mockGetVertexLocation = jest.fn();
const mockGetAvailableModels = jest.fn(); const mockGetAvailableModels = jest.fn();
@@ -113,6 +114,7 @@ jest.unstable_mockModule('../../scripts/modules/config-manager.js', () => ({
getAllProviders: mockGetAllProviders, getAllProviders: mockGetAllProviders,
getOllamaBaseURL: mockGetOllamaBaseURL, getOllamaBaseURL: mockGetOllamaBaseURL,
getAzureBaseURL: mockGetAzureBaseURL, getAzureBaseURL: mockGetAzureBaseURL,
getBedrockBaseURL: mockGetBedrockBaseURL,
getVertexProjectId: mockGetVertexProjectId, getVertexProjectId: mockGetVertexProjectId,
getVertexLocation: mockGetVertexLocation, getVertexLocation: mockGetVertexLocation,
getMcpApiKeyStatus: mockGetMcpApiKeyStatus getMcpApiKeyStatus: mockGetMcpApiKeyStatus

View File

@@ -139,7 +139,8 @@ const DEFAULT_CONFIG = {
defaultSubtasks: 5, defaultSubtasks: 5,
defaultPriority: 'medium', defaultPriority: 'medium',
projectName: 'Task Master', projectName: 'Task Master',
ollamaBaseURL: 'http://localhost:11434/api' ollamaBaseURL: 'http://localhost:11434/api',
bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com'
} }
}; };

View File

@@ -127,6 +127,7 @@ jest.unstable_mockModule(
getProjectName: jest.fn(() => 'Test Project'), getProjectName: jest.fn(() => 'Test Project'),
getOllamaBaseURL: jest.fn(() => 'http://localhost:11434/api'), getOllamaBaseURL: jest.fn(() => 'http://localhost:11434/api'),
getAzureBaseURL: jest.fn(() => undefined), getAzureBaseURL: jest.fn(() => undefined),
getBedrockBaseURL: jest.fn(() => undefined),
getParametersForRole: jest.fn(() => ({ getParametersForRole: jest.fn(() => ({
maxTokens: 4000, maxTokens: 4000,
temperature: 0.7 temperature: 0.7