Compare commits

..

20 Commits

Author SHA1 Message Date
Aaron Gabriel Neyer
cba86510d3 Update README.md - Remove trailing commas (#673)
JSON doesn't allow for trailing commas, so these need to be removed in order for this to work
2025-06-05 19:08:24 +02:00
Joe Danziger
86ea6d1dbc Add one-click MCP server installation for Cursor (#671) 2025-06-05 19:08:15 +02:00
Saksham Goel
a22d2a45b5 Fixed the Typo in cursor rules Issue:#675 (#677)
Fixed the typo in the Api keys
2025-06-05 19:06:01 +02:00
Ralph Khreish
d73c8e17ec Merge pull request #661 from eyaltoledano/chore/update.next
Update next from main branch
2025-06-03 18:13:22 +02:00
Ralph Khreish
4f23751d25 chore: update package-lock.json 2025-06-03 18:12:02 +02:00
Ibrahim H.
7d5c028ca0 fix: markdown format (#622) 2025-06-03 15:54:13 +02:00
github-actions[bot]
f18df6da19 Version Packages 2025-06-03 15:14:34 +02:00
github-actions[bot]
1754a31372 Version Packages 2025-06-03 15:13:26 +02:00
Ralph Khreish
3096ccdfb3 chore: add package-lock.json 2025-06-03 15:13:26 +02:00
github-actions[bot]
6464bb11e5 Version Packages 2025-06-03 15:13:26 +02:00
Ralph Khreish
edaa5fe0d5 fix: projectRoot duplicate .taskmaster directory (#655) 2025-06-03 15:12:50 +02:00
Ralph Khreish
41d9dbbe6d Merge pull request #650 from eyaltoledano/changeset-release/main 2025-06-03 01:40:34 +02:00
github-actions[bot]
6e0d866756 Version Packages 2025-06-02 23:26:36 +00:00
Ralph Khreish
926aa61a4e Merge pull request #642 from eyaltoledano/next
Release 0.16.1
2025-06-03 01:26:12 +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
Ralph Khreish
293b59bac6 Merge pull request #630 from eyaltoledano/changeset-release/main
Version Packages
2025-06-01 17:49:18 +02:00
Ralph Khreish
1809c4ed7b chore: add package-lock.json 2025-06-01 11:48:11 -04:00
github-actions[bot]
6e406958c1 Version Packages 2025-06-01 15:24:59 +00:00
Ralph Khreish
074b7ec0bc Merge pull request #625 from eyaltoledano/next 2025-06-01 17:24:37 +02:00
42 changed files with 12183 additions and 12132 deletions

View File

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

View File

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

View File

@@ -1,13 +0,0 @@
---
'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

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,5 @@
---
"task-master-ai": patch
---
Add one-click MCP server installation for Cursor

View File

@@ -1,18 +0,0 @@
{
"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

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

View File

@@ -1,7 +0,0 @@
---
'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

@@ -1,31 +0,0 @@
---
"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

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

View File

@@ -1,5 +0,0 @@
---
'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.
@@ -395,7 +395,7 @@ Environment variables are used **only** for sensitive API keys related to AI pro
* `AZURE_OPENAI_API_KEY` (Requires `AZURE_OPENAI_ENDPOINT` too) * `AZURE_OPENAI_API_KEY` (Requires `AZURE_OPENAI_ENDPOINT` too)
* `OPENROUTER_API_KEY` * `OPENROUTER_API_KEY`
* `XAI_API_KEY` * `XAI_API_KEY`
* `OLLANA_API_KEY` (Requires `OLLAMA_BASE_URL` too) * `OLLAMA_API_KEY` (Requires `OLLAMA_BASE_URL` too)
* **Endpoints (Optional/Provider Specific inside .taskmaster/config.json):** * **Endpoints (Optional/Provider Specific inside .taskmaster/config.json):**
* `AZURE_OPENAI_ENDPOINT` * `AZURE_OPENAI_ENDPOINT`
* `OLLAMA_BASE_URL` (Default: `http://localhost:11434/api`) * `OLLAMA_BASE_URL` (Default: `http://localhost:11434/api`)

View File

@@ -1,5 +1,79 @@
# task-master-ai # task-master-ai
## 0.16.1
### Patch Changes
- [#641](https://github.com/eyaltoledano/claude-task-master/pull/641) [`ad61276`](https://github.com/eyaltoledano/claude-task-master/commit/ad612763ffbdd35aa1b593c9613edc1dc27a8856) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix bedrock issues
- [#648](https://github.com/eyaltoledano/claude-task-master/pull/648) [`9b4168b`](https://github.com/eyaltoledano/claude-task-master/commit/9b4168bb4e4dfc2f4fb0cf6bd5f81a8565879176) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix MCP tool calls logging errors
- [#641](https://github.com/eyaltoledano/claude-task-master/pull/641) [`ad61276`](https://github.com/eyaltoledano/claude-task-master/commit/ad612763ffbdd35aa1b593c9613edc1dc27a8856) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Update rules for new directory structure
- [#648](https://github.com/eyaltoledano/claude-task-master/pull/648) [`9b4168b`](https://github.com/eyaltoledano/claude-task-master/commit/9b4168bb4e4dfc2f4fb0cf6bd5f81a8565879176) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix bug in expand_all mcp tool
- [#641](https://github.com/eyaltoledano/claude-task-master/pull/641) [`ad61276`](https://github.com/eyaltoledano/claude-task-master/commit/ad612763ffbdd35aa1b593c9613edc1dc27a8856) Thanks [@Crunchyman-ralph](https://github.com/Crunchyman-ralph)! - Fix MCP crashing after certain commands due to console logs
## 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

@@ -39,9 +39,17 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
| **Cursor** | Global | `~/.cursor/mcp.json` | `%USERPROFILE%\.cursor\mcp.json` | `mcpServers` | | **Cursor** | Global | `~/.cursor/mcp.json` | `%USERPROFILE%\.cursor\mcp.json` | `mcpServers` |
| | Project | `<project_folder>/.cursor/mcp.json` | `<project_folder>\.cursor\mcp.json` | `mcpServers` | | | Project | `<project_folder>/.cursor/mcp.json` | `<project_folder>\.cursor\mcp.json` | `mcpServers` |
| **Windsurf** | Global | `~/.codeium/windsurf/mcp_config.json` | `%USERPROFILE%\.codeium\windsurf\mcp_config.json` | `mcpServers` | | **Windsurf** | Global | `~/.codeium/windsurf/mcp_config.json` | `%USERPROFILE%\.codeium\windsurf\mcp_config.json` | `mcpServers` |
| **VSCode** | Project | `<project_folder>/.vscode/mcp.json` | `<project_folder>\.vscode\mcp.json` | `servers` | | **VS Code** | Project | `<project_folder>/.vscode/mcp.json` | `<project_folder>\.vscode\mcp.json` | `servers` |
##### Cursor & Windsurf (`mcpServers`) ##### Quick Install for Cursor (One-Click)
[<img src="https://cursor.com/deeplink/mcp-install-dark.png" alt="Add Task Master MCP server to Cursor" style="max-height: 32px;">](cursor://anysphere.cursor-deeplink/mcp/install?name=taskmaster-ai&config=eyJjb21tYW5kIjoibnB4IiwiYXJncyI6WyIteSIsIi0tcGFja2FnZT10YXNrLW1hc3Rlci1haSIsInRhc2stbWFzdGVyLWFpIl0sImVudiI6eyJBTlRIUk9QSUNfQVBJX0tFWSI6IllPVVJfQU5USFJPUElDX0FQSV9LRVlfSEVSRSIsIlBFUlBMRVhJVFlfQVBJX0tFWSI6IllPVVJfUEVSUExFWElUWV9BUElfS0VZX0hFUkUiLCJPUEVOQUlfQVBJX0tFWSI6IllPVVJfT1BFTkFJX0tFWV9IRVJFIiwiR09PR0xFX0FQSV9LRVkiOiJZT1VSX0dPT0dMRV9LRVlfSEVSRSIsIk1JU1RSQUxfQVBJX0tFWSI6IllPVVJfTUlTVFJBTF9LRVlfSEVSRSIsIk9QRU5ST1VURVJfQVBJX0tFWSI6IllPVVJfT1BFTlJPVVRFUl9LRVlfSEVSRSIsIlhBSV9BUElfS0VZIjoiWU9VUl9YQUlfS0VZX0hFUkUiLCJBWlVSRV9PUEVOQUJFX0FQSV9LRVkiOiJZT1VSX0FaVVJFX0tFWV9IRVJFIiwiT0xMQU1BX0FQSV9LRVkiOiJZT1VSX09MTEFNQV9BUElfS0VZX0hFUkUifX0%3D)
> **Note:** After clicking the install button, you'll still need to add your API keys to the configuration. The button installs the MCP server with placeholder keys that you'll need to replace with your actual API keys.
##### Manual Configuration
###### Cursor & Windsurf (`mcpServers`)
```jsonc ```jsonc
{ {
@@ -58,16 +66,16 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
"OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE", "OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE",
"XAI_API_KEY": "YOUR_XAI_KEY_HERE", "XAI_API_KEY": "YOUR_XAI_KEY_HERE",
"AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE", "AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE",
"OLLAMA_API_KEY": "YOUR_OLLAMA_API_KEY_HERE", "OLLAMA_API_KEY": "YOUR_OLLAMA_API_KEY_HERE"
}, }
}, }
}, }
} }
``` ```
> 🔑 Replace `YOUR_…_KEY_HERE` with your real API keys. You can remove keys you don't use. > 🔑 Replace `YOUR_…_KEY_HERE` with your real API keys. You can remove keys you don't use.
##### VSCode (`servers` + `type`) ###### VSCode (`servers` + `type`)
```jsonc ```jsonc
{ {
@@ -83,11 +91,11 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
"MISTRAL_API_KEY": "YOUR_MISTRAL_KEY_HERE", "MISTRAL_API_KEY": "YOUR_MISTRAL_KEY_HERE",
"OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE", "OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE",
"XAI_API_KEY": "YOUR_XAI_KEY_HERE", "XAI_API_KEY": "YOUR_XAI_KEY_HERE",
"AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE", "AZURE_OPENAI_API_KEY": "YOUR_AZURE_KEY_HERE"
}, },
"type": "stdio", "type": "stdio"
}, }
}, }
} }
``` ```

View File

@@ -1,4 +1,4 @@
``# Taskmaster AI Installation Guide # Taskmaster AI Installation Guide
This guide helps AI assistants install and configure Taskmaster for users in their development projects. This guide helps AI assistants install and configure Taskmaster for users in their development projects.

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

@@ -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, {
data: { success: true,
moves: results, data: {
message: `Successfully moved ${results.length} tasks` moves: results,
} message: `Successfully moved ${results.length} tasks`
}; }
},
log
);
} else { } else {
// Moving a single task // Moving a single task
return handleApiResult( return handleApiResult(

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

23506
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
{ {
"name": "task-master-ai", "name": "task-master-ai",
"version": "0.16.0-rc.0", "version": "0.16.1",
"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

@@ -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) {

View File

@@ -16,6 +16,32 @@ import {
} from '../constants/paths.js'; } from '../constants/paths.js';
import { getLoggerOrDefault } from './logger-utils.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
* @param {string} startDir - Directory to start searching from * @param {string} startDir - Directory to start searching from
@@ -80,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)
@@ -151,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
@@ -220,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
@@ -283,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}`);
@@ -326,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}`);
@@ -369,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
@@ -384,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
}; };
} }