fix: projectRoot duplicate .taskmaster directory (#655)

This commit is contained in:
Ralph Khreish
2025-06-03 15:12:50 +02:00
committed by GitHub
parent 9b4168bb4e
commit edaa5fe0d5
4 changed files with 113 additions and 33 deletions

View File

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

View File

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

View File

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

View File

@@ -16,6 +16,32 @@ import {
} 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
* @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
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)
@@ -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
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
@@ -220,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
@@ -283,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}`);
@@ -326,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}`);
@@ -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
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