Compare commits

..

11 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
9 changed files with 141 additions and 48 deletions

View File

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

View File

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

View File

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

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

@@ -3,7 +3,8 @@ 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';
@@ -49,19 +50,24 @@ export function findPrdPath(explicitPath, args = null, log = silentLogger) {
export function resolveTasksPath(args, log = silentLogger) { 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);
} }
@@ -79,19 +85,24 @@ export function resolveTasksPath(args, log = silentLogger) {
export function resolvePrdPath(args, log = silentLogger) { 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);
} }
@@ -109,19 +120,24 @@ export function resolvePrdPath(args, log = silentLogger) {
export function resolveComplexityReportPath(args, log = silentLogger) { 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);
} }
@@ -142,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);
} }
/** /**

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(
{ {

4
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{ {
"name": "task-master-ai", "name": "task-master-ai",
"version": "0.16.0", "version": "0.16.1",
"lockfileVersion": 3, "lockfileVersion": 3,
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"name": "task-master-ai", "name": "task-master-ai",
"version": "0.16.0", "version": "0.16.1",
"license": "MIT WITH Commons-Clause", "license": "MIT WITH Commons-Clause",
"dependencies": { "dependencies": {
"@ai-sdk/amazon-bedrock": "^2.2.9", "@ai-sdk/amazon-bedrock": "^2.2.9",

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