Compare commits

...

1 Commits

Author SHA1 Message Date
Ralph Khreish
c9aa9faf59 fix: projectRoot duplicate .taskmaster directory 2025-06-03 15:00:46 +02:00
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, 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(
{ {

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