Merge branch 'next' of https://github.com/eyaltoledano/claude-task-master into joedanz/flexible-brand-rules

# Conflicts:
#	scripts/init.js
#	scripts/modules/commands.js
#	tests/integration/roo-files-inclusion.test.js
#	tests/integration/roo-init-functionality.test.js
This commit is contained in:
Joe Danziger
2025-06-04 13:39:50 -04:00
191 changed files with 15679 additions and 19668 deletions

54
src/constants/paths.js Normal file
View File

@@ -0,0 +1,54 @@
/**
* Path constants for Task Master application
*/
// .taskmaster directory structure paths
export const TASKMASTER_DIR = '.taskmaster';
export const TASKMASTER_TASKS_DIR = '.taskmaster/tasks';
export const TASKMASTER_DOCS_DIR = '.taskmaster/docs';
export const TASKMASTER_REPORTS_DIR = '.taskmaster/reports';
export const TASKMASTER_TEMPLATES_DIR = '.taskmaster/templates';
// Task Master configuration files
export const TASKMASTER_CONFIG_FILE = '.taskmaster/config.json';
export const LEGACY_CONFIG_FILE = '.taskmasterconfig';
// Task Master report files
export const COMPLEXITY_REPORT_FILE =
'.taskmaster/reports/task-complexity-report.json';
export const LEGACY_COMPLEXITY_REPORT_FILE =
'scripts/task-complexity-report.json';
// Task Master PRD file paths
export const PRD_FILE = '.taskmaster/docs/prd.txt';
export const LEGACY_PRD_FILE = 'scripts/prd.txt';
// Task Master template files
export const EXAMPLE_PRD_FILE = '.taskmaster/templates/example_prd.txt';
export const LEGACY_EXAMPLE_PRD_FILE = 'scripts/example_prd.txt';
// Task Master task file paths
export const TASKMASTER_TASKS_FILE = '.taskmaster/tasks/tasks.json';
export const LEGACY_TASKS_FILE = 'tasks/tasks.json';
// General project files (not Task Master specific but commonly used)
export const ENV_EXAMPLE_FILE = '.env.example';
export const GITIGNORE_FILE = '.gitignore';
// Task file naming pattern
export const TASK_FILE_PREFIX = 'task_';
export const TASK_FILE_EXTENSION = '.txt';
/**
* Project markers used to identify a task-master project root
* These files/directories indicate that a directory is a Task Master project
*/
export const PROJECT_MARKERS = [
'.taskmaster', // New taskmaster directory
LEGACY_CONFIG_FILE, // .taskmasterconfig
'tasks.json', // Generic tasks file
LEGACY_TASKS_FILE, // tasks/tasks.json (legacy location)
TASKMASTER_TASKS_FILE, // .taskmaster/tasks/tasks.json (new location)
'.git', // Git repository
'.svn' // SVN repository
];

View File

@@ -33,6 +33,20 @@ export const RULE_PROFILES = [
'windsurf'
];
/**
* Centralized enum for all supported Roo agent modes
* @type {string[]}
* @description Available Roo Code IDE modes for rule generation
*/
export const ROO_MODES = [
'architect',
'ask',
'boomerang',
'code',
'debug',
'test'
];
/**
* Check if a given rule profile is valid
* @param {string} rulesProfile - The rule profile to check

31
src/utils/logger-utils.js Normal file
View 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();
}

446
src/utils/path-utils.js Normal file
View File

@@ -0,0 +1,446 @@
/**
* Path utility functions for Task Master
* Provides centralized path resolution logic for both CLI and MCP use cases
*/
import path from 'path';
import fs from 'fs';
import {
TASKMASTER_TASKS_FILE,
LEGACY_TASKS_FILE,
TASKMASTER_DOCS_DIR,
TASKMASTER_REPORTS_DIR,
COMPLEXITY_REPORT_FILE,
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
* @param {string} startDir - Directory to start searching from
* @returns {string|null} - Project root path or null if not found
*/
export function findProjectRoot(startDir = process.cwd()) {
const projectMarkers = [
'.taskmaster',
TASKMASTER_TASKS_FILE,
'tasks.json',
LEGACY_TASKS_FILE,
'.git',
'.svn',
'package.json',
'yarn.lock',
'package-lock.json',
'pnpm-lock.yaml'
];
let currentDir = path.resolve(startDir);
const rootDir = path.parse(currentDir).root;
while (currentDir !== rootDir) {
// Check if current directory contains any project markers
for (const marker of projectMarkers) {
const markerPath = path.join(currentDir, marker);
if (fs.existsSync(markerPath)) {
return currentDir;
}
}
currentDir = path.dirname(currentDir);
}
return null;
}
/**
* Find the tasks.json file path with fallback logic
* @param {string|null} explicitPath - Explicit path provided by user (highest priority)
* @param {Object|null} args - Args object from MCP args (optional)
* @param {Object|null} log - Logger object (optional)
* @returns {string|null} - Resolved tasks.json path or null if not found
*/
export function findTasksPath(explicitPath = null, args = null, log = null) {
// 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) {
const resolvedPath = path.isAbsolute(explicitPath)
? explicitPath
: path.resolve(process.cwd(), explicitPath);
if (fs.existsSync(resolvedPath)) {
logger.info?.(`Using explicit tasks path: ${resolvedPath}`);
return resolvedPath;
} else {
logger.warn?.(
`Explicit tasks path not found: ${resolvedPath}, trying fallbacks`
);
}
}
// 2. Try to get project root from args (MCP) or find it
const rawProjectRoot = args?.projectRoot || findProjectRoot();
if (!rawProjectRoot) {
logger.warn?.('Could not determine project root directory');
return null;
}
// 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)
path.join(projectRoot, LEGACY_TASKS_FILE) // tasks/tasks.json (LEGACY)
];
for (const tasksPath of possiblePaths) {
if (fs.existsSync(tasksPath)) {
logger.info?.(`Found tasks file at: ${tasksPath}`);
// Issue deprecation warning for legacy paths
if (
tasksPath.includes('tasks/tasks.json') &&
!tasksPath.includes('.taskmaster')
) {
logger.warn?.(
`⚠️ DEPRECATION WARNING: Found tasks.json in legacy location '${tasksPath}'. Please migrate to the new .taskmaster directory structure. Run 'task-master migrate' to automatically migrate your project.`
);
} else if (
tasksPath.endsWith('tasks.json') &&
!tasksPath.includes('.taskmaster') &&
!tasksPath.includes('tasks/')
) {
logger.warn?.(
`⚠️ DEPRECATION WARNING: Found tasks.json in legacy root location '${tasksPath}'. Please migrate to the new .taskmaster directory structure. Run 'task-master migrate' to automatically migrate your project.`
);
}
return tasksPath;
}
}
logger.warn?.(`No tasks.json found in project: ${projectRoot}`);
return null;
}
/**
* Find the PRD document file path with fallback logic
* @param {string|null} explicitPath - Explicit path provided by user (highest priority)
* @param {Object|null} args - Args object for MCP context (optional)
* @param {Object|null} log - Logger object (optional)
* @returns {string|null} - Resolved PRD document path or null if not found
*/
export function findPRDPath(explicitPath = null, args = null, log = null) {
const logger = getLoggerOrDefault(log);
// 1. If explicit path is provided, use it (highest priority)
if (explicitPath) {
const resolvedPath = path.isAbsolute(explicitPath)
? explicitPath
: path.resolve(process.cwd(), explicitPath);
if (fs.existsSync(resolvedPath)) {
logger.info?.(`Using explicit PRD path: ${resolvedPath}`);
return resolvedPath;
} else {
logger.warn?.(
`Explicit PRD path not found: ${resolvedPath}, trying fallbacks`
);
}
}
// 2. Try to get project root from args (MCP) or find it
const rawProjectRoot = args?.projectRoot || findProjectRoot();
if (!rawProjectRoot) {
logger.warn?.('Could not determine project root directory');
return null;
}
// 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
'' // Project root
];
const fileNames = ['PRD.md', 'prd.md', 'PRD.txt', 'prd.txt'];
for (const location of locations) {
for (const fileName of fileNames) {
const prdPath = path.join(projectRoot, location, fileName);
if (fs.existsSync(prdPath)) {
logger.info?.(`Found PRD document at: ${prdPath}`);
// Issue deprecation warning for legacy paths
if (location === 'scripts/' || location === '') {
logger.warn?.(
`⚠️ DEPRECATION WARNING: Found PRD file in legacy location '${prdPath}'. Please migrate to .taskmaster/docs/ directory. Run 'task-master migrate' to automatically migrate your project.`
);
}
return prdPath;
}
}
}
logger.warn?.(`No PRD document found in project: ${projectRoot}`);
return null;
}
/**
* Find the complexity report file path with fallback logic
* @param {string|null} explicitPath - Explicit path provided by user (highest priority)
* @param {Object|null} args - Args object for MCP context (optional)
* @param {Object|null} log - Logger object (optional)
* @returns {string|null} - Resolved complexity report path or null if not found
*/
export function findComplexityReportPath(
explicitPath = null,
args = null,
log = null
) {
const logger = getLoggerOrDefault(log);
// 1. If explicit path is provided, use it (highest priority)
if (explicitPath) {
const resolvedPath = path.isAbsolute(explicitPath)
? explicitPath
: path.resolve(process.cwd(), explicitPath);
if (fs.existsSync(resolvedPath)) {
logger.info?.(`Using explicit complexity report path: ${resolvedPath}`);
return resolvedPath;
} else {
logger.warn?.(
`Explicit complexity report path not found: ${resolvedPath}, trying fallbacks`
);
}
}
// 2. Try to get project root from args (MCP) or find it
const rawProjectRoot = args?.projectRoot || findProjectRoot();
if (!rawProjectRoot) {
logger.warn?.('Could not determine project root directory');
return null;
}
// 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
'' // Project root
];
const fileNames = ['task-complexity-report.json', 'complexity-report.json'];
for (const location of locations) {
for (const fileName of fileNames) {
const reportPath = path.join(projectRoot, location, fileName);
if (fs.existsSync(reportPath)) {
logger.info?.(`Found complexity report at: ${reportPath}`);
// Issue deprecation warning for legacy paths
if (location === 'scripts/' || location === '') {
logger.warn?.(
`⚠️ DEPRECATION WARNING: Found complexity report in legacy location '${reportPath}'. Please migrate to .taskmaster/reports/ directory. Run 'task-master migrate' to automatically migrate your project.`
);
}
return reportPath;
}
}
}
logger.warn?.(`No complexity report found in project: ${projectRoot}`);
return null;
}
/**
* Resolve output path for tasks.json (create if needed)
* @param {string|null} explicitPath - Explicit output path provided by user
* @param {Object|null} args - Args object for MCP context (optional)
* @param {Object|null} log - Logger object (optional)
* @returns {string} - Resolved output path for tasks.json
*/
export function resolveTasksOutputPath(
explicitPath = null,
args = null,
log = null
) {
const logger = getLoggerOrDefault(log);
// 1. If explicit path is provided, use it
if (explicitPath) {
const resolvedPath = path.isAbsolute(explicitPath)
? explicitPath
: path.resolve(process.cwd(), explicitPath);
logger.info?.(`Using explicit output path: ${resolvedPath}`);
return resolvedPath;
}
// 2. Try to get project root from args (MCP) or find it
const rawProjectRoot =
args?.projectRoot || findProjectRoot() || process.cwd();
// 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}`);
// Ensure the directory exists
const outputDir = path.dirname(defaultPath);
if (!fs.existsSync(outputDir)) {
logger.info?.(`Creating tasks directory: ${outputDir}`);
fs.mkdirSync(outputDir, { recursive: true });
}
return defaultPath;
}
/**
* Resolve output path for complexity report (create if needed)
* @param {string|null} explicitPath - Explicit output path provided by user
* @param {Object|null} args - Args object for MCP context (optional)
* @param {Object|null} log - Logger object (optional)
* @returns {string} - Resolved output path for complexity report
*/
export function resolveComplexityReportOutputPath(
explicitPath = null,
args = null,
log = null
) {
const logger = getLoggerOrDefault(log);
// 1. If explicit path is provided, use it
if (explicitPath) {
const resolvedPath = path.isAbsolute(explicitPath)
? explicitPath
: path.resolve(process.cwd(), explicitPath);
logger.info?.(
`Using explicit complexity report output path: ${resolvedPath}`
);
return resolvedPath;
}
// 2. Try to get project root from args (MCP) or find it
const rawProjectRoot =
args?.projectRoot || findProjectRoot() || process.cwd();
// 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}`);
// Ensure the directory exists
const outputDir = path.dirname(defaultPath);
if (!fs.existsSync(outputDir)) {
logger.info?.(`Creating reports directory: ${outputDir}`);
fs.mkdirSync(outputDir, { recursive: true });
}
return defaultPath;
}
/**
* Find the configuration file path with fallback logic
* @param {string|null} explicitPath - Explicit path provided by user (highest priority)
* @param {Object|null} args - Args object for MCP context (optional)
* @param {Object|null} log - Logger object (optional)
* @returns {string|null} - Resolved config file path or null if not found
*/
export function findConfigPath(explicitPath = null, args = null, log = null) {
const logger = getLoggerOrDefault(log);
// 1. If explicit path is provided, use it (highest priority)
if (explicitPath) {
const resolvedPath = path.isAbsolute(explicitPath)
? explicitPath
: path.resolve(process.cwd(), explicitPath);
if (fs.existsSync(resolvedPath)) {
logger.info?.(`Using explicit config path: ${resolvedPath}`);
return resolvedPath;
} else {
logger.warn?.(
`Explicit config path not found: ${resolvedPath}, trying fallbacks`
);
}
}
// 2. Try to get project root from args (MCP) or find it
const rawProjectRoot = args?.projectRoot || findProjectRoot();
if (!rawProjectRoot) {
logger.warn?.('Could not determine project root directory');
return null;
}
// 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
];
for (const configPath of possiblePaths) {
if (fs.existsSync(configPath)) {
// Issue deprecation warning for legacy paths
if (configPath?.endsWith(LEGACY_CONFIG_FILE)) {
logger.warn?.(
`⚠️ DEPRECATION WARNING: Found configuration in legacy location '${configPath}'. Please migrate to .taskmaster/config.json. Run 'task-master migrate' to automatically migrate your project.`
);
}
return configPath;
}
}
logger.warn?.(`No configuration file found in project: ${projectRoot}`);
return null;
}