Compare commits
16 Commits
v0.16.0
...
v0.16.2-rc
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cba86510d3 | ||
|
|
86ea6d1dbc | ||
|
|
a22d2a45b5 | ||
|
|
d73c8e17ec | ||
|
|
4f23751d25 | ||
|
|
7d5c028ca0 | ||
|
|
f18df6da19 | ||
|
|
1754a31372 | ||
|
|
3096ccdfb3 | ||
|
|
6464bb11e5 | ||
|
|
edaa5fe0d5 | ||
|
|
41d9dbbe6d | ||
|
|
6e0d866756 | ||
|
|
926aa61a4e | ||
|
|
9b4168bb4e | ||
|
|
ad612763ff |
7
.changeset/pink-houses-lay.md
Normal file
7
.changeset/pink-houses-lay.md
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Fix double .taskmaster directory paths in file resolution utilities
|
||||
|
||||
- Closes #636
|
||||
5
.changeset/polite-areas-shave.md
Normal file
5
.changeset/polite-areas-shave.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
"task-master-ai": patch
|
||||
---
|
||||
|
||||
Add one-click MCP server installation for Cursor
|
||||
@@ -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.`
|
||||
* **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>`)
|
||||
* `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>`)
|
||||
* `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.
|
||||
@@ -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)
|
||||
* `OPENROUTER_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):**
|
||||
* `AZURE_OPENAI_ENDPOINT`
|
||||
* `OLLAMA_BASE_URL` (Default: `http://localhost:11434/api`)
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"defaultPriority": "medium",
|
||||
"projectName": "Taskmaster",
|
||||
"ollamaBaseURL": "http://localhost:11434/api",
|
||||
"bedrockBaseURL": "https://bedrock.us-east-1.amazonaws.com",
|
||||
"userId": "1234567890",
|
||||
"azureBaseURL": "https://your-endpoint.azure.com/"
|
||||
}
|
||||
|
||||
@@ -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/"
|
||||
}
|
||||
}
|
||||
14
CHANGELOG.md
14
CHANGELOG.md
@@ -1,5 +1,19 @@
|
||||
# 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
|
||||
|
||||
30
README.md
30
README.md
@@ -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` |
|
||||
| | 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` |
|
||||
| **VS Code** | 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
|
||||
{
|
||||
@@ -58,16 +66,16 @@ MCP (Model Control Protocol) lets you run Task Master directly from your editor.
|
||||
"OPENROUTER_API_KEY": "YOUR_OPENROUTER_KEY_HERE",
|
||||
"XAI_API_KEY": "YOUR_XAI_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.
|
||||
|
||||
##### VS Code (`servers` + `type`)
|
||||
###### VS Code (`servers` + `type`)
|
||||
|
||||
```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",
|
||||
"OPENROUTER_API_KEY": "YOUR_OPENROUTER_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"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -56,20 +56,24 @@ task-master generate # Update task markd
|
||||
|
||||
```
|
||||
project/
|
||||
├── tasks/
|
||||
│ ├── tasks.json # Main task database
|
||||
│ ├── task-1.md # Individual task files
|
||||
│ └── task-2.md
|
||||
├── scripts/
|
||||
│ ├── prd.txt # Product requirements
|
||||
│ └── task-complexity-report.json
|
||||
├── .taskmaster/
|
||||
│ ├── tasks/ # Task files directory
|
||||
│ │ ├── tasks.json # Main task database
|
||||
│ │ ├── task-1.md # Individual task files
|
||||
│ │ └── task-2.md
|
||||
│ ├── docs/ # Documentation directory
|
||||
│ │ ├── 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/
|
||||
│ ├── settings.json # Claude Code configuration
|
||||
│ └── commands/ # Custom slash commands
|
||||
├── .taskmasterconfig # AI models & settings
|
||||
├── .env # API keys
|
||||
├── .mcp.json # MCP configuration
|
||||
└── CLAUDE.md # This file - auto-loaded by Claude Code
|
||||
│ ├── settings.json # Claude Code configuration
|
||||
│ └── commands/ # Custom slash commands
|
||||
├── .env # API keys
|
||||
├── .mcp.json # MCP configuration
|
||||
└── CLAUDE.md # This file - auto-loaded by Claude Code
|
||||
```
|
||||
|
||||
## MCP Integration
|
||||
@@ -384,7 +388,7 @@ These commands make AI calls and may take up to a minute:
|
||||
### File Management
|
||||
|
||||
- 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
|
||||
- Run `task-master generate` after manual changes to tasks.json
|
||||
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
"defaultPriority": "medium",
|
||||
"projectName": "Taskmaster",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -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.
|
||||
MISTRAL_API_KEY="your_mistral_key_here" # Optional, for Mistral 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.
|
||||
@@ -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:
|
||||
|
||||
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.
|
||||
- Managed using the `task-master models --setup` command or the `models` MCP tool.
|
||||
@@ -192,7 +192,7 @@ Notes:
|
||||
## AI Integration (Updated)
|
||||
|
||||
- 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.
|
||||
- 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).
|
||||
@@ -357,25 +357,25 @@ The output report structure is:
|
||||
|
||||
```json
|
||||
{
|
||||
"meta": {
|
||||
"generatedAt": "2023-06-15T12:34:56.789Z",
|
||||
"tasksAnalyzed": 20,
|
||||
"thresholdScore": 5,
|
||||
"projectName": "Your Project Name",
|
||||
"usedResearch": true
|
||||
},
|
||||
"complexityAnalysis": [
|
||||
{
|
||||
"taskId": 8,
|
||||
"taskTitle": "Develop Implementation Drift Handling",
|
||||
"complexityScore": 9.5,
|
||||
"recommendedSubtasks": 6,
|
||||
"expansionPrompt": "Create subtasks that handle detecting...",
|
||||
"reasoning": "This task requires sophisticated logic...",
|
||||
"expansionCommand": "task-master expand --id=8 --num=6 --prompt=\"Create subtasks...\" --research"
|
||||
}
|
||||
// More tasks sorted by complexity score (highest first)
|
||||
]
|
||||
"meta": {
|
||||
"generatedAt": "2023-06-15T12:34:56.789Z",
|
||||
"tasksAnalyzed": 20,
|
||||
"thresholdScore": 5,
|
||||
"projectName": "Your Project Name",
|
||||
"usedResearch": true
|
||||
},
|
||||
"complexityAnalysis": [
|
||||
{
|
||||
"taskId": 8,
|
||||
"taskTitle": "Develop Implementation Drift Handling",
|
||||
"complexityScore": 9.5,
|
||||
"recommendedSubtasks": 6,
|
||||
"expansionPrompt": "Create subtasks that handle detecting...",
|
||||
"reasoning": "This task requires sophisticated logic...",
|
||||
"expansionCommand": "task-master expand --id=8 --num=6 --prompt=\"Create subtasks...\" --research"
|
||||
}
|
||||
// More tasks sorted by complexity score (highest first)
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -28,8 +28,7 @@ export async function complexityReportDirect(args, log) {
|
||||
log.error('complexityReportDirect called without reportPath');
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' },
|
||||
fromCache: false
|
||||
error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -111,8 +110,7 @@ export async function complexityReportDirect(args, log) {
|
||||
error: {
|
||||
code: 'UNEXPECTED_ERROR',
|
||||
message: error.message
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +60,8 @@ export async function expandAllTasksDirect(args, log, context = {}) {
|
||||
useResearch,
|
||||
additionalContext,
|
||||
forceFlag,
|
||||
{ session, mcpLog, projectRoot }
|
||||
{ session, mcpLog, projectRoot },
|
||||
'json'
|
||||
);
|
||||
|
||||
// Core function now returns a summary object including the *aggregated* telemetryData
|
||||
|
||||
@@ -29,7 +29,7 @@ import { createLogWrapper } from '../../tools/utils.js';
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} context - Context object containing session
|
||||
* @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 = {}) {
|
||||
const { session } = context; // Extract session
|
||||
@@ -54,8 +54,7 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -73,8 +72,7 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
error: {
|
||||
code: 'INPUT_VALIDATION_ERROR',
|
||||
message: 'Task ID is required'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -105,8 +103,7 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
error: {
|
||||
code: 'INVALID_TASKS_FILE',
|
||||
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: {
|
||||
code: 'TASK_NOT_FOUND',
|
||||
message: `Task with ID ${taskId} not found`
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -133,8 +129,7 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
error: {
|
||||
code: 'TASK_COMPLETED',
|
||||
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,
|
||||
subtasksAdded: 0,
|
||||
hasExistingSubtasks
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -232,8 +226,7 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
subtasksAdded,
|
||||
hasExistingSubtasks,
|
||||
telemetryData: coreResult.telemetryData
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
} catch (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: {
|
||||
code: 'CORE_FUNCTION_ERROR',
|
||||
message: error.message || 'Failed to expand task'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -256,8 +248,7 @@ export async function expandTaskDirect(args, log, context = {}) {
|
||||
error: {
|
||||
code: 'CORE_FUNCTION_ERROR',
|
||||
message: error.message || 'Failed to expand task'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,8 +28,7 @@ export async function generateTaskFilesDirect(args, log) {
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage }
|
||||
};
|
||||
}
|
||||
if (!outputDir) {
|
||||
@@ -37,8 +36,7 @@ export async function generateTaskFilesDirect(args, log) {
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,8 +63,7 @@ export async function generateTaskFilesDirect(args, log) {
|
||||
log.error(`Error in generateTaskFiles: ${genError.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'GENERATE_FILES_ERROR', message: genError.message },
|
||||
fromCache: false
|
||||
error: { code: 'GENERATE_FILES_ERROR', message: genError.message }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -79,8 +76,7 @@ export async function generateTaskFilesDirect(args, log) {
|
||||
outputDir: resolvedOutputDir,
|
||||
taskFiles:
|
||||
'Individual task files have been generated in the output directory'
|
||||
},
|
||||
fromCache: false // This operation always modifies state and should never be cached
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Make sure to restore normal logging if an outer error occurs
|
||||
@@ -92,8 +88,7 @@ export async function generateTaskFilesDirect(args, log) {
|
||||
error: {
|
||||
code: 'GENERATE_TASKS_ERROR',
|
||||
message: error.message || 'Unknown error generating task files'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,8 +41,7 @@ export async function initializeProjectDirect(args, log, context = {}) {
|
||||
code: 'INVALID_TARGET_DIRECTORY',
|
||||
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
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,8 +96,8 @@ export async function initializeProjectDirect(args, log, context = {}) {
|
||||
}
|
||||
|
||||
if (success) {
|
||||
return { success: true, data: resultData, fromCache: false };
|
||||
return { success: true, data: resultData };
|
||||
} else {
|
||||
return { success: false, error: errorResult, fromCache: false };
|
||||
return { success: false, error: errorResult };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ import {
|
||||
*
|
||||
* @param {Object} args - Command arguments (now expecting tasksJsonPath explicitly).
|
||||
* @param {Object} log - Logger object.
|
||||
* @returns {Promise<Object>} - Task list result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }.
|
||||
* @returns {Promise<Object>} - Task list result { success: boolean, data?: any, error?: { code: string, message: string } }.
|
||||
*/
|
||||
export async function listTasksDirect(args, log) {
|
||||
// Destructure the explicit tasksJsonPath from args
|
||||
@@ -27,8 +27,7 @@ export async function listTasksDirect(args, log) {
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ import {
|
||||
* @param {Object} args - Command arguments
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<Object>} - Next task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
|
||||
* @returns {Promise<Object>} - Next task result { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function nextTaskDirect(args, log) {
|
||||
// Destructure expected args
|
||||
@@ -32,8 +32,7 @@ export async function nextTaskDirect(args, log) {
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -121,7 +120,7 @@ export async function nextTaskDirect(args, log) {
|
||||
// Use the caching utility
|
||||
try {
|
||||
const result = await coreNextTaskAction();
|
||||
log.info(`nextTaskDirect completed.`);
|
||||
log.info('nextTaskDirect completed.');
|
||||
return result;
|
||||
} catch (error) {
|
||||
log.error(`Unexpected error during nextTask: ${error.message}`);
|
||||
@@ -130,8 +129,7 @@ export async function nextTaskDirect(args, log) {
|
||||
error: {
|
||||
code: 'UNEXPECTED_ERROR',
|
||||
message: error.message
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,7 +77,7 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
? path.isAbsolute(outputArg)
|
||||
? outputArg
|
||||
: path.resolve(projectRoot, outputArg)
|
||||
: resolveProjectPath(TASKMASTER_TASKS_FILE, session) ||
|
||||
: resolveProjectPath(TASKMASTER_TASKS_FILE, args) ||
|
||||
path.resolve(projectRoot, TASKMASTER_TASKS_FILE);
|
||||
|
||||
// Check if input file exists
|
||||
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
|
||||
* @param {string} args.id - The ID(s) of the task(s) or subtask(s) to remove (comma-separated for multiple).
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: false }
|
||||
* @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
export async function removeTaskDirect(args, log) {
|
||||
// Destructure expected args
|
||||
@@ -35,8 +35,7 @@ export async function removeTaskDirect(args, log) {
|
||||
error: {
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -48,8 +47,7 @@ export async function removeTaskDirect(args, log) {
|
||||
error: {
|
||||
code: 'INPUT_VALIDATION_ERROR',
|
||||
message: 'Task ID is required'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -68,8 +66,7 @@ export async function removeTaskDirect(args, log) {
|
||||
error: {
|
||||
code: 'INVALID_TASKS_FILE',
|
||||
message: `No valid tasks found in ${tasksJsonPath}`
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -83,8 +80,7 @@ export async function removeTaskDirect(args, log) {
|
||||
error: {
|
||||
code: 'INVALID_TASK_ID',
|
||||
message: `The following tasks were not found: ${invalidTasks.join(', ')}`
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -133,8 +129,7 @@ export async function removeTaskDirect(args, log) {
|
||||
details: failedRemovals
|
||||
.map((r) => `${r.taskId}: ${r.error}`)
|
||||
.join('; ')
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -147,8 +142,7 @@ export async function removeTaskDirect(args, log) {
|
||||
failed: failedRemovals.length,
|
||||
results: results,
|
||||
tasksPath: tasksJsonPath
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Ensure silent mode is disabled even if an outer error occurs
|
||||
@@ -161,8 +155,7 @@ export async function removeTaskDirect(args, log) {
|
||||
error: {
|
||||
code: 'UNEXPECTED_ERROR',
|
||||
message: error.message
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,8 +29,7 @@ export async function setTaskStatusDirect(args, log) {
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -41,8 +40,7 @@ export async function setTaskStatusDirect(args, log) {
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_TASK_ID', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'MISSING_TASK_ID', message: errorMessage }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -52,8 +50,7 @@ export async function setTaskStatusDirect(args, log) {
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_STATUS', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'MISSING_STATUS', message: errorMessage }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -82,8 +79,7 @@ export async function setTaskStatusDirect(args, log) {
|
||||
taskId,
|
||||
status: newStatus,
|
||||
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
|
||||
@@ -126,8 +122,7 @@ export async function setTaskStatusDirect(args, log) {
|
||||
error: {
|
||||
code: 'SET_STATUS_ERROR',
|
||||
message: error.message || 'Unknown error setting task status'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
// ALWAYS restore normal logging in finally block
|
||||
@@ -145,8 +140,7 @@ export async function setTaskStatusDirect(args, log) {
|
||||
error: {
|
||||
code: 'SET_STATUS_ERROR',
|
||||
message: error.message || 'Unknown error setting task status'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
logWrapper.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,8 +53,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
logWrapper.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'INVALID_SUBTASK_ID', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'INVALID_SUBTASK_ID', message: errorMessage }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,8 +63,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
logWrapper.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_PROMPT', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'MISSING_PROMPT', message: errorMessage }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -77,8 +74,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'INVALID_SUBTASK_ID_TYPE', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'INVALID_SUBTASK_ID_TYPE', message: errorMessage }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -88,8 +84,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
log.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'INVALID_SUBTASK_ID_FORMAT', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'INVALID_SUBTASK_ID_FORMAT', message: errorMessage }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -128,8 +123,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
logWrapper.error(message);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'SUBTASK_NOT_FOUND', message: message },
|
||||
fromCache: false
|
||||
error: { code: 'SUBTASK_NOT_FOUND', message: message }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -146,8 +140,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
tasksPath,
|
||||
useResearch,
|
||||
telemetryData: coreResult.telemetryData
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
logWrapper.error(`Error updating subtask by ID: ${error.message}`);
|
||||
@@ -156,8 +149,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
error: {
|
||||
code: 'UPDATE_SUBTASK_CORE_ERROR',
|
||||
message: error.message || 'Unknown error updating subtask'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
if (!wasSilent && isSilentMode()) {
|
||||
@@ -174,8 +166,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
|
||||
error: {
|
||||
code: 'DIRECT_FUNCTION_SETUP_ERROR',
|
||||
message: error.message || 'Unknown setup error'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,8 +42,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
logWrapper.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'MISSING_ARGUMENT', message: errorMessage }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -54,8 +53,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
logWrapper.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_TASK_ID', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'MISSING_TASK_ID', message: errorMessage }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -65,8 +63,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
logWrapper.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'MISSING_PROMPT', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'MISSING_PROMPT', message: errorMessage }
|
||||
};
|
||||
}
|
||||
|
||||
@@ -84,8 +81,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
logWrapper.error(errorMessage);
|
||||
return {
|
||||
success: false,
|
||||
error: { code: 'INVALID_TASK_ID', message: errorMessage },
|
||||
fromCache: false
|
||||
error: { code: 'INVALID_TASK_ID', message: errorMessage }
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -137,8 +133,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
taskId: taskId,
|
||||
updated: false,
|
||||
telemetryData: coreResult?.telemetryData
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -155,8 +150,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
updated: true,
|
||||
updatedTask: coreResult.updatedTask,
|
||||
telemetryData: coreResult.telemetryData
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
logWrapper.error(`Error updating task by ID: ${error.message}`);
|
||||
@@ -165,8 +159,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
error: {
|
||||
code: 'UPDATE_TASK_CORE_ERROR',
|
||||
message: error.message || 'Unknown error updating task'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
if (!wasSilent && isSilentMode()) {
|
||||
@@ -181,8 +174,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
|
||||
error: {
|
||||
code: 'DIRECT_FUNCTION_SETUP_ERROR',
|
||||
message: error.message || 'Unknown setup error'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import {
|
||||
findTasksPath as coreFindTasksPath,
|
||||
findPRDPath as coreFindPrdPath,
|
||||
findComplexityReportPath as coreFindComplexityReportPath,
|
||||
findProjectRoot as coreFindProjectRoot
|
||||
findProjectRoot as coreFindProjectRoot,
|
||||
normalizeProjectRoot
|
||||
} from '../../../../src/utils/path-utils.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
|
||||
*/
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
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
|
||||
* @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
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* Prioritizes explicit path parameter, then uses fallback logic
|
||||
@@ -62,22 +47,27 @@ export function findComplexityReportPathCore(
|
||||
* @param {Object} [log] - Logger object to prevent console logging
|
||||
* @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
|
||||
const explicitPath = args?.file;
|
||||
const projectRoot = args?.projectRoot;
|
||||
const rawProjectRoot = args?.projectRoot;
|
||||
|
||||
// If explicit path is provided and absolute, use it directly
|
||||
if (explicitPath && path.isAbsolute(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) {
|
||||
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) {
|
||||
return coreFindTasksPath(explicitPath, { projectRoot }, log);
|
||||
}
|
||||
@@ -92,22 +82,27 @@ export function resolveTasksPath(args, log = null) {
|
||||
* @param {Object} [log] - Logger object to prevent console logging
|
||||
* @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
|
||||
const explicitPath = args?.input;
|
||||
const projectRoot = args?.projectRoot;
|
||||
const rawProjectRoot = args?.projectRoot;
|
||||
|
||||
// If explicit path is provided and absolute, use it directly
|
||||
if (explicitPath && path.isAbsolute(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) {
|
||||
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) {
|
||||
return coreFindPrdPath(explicitPath, { projectRoot }, log);
|
||||
}
|
||||
@@ -122,22 +117,27 @@ export function resolvePrdPath(args, log = null) {
|
||||
* @param {Object} [log] - Logger object to prevent console logging
|
||||
* @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
|
||||
const explicitPath = args?.complexityReport;
|
||||
const projectRoot = args?.projectRoot;
|
||||
const rawProjectRoot = args?.projectRoot;
|
||||
|
||||
// If explicit path is provided and absolute, use it directly
|
||||
if (explicitPath && path.isAbsolute(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) {
|
||||
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) {
|
||||
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');
|
||||
}
|
||||
|
||||
// Normalize the project root to prevent double .taskmaster paths
|
||||
const projectRoot = normalizeProjectRoot(args.projectRoot);
|
||||
|
||||
// If already absolute, return as-is
|
||||
if (path.isAbsolute(relativePath)) {
|
||||
return relativePath;
|
||||
}
|
||||
|
||||
// Resolve relative to projectRoot
|
||||
return path.resolve(args.projectRoot, relativePath);
|
||||
// Resolve relative to normalized projectRoot
|
||||
return path.resolve(projectRoot, relativePath);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +187,7 @@ export function findProjectRoot(startDir) {
|
||||
* @param {Object} [log] - Log function to prevent console logging
|
||||
* @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);
|
||||
}
|
||||
|
||||
@@ -194,7 +197,7 @@ export function findTasksPath(args, log = null) {
|
||||
* @param {Object} [log] - Log function to prevent console logging
|
||||
* @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);
|
||||
}
|
||||
|
||||
@@ -205,7 +208,7 @@ export function findComplexityReportPath(args, log = null) {
|
||||
* @param {Object} [log] - Logger object to prevent console logging
|
||||
* @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);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
} from './utils.js';
|
||||
import { complexityReportDirect } from '../core/task-master-core.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
|
||||
@@ -38,10 +38,18 @@ export function registerComplexityReportTool(server) {
|
||||
`Getting complexity report with args: ${JSON.stringify(args)}`
|
||||
);
|
||||
|
||||
// Use args.projectRoot directly (guaranteed by withNormalizedProjectRoot)
|
||||
const reportPath = args.file
|
||||
? path.resolve(args.projectRoot, args.file)
|
||||
: path.resolve(args.projectRoot, COMPLEXITY_REPORT_FILE);
|
||||
const pathArgs = {
|
||||
projectRoot: args.projectRoot,
|
||||
complexityReport: args.file
|
||||
};
|
||||
|
||||
const reportPath = findComplexityReportPath(pathArgs, log);
|
||||
|
||||
if (!reportPath) {
|
||||
return createErrorResponse(
|
||||
'No complexity report found. Run task-master analyze-complexity first.'
|
||||
);
|
||||
}
|
||||
|
||||
const result = await complexityReportDirect(
|
||||
{
|
||||
@@ -51,9 +59,7 @@ export function registerComplexityReportTool(server) {
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
log.info(
|
||||
`Successfully retrieved complexity report${result.fromCache ? ' (from cache)' : ''}`
|
||||
);
|
||||
log.info('Successfully retrieved complexity report');
|
||||
} else {
|
||||
log.error(
|
||||
`Failed to retrieve complexity report: ${result.error.message}`
|
||||
|
||||
@@ -116,9 +116,7 @@ export function registerShowTaskTool(server) {
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
log.info(
|
||||
`Successfully retrieved task details for ID: ${args.id}${result.fromCache ? ' (from cache)' : ''}`
|
||||
);
|
||||
log.info(`Successfully retrieved task details for ID: ${args.id}`);
|
||||
} else {
|
||||
log.error(`Failed to get task: ${result.error.message}`);
|
||||
}
|
||||
|
||||
@@ -58,7 +58,7 @@ export function registerListTasksTool(server) {
|
||||
// Resolve the path to tasks.json using new path utilities
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = resolveTasksPath(args, session);
|
||||
tasksJsonPath = resolveTasksPath(args, log);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
@@ -87,7 +87,7 @@ export function registerListTasksTool(server) {
|
||||
);
|
||||
|
||||
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');
|
||||
} catch (error) {
|
||||
|
||||
@@ -47,7 +47,6 @@ export function registerModelsTool(server) {
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('The directory of the project. Must be an absolute path.'),
|
||||
openrouter: z
|
||||
.boolean()
|
||||
|
||||
@@ -34,7 +34,6 @@ export function registerMoveTaskTool(server) {
|
||||
file: z.string().optional().describe('Custom path to tasks.json file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (typically derived from session)'
|
||||
)
|
||||
@@ -95,13 +94,16 @@ export function registerMoveTaskTool(server) {
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
moves: results,
|
||||
message: `Successfully moved ${results.length} tasks`
|
||||
}
|
||||
};
|
||||
return handleApiResult(
|
||||
{
|
||||
success: true,
|
||||
data: {
|
||||
moves: results,
|
||||
message: `Successfully moved ${results.length} tasks`
|
||||
}
|
||||
},
|
||||
log
|
||||
);
|
||||
} else {
|
||||
// Moving a single task
|
||||
return handleApiResult(
|
||||
|
||||
@@ -32,7 +32,6 @@ export function registerParsePRDTool(server) {
|
||||
.describe('Absolute path to the PRD document file (.txt, .md, etc.)'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('The directory of the project. Must be an absolute path.'),
|
||||
output: z
|
||||
.string()
|
||||
|
||||
@@ -7,6 +7,7 @@ import { spawnSync } from 'child_process';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
import { contextManager } from '../core/context-manager.js'; // Import the singleton
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// Import path utilities to ensure consistent path resolution
|
||||
import {
|
||||
@@ -14,6 +15,50 @@ import {
|
||||
PROJECT_MARKERS
|
||||
} 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
|
||||
* @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
|
||||
* @returns {Object} - Standardized MCP response object
|
||||
*/
|
||||
function handleApiResult(
|
||||
async function handleApiResult(
|
||||
result,
|
||||
log,
|
||||
errorPrefix = 'API error',
|
||||
processFunction = processMCPResponseData
|
||||
) {
|
||||
// Get version info for every response
|
||||
const versionInfo = getVersionInfo();
|
||||
|
||||
if (!result.success) {
|
||||
const errorMsg = result.error?.message || `Unknown ${errorPrefix}`;
|
||||
// Include cache status in error logs
|
||||
log.error(`${errorPrefix}: ${errorMsg}. From cache: ${result.fromCache}`); // Keep logging cache status on error
|
||||
return createErrorResponse(errorMsg);
|
||||
log.error(`${errorPrefix}: ${errorMsg}`);
|
||||
return createErrorResponse(errorMsg, versionInfo);
|
||||
}
|
||||
|
||||
// Process the result data if needed
|
||||
@@ -217,16 +264,14 @@ function handleApiResult(
|
||||
? processFunction(result.data)
|
||||
: result.data;
|
||||
|
||||
// Log success including cache status
|
||||
log.info(`Successfully completed operation. From cache: ${result.fromCache}`); // Add success log with cache status
|
||||
log.info('Successfully completed operation');
|
||||
|
||||
// Create the response payload including the fromCache flag
|
||||
// Create the response payload including version info
|
||||
const responsePayload = {
|
||||
fromCache: result.fromCache, // Get the flag from the original 'result'
|
||||
data: processedData // Nest the processed data under a 'data' key
|
||||
data: processedData,
|
||||
version: versionInfo
|
||||
};
|
||||
|
||||
// Pass this combined payload to createContentResponse
|
||||
return createContentResponse(responsePayload);
|
||||
}
|
||||
|
||||
@@ -320,8 +365,8 @@ function executeTaskMasterCommand(
|
||||
* @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 } }.
|
||||
* @param {Object} options.log - The logger instance.
|
||||
* @returns {Promise<Object>} - An object containing the result, indicating if it was from cache.
|
||||
* Format: { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
|
||||
* @returns {Promise<Object>} - An object containing the result.
|
||||
* Format: { success: boolean, data?: any, error?: { code: string, message: string } }
|
||||
*/
|
||||
async function getCachedOrExecute({ cacheKey, actionFn, log }) {
|
||||
// Check cache first
|
||||
@@ -329,11 +374,7 @@ async function getCachedOrExecute({ cacheKey, actionFn, log }) {
|
||||
|
||||
if (cachedResult !== undefined) {
|
||||
log.info(`Cache hit for key: ${cacheKey}`);
|
||||
// Return the cached data in the same structure as a fresh result
|
||||
return {
|
||||
...cachedResult, // Spread the cached result to maintain its structure
|
||||
fromCache: true // Just add the fromCache flag
|
||||
};
|
||||
return cachedResult;
|
||||
}
|
||||
|
||||
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
|
||||
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) {
|
||||
log.info(`Action successful. Caching result for key: ${cacheKey}`);
|
||||
// Cache the entire result structure (minus the fromCache flag)
|
||||
const { fromCache, ...resultToCache } = result;
|
||||
contextManager.setCachedData(cacheKey, resultToCache);
|
||||
contextManager.setCachedData(cacheKey, result);
|
||||
} else if (!result.success) {
|
||||
log.warn(
|
||||
`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,
|
||||
fromCache: false
|
||||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -460,14 +495,22 @@ function createContentResponse(content) {
|
||||
/**
|
||||
* Creates error response for tools
|
||||
* @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
|
||||
*/
|
||||
function createErrorResponse(errorMessage) {
|
||||
function createErrorResponse(errorMessage, versionInfo) {
|
||||
// Provide fallback version info if not provided
|
||||
if (!versionInfo) {
|
||||
versionInfo = getVersionInfo();
|
||||
}
|
||||
|
||||
return {
|
||||
content: [
|
||||
{
|
||||
type: 'text',
|
||||
text: `Error: ${errorMessage}`
|
||||
text: `Error: ${errorMessage}
|
||||
Version: ${versionInfo.version}
|
||||
Name: ${versionInfo.name}`
|
||||
}
|
||||
],
|
||||
isError: true
|
||||
|
||||
4
package-lock.json
generated
4
package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "task-master-ai",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "task-master-ai",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"license": "MIT WITH Commons-Clause",
|
||||
"dependencies": {
|
||||
"@ai-sdk/amazon-bedrock": "^2.2.9",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "task-master-ai",
|
||||
"version": "0.16.0",
|
||||
"version": "0.16.1",
|
||||
"description": "A task management system for ambitious AI-driven development that doesn't overwhelm and confuse Cursor.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
|
||||
@@ -509,9 +509,9 @@ function createProjectStructure(addAliases, dryRun, options) {
|
||||
replacements
|
||||
);
|
||||
|
||||
// Copy .taskmasterconfig with project name to NEW location
|
||||
// Copy config.json with project name to NEW location
|
||||
copyTemplateFile(
|
||||
'.taskmasterconfig',
|
||||
'config.json',
|
||||
path.join(targetDir, TASKMASTER_CONFIG_FILE),
|
||||
{
|
||||
...replacements
|
||||
|
||||
@@ -22,6 +22,7 @@ import {
|
||||
isApiKeySet,
|
||||
getOllamaBaseURL,
|
||||
getAzureBaseURL,
|
||||
getBedrockBaseURL,
|
||||
getVertexProjectId,
|
||||
getVertexLocation
|
||||
} 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
|
||||
baseURL = getOllamaBaseURL(effectiveProjectRoot);
|
||||
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
|
||||
|
||||
@@ -61,7 +61,8 @@ const DEFAULTS = {
|
||||
defaultSubtasks: 5,
|
||||
defaultPriority: 'medium',
|
||||
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;
|
||||
}
|
||||
|
||||
function getBedrockBaseURL(explicitRoot = null) {
|
||||
// Directly return value from config
|
||||
return getGlobalConfig(explicitRoot).bedrockBaseURL;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the Google Cloud project ID for Vertex AI from configuration
|
||||
* @param {string|null} explicitRoot - Optional explicit path to the project root.
|
||||
@@ -779,6 +785,7 @@ export {
|
||||
getProjectName,
|
||||
getOllamaBaseURL,
|
||||
getAzureBaseURL,
|
||||
getBedrockBaseURL,
|
||||
getParametersForRole,
|
||||
getUserId,
|
||||
// API Key Checkers (still relevant)
|
||||
|
||||
@@ -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}`
|
||||
);
|
||||
}
|
||||
} 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 {
|
||||
// Invalid provider hint - should not happen
|
||||
throw new Error(`Invalid provider hint received: ${providerHint}`);
|
||||
|
||||
@@ -24,7 +24,10 @@ import {
|
||||
} from './task-manager.js';
|
||||
import { getProjectName, getDefaultSubtasks } from './config-manager.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';
|
||||
|
||||
// Create a color gradient for the banner
|
||||
@@ -687,7 +690,7 @@ function displayHelp() {
|
||||
|
||||
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.dim('Managed by models cmd')}${chalk.reset('')}`
|
||||
],
|
||||
@@ -1851,7 +1854,7 @@ function displayApiKeyStatus(statusReport) {
|
||||
console.log(table.toString());
|
||||
console.log(
|
||||
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}.`
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -155,8 +155,17 @@ function log(level, ...args) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Get log level dynamically from config-manager
|
||||
const configLevel = getLogLevel() || 'info'; // Use getter
|
||||
// GUARD: Prevent circular dependency during config loading
|
||||
// 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
|
||||
const prefixes = {
|
||||
@@ -190,8 +199,17 @@ function log(level, ...args) {
|
||||
* @returns {Object|null} Parsed JSON data or null if error occurs
|
||||
*/
|
||||
function readJSON(filepath) {
|
||||
// Get debug flag dynamically from config-manager
|
||||
const isDebug = getDebugFlag();
|
||||
// GUARD: Prevent circular dependency during config loading
|
||||
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 {
|
||||
const rawData = fs.readFileSync(filepath, 'utf8');
|
||||
return JSON.parse(rawData);
|
||||
@@ -212,8 +230,17 @@ function readJSON(filepath) {
|
||||
* @param {Object} data - Data to write
|
||||
*/
|
||||
function writeJSON(filepath, data) {
|
||||
// Get debug flag dynamically from config-manager
|
||||
const isDebug = getDebugFlag();
|
||||
// GUARD: Prevent circular dependency during config loading
|
||||
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 {
|
||||
const dir = path.dirname(filepath);
|
||||
if (!fs.existsSync(dir)) {
|
||||
@@ -246,8 +273,17 @@ function sanitizePrompt(prompt) {
|
||||
* @returns {Object|null} The parsed complexity report or null if not found
|
||||
*/
|
||||
function readComplexityReport(customPath = null) {
|
||||
// Get debug flag dynamically from config-manager
|
||||
const isDebug = getDebugFlag();
|
||||
// GUARD: Prevent circular dependency during config loading
|
||||
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 {
|
||||
let reportPath;
|
||||
if (customPath) {
|
||||
|
||||
31
src/utils/logger-utils.js
Normal file
31
src/utils/logger-utils.js
Normal 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();
|
||||
}
|
||||
@@ -14,6 +14,33 @@ import {
|
||||
TASKMASTER_CONFIG_FILE,
|
||||
LEGACY_CONFIG_FILE
|
||||
} 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
|
||||
@@ -59,7 +86,8 @@ export function findProjectRoot(startDir = process.cwd()) {
|
||||
* @returns {string|null} - Resolved tasks.json path or null if not found
|
||||
*/
|
||||
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)
|
||||
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
|
||||
const projectRoot = args?.projectRoot || findProjectRoot();
|
||||
const rawProjectRoot = args?.projectRoot || findProjectRoot();
|
||||
|
||||
if (!projectRoot) {
|
||||
if (!rawProjectRoot) {
|
||||
logger.warn?.('Could not determine project root directory');
|
||||
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 = [
|
||||
path.join(projectRoot, TASKMASTER_TASKS_FILE), // .taskmaster/tasks/tasks.json (NEW)
|
||||
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
|
||||
*/
|
||||
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)
|
||||
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
|
||||
const projectRoot = args?.projectRoot || findProjectRoot();
|
||||
const rawProjectRoot = args?.projectRoot || findProjectRoot();
|
||||
|
||||
if (!projectRoot) {
|
||||
if (!rawProjectRoot) {
|
||||
logger.warn?.('Could not determine project root directory');
|
||||
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 = [
|
||||
TASKMASTER_DOCS_DIR, // .taskmaster/docs/ (NEW)
|
||||
'scripts/', // Legacy location
|
||||
@@ -199,7 +233,7 @@ export function findComplexityReportPath(
|
||||
args = null,
|
||||
log = null
|
||||
) {
|
||||
const logger = log || console;
|
||||
const logger = getLoggerOrDefault(log);
|
||||
|
||||
// 1. If explicit path is provided, use it (highest priority)
|
||||
if (explicitPath) {
|
||||
@@ -218,14 +252,17 @@ export function findComplexityReportPath(
|
||||
}
|
||||
|
||||
// 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');
|
||||
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 = [
|
||||
TASKMASTER_REPORTS_DIR, // .taskmaster/reports/ (NEW)
|
||||
'scripts/', // Legacy location
|
||||
@@ -268,7 +305,7 @@ export function resolveTasksOutputPath(
|
||||
args = null,
|
||||
log = null
|
||||
) {
|
||||
const logger = log || console;
|
||||
const logger = getLoggerOrDefault(log);
|
||||
|
||||
// 1. If explicit path is provided, use it
|
||||
if (explicitPath) {
|
||||
@@ -281,9 +318,13 @@ export function resolveTasksOutputPath(
|
||||
}
|
||||
|
||||
// 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);
|
||||
logger.info?.(`Using default output path: ${defaultPath}`);
|
||||
|
||||
@@ -309,7 +350,7 @@ export function resolveComplexityReportOutputPath(
|
||||
args = null,
|
||||
log = null
|
||||
) {
|
||||
const logger = log || console;
|
||||
const logger = getLoggerOrDefault(log);
|
||||
|
||||
// 1. If explicit path is provided, use it
|
||||
if (explicitPath) {
|
||||
@@ -324,9 +365,13 @@ export function resolveComplexityReportOutputPath(
|
||||
}
|
||||
|
||||
// 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);
|
||||
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
|
||||
*/
|
||||
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)
|
||||
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
|
||||
const projectRoot = args?.projectRoot || findProjectRoot();
|
||||
const rawProjectRoot = args?.projectRoot || findProjectRoot();
|
||||
|
||||
if (!projectRoot) {
|
||||
if (!rawProjectRoot) {
|
||||
logger.warn?.('Could not determine project root directory');
|
||||
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 = [
|
||||
path.join(projectRoot, TASKMASTER_CONFIG_FILE), // NEW 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) {
|
||||
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
|
||||
if (configPath?.endsWith(LEGACY_CONFIG_FILE)) {
|
||||
logger.warn?.(
|
||||
|
||||
@@ -253,8 +253,7 @@ describe('MCP Server Direct Functions', () => {
|
||||
error: {
|
||||
code: 'FILE_NOT_FOUND_ERROR',
|
||||
message: 'Tasks file not found'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -288,8 +287,7 @@ describe('MCP Server Direct Functions', () => {
|
||||
.length,
|
||||
pending: tasksData.filter((t) => t.status === 'pending').length
|
||||
}
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -305,8 +303,7 @@ describe('MCP Server Direct Functions', () => {
|
||||
total: tasksData.length,
|
||||
filtered: filteredTasks.length
|
||||
}
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -320,8 +317,7 @@ describe('MCP Server Direct Functions', () => {
|
||||
stats: {
|
||||
total: tasksData.length
|
||||
}
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -441,8 +437,7 @@ describe('MCP Server Direct Functions', () => {
|
||||
error: {
|
||||
code: 'INPUT_VALIDATION_ERROR',
|
||||
message: 'Task ID is required'
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -454,8 +449,7 @@ describe('MCP Server Direct Functions', () => {
|
||||
error: {
|
||||
code: 'TASK_NOT_FOUND',
|
||||
message: `Task with ID ${args.id} not found`
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -469,8 +463,7 @@ describe('MCP Server Direct Functions', () => {
|
||||
error: {
|
||||
code: 'TASK_COMPLETED',
|
||||
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,
|
||||
subtasksAdded: expandedTask.subtasks.length,
|
||||
hasExistingSubtasks: false
|
||||
},
|
||||
fromCache: false
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -43,6 +43,7 @@ const mockGetBaseUrlForRole = jest.fn();
|
||||
const mockGetAllProviders = jest.fn();
|
||||
const mockGetOllamaBaseURL = jest.fn();
|
||||
const mockGetAzureBaseURL = jest.fn();
|
||||
const mockGetBedrockBaseURL = jest.fn();
|
||||
const mockGetVertexProjectId = jest.fn();
|
||||
const mockGetVertexLocation = jest.fn();
|
||||
const mockGetAvailableModels = jest.fn();
|
||||
@@ -113,6 +114,7 @@ jest.unstable_mockModule('../../scripts/modules/config-manager.js', () => ({
|
||||
getAllProviders: mockGetAllProviders,
|
||||
getOllamaBaseURL: mockGetOllamaBaseURL,
|
||||
getAzureBaseURL: mockGetAzureBaseURL,
|
||||
getBedrockBaseURL: mockGetBedrockBaseURL,
|
||||
getVertexProjectId: mockGetVertexProjectId,
|
||||
getVertexLocation: mockGetVertexLocation,
|
||||
getMcpApiKeyStatus: mockGetMcpApiKeyStatus
|
||||
|
||||
@@ -139,7 +139,8 @@ const DEFAULT_CONFIG = {
|
||||
defaultSubtasks: 5,
|
||||
defaultPriority: 'medium',
|
||||
projectName: 'Task Master',
|
||||
ollamaBaseURL: 'http://localhost:11434/api'
|
||||
ollamaBaseURL: 'http://localhost:11434/api',
|
||||
bedrockBaseURL: 'https://bedrock.us-east-1.amazonaws.com'
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -127,6 +127,7 @@ jest.unstable_mockModule(
|
||||
getProjectName: jest.fn(() => 'Test Project'),
|
||||
getOllamaBaseURL: jest.fn(() => 'http://localhost:11434/api'),
|
||||
getAzureBaseURL: jest.fn(() => undefined),
|
||||
getBedrockBaseURL: jest.fn(() => undefined),
|
||||
getParametersForRole: jest.fn(() => ({
|
||||
maxTokens: 4000,
|
||||
temperature: 0.7
|
||||
|
||||
Reference in New Issue
Block a user