Compare commits

...

4 Commits

Author SHA1 Message Date
Ralph Khreish
ee2d9921c2 chore: fix format 2025-07-11 14:13:00 +03:00
Ralph Khreish
fff2172a30 chore: fix unit tests 2025-07-11 14:06:22 +03:00
Ralph Khreish
5fa7d2ecaa chore: fix format 2025-07-11 13:43:13 +03:00
Ralph Khreish
8e9d00e03d fix: more regression bugs 2025-07-11 07:25:29 +03:00
9 changed files with 106 additions and 32 deletions

View File

@@ -1500,10 +1500,16 @@ function registerCommands(programInstance) {
.option('--tag <tag>', 'Specify tag context for task operations') .option('--tag <tag>', 'Specify tag context for task operations')
.action(async (options) => { .action(async (options) => {
// Initialize TaskMaster // Initialize TaskMaster
const taskMaster = initTaskMaster({ const initOptions = {
tasksPath: options.file || true, tasksPath: options.file || true
complexityReportPath: options.report || false };
});
// Only pass complexityReportPath if user provided a custom path
if (options.report && options.report !== COMPLEXITY_REPORT_FILE) {
initOptions.complexityReportPath = options.report;
}
const taskMaster = initTaskMaster(initOptions);
const statusFilter = options.status; const statusFilter = options.status;
const withSubtasks = options.withSubtasks || false; const withSubtasks = options.withSubtasks || false;
@@ -1690,7 +1696,7 @@ function registerCommands(programInstance) {
const outputPath = const outputPath =
options.output === COMPLEXITY_REPORT_FILE && targetTag !== 'master' options.output === COMPLEXITY_REPORT_FILE && targetTag !== 'master'
? baseOutputPath.replace('.json', `_${targetTag}.json`) ? baseOutputPath.replace('.json', `_${targetTag}.json`)
: baseOutputPath; : options.output || baseOutputPath;
console.log( console.log(
chalk.blue( chalk.blue(
@@ -1770,6 +1776,11 @@ function registerCommands(programInstance) {
) )
.option('--tag <tag>', 'Specify tag context for task operations') .option('--tag <tag>', 'Specify tag context for task operations')
.action(async (prompt, options) => { .action(async (prompt, options) => {
// Initialize TaskMaster
const taskMaster = initTaskMaster({
tasksPath: options.file || true
});
// Parameter validation // Parameter validation
if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) { if (!prompt || typeof prompt !== 'string' || prompt.trim().length === 0) {
console.error( console.error(
@@ -2211,6 +2222,8 @@ ${result.result}
tasksPath: options.file || true tasksPath: options.file || true
}); });
const projectRoot = taskMaster.getProjectRoot();
// Show current tag context // Show current tag context
displayCurrentTagIndicator( displayCurrentTagIndicator(
options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master' options.tag || getCurrentTag(taskMaster.getProjectRoot()) || 'master'
@@ -3462,6 +3475,9 @@ Examples:
const taskMaster = initTaskMaster({ const taskMaster = initTaskMaster({
tasksPath: options.file || false tasksPath: options.file || false
}); });
const projectRoot = taskMaster.getProjectRoot();
// Validate flags: cannot use multiple provider flags simultaneously // Validate flags: cannot use multiple provider flags simultaneously
const providerFlags = [ const providerFlags = [
options.openrouter, options.openrouter,

View File

@@ -4,7 +4,10 @@ import chalk from 'chalk';
import { z } from 'zod'; import { z } from 'zod';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import { log, findProjectRoot, resolveEnvVariable, isEmpty } from './utils.js'; import { log, findProjectRoot, resolveEnvVariable, isEmpty } from './utils.js';
import { LEGACY_CONFIG_FILE } from '../../src/constants/paths.js'; import {
LEGACY_CONFIG_FILE,
TASKMASTER_DIR
} from '../../src/constants/paths.js';
import { findConfigPath } from '../../src/utils/path-utils.js'; import { findConfigPath } from '../../src/utils/path-utils.js';
import { import {
VALIDATED_PROVIDERS, VALIDATED_PROVIDERS,
@@ -99,17 +102,30 @@ function _loadAndValidateConfig(explicitRoot = null) {
if (rootToUse) { if (rootToUse) {
configSource = `found root (${rootToUse})`; configSource = `found root (${rootToUse})`;
} else { } else {
// No root found, return defaults immediately // No root found, use current working directory as fallback
return defaults; // This prevents infinite loops during initialization
rootToUse = process.cwd();
configSource = `current directory (${rootToUse}) - no project markers found`;
} }
} }
// ---> End find project root logic <--- // ---> End find project root logic <---
// --- Find configuration file using centralized path utility --- // --- Find configuration file ---
const configPath = findConfigPath(null, { projectRoot: rootToUse }); let configPath = null;
let config = { ...defaults }; // Start with a deep copy of defaults let config = { ...defaults }; // Start with a deep copy of defaults
let configExists = false; let configExists = false;
// During initialization (no project markers), skip config file search entirely
const hasProjectMarkers =
fs.existsSync(path.join(rootToUse, TASKMASTER_DIR)) ||
fs.existsSync(path.join(rootToUse, LEGACY_CONFIG_FILE));
if (hasProjectMarkers) {
// Only try to find config if we have project markers
// This prevents the repeated warnings during init
configPath = findConfigPath(null, { projectRoot: rootToUse });
}
if (configPath) { if (configPath) {
configExists = true; configExists = true;
const isLegacy = configPath.endsWith(LEGACY_CONFIG_FILE); const isLegacy = configPath.endsWith(LEGACY_CONFIG_FILE);
@@ -199,12 +215,23 @@ function _loadAndValidateConfig(explicitRoot = null) {
) )
); );
} else { } else {
// Don't warn about missing config during initialization
// Only warn if this looks like an existing project (has .taskmaster dir or legacy config marker)
const hasTaskmasterDir = fs.existsSync(
path.join(rootToUse, TASKMASTER_DIR)
);
const hasLegacyMarker = fs.existsSync(
path.join(rootToUse, LEGACY_CONFIG_FILE)
);
if (hasTaskmasterDir || hasLegacyMarker) {
console.warn( console.warn(
chalk.yellow( chalk.yellow(
`Warning: Configuration file not found at derived root (${rootToUse}). Using defaults.` `Warning: Configuration file not found at derived root (${rootToUse}). Using defaults.`
) )
); );
} }
}
// Keep config as defaults // Keep config as defaults
config = { ...defaults }; config = { ...defaults };
configSource = `defaults (no config file found at ${rootToUse})`; configSource = `defaults (no config file found at ${rootToUse})`;

View File

@@ -469,7 +469,7 @@ async function expandTask(
complexityReasoningContext: complexityReasoningContext, complexityReasoningContext: complexityReasoningContext,
gatheredContext: gatheredContext, gatheredContext: gatheredContext,
useResearch: useResearch, useResearch: useResearch,
expansionPrompt: taskAnalysis?.expansionPrompt || null expansionPrompt: taskAnalysis?.expansionPrompt || undefined
}; };
let variantKey = 'default'; let variantKey = 'default';

View File

@@ -205,12 +205,10 @@ async function performResearch(
} }
}; };
// Select variant based on detail level // Load prompts - the research template handles detail level internally
const variantKey = detailLevel; // 'low', 'medium', or 'high'
const { systemPrompt, userPrompt } = await promptManager.loadPrompt( const { systemPrompt, userPrompt } = await promptManager.loadPrompt(
'research', 'research',
promptParams, promptParams
variantKey
); );
// Count tokens for system and user prompts // Count tokens for system and user prompts

View File

@@ -214,7 +214,7 @@ async function updateSubtaskById(
title: parentTask.subtasks[subtaskIndex - 1].title, title: parentTask.subtasks[subtaskIndex - 1].title,
status: parentTask.subtasks[subtaskIndex - 1].status status: parentTask.subtasks[subtaskIndex - 1].status
} }
: null; : undefined;
const nextSubtask = const nextSubtask =
subtaskIndex < parentTask.subtasks.length - 1 subtaskIndex < parentTask.subtasks.length - 1
? { ? {
@@ -222,7 +222,7 @@ async function updateSubtaskById(
title: parentTask.subtasks[subtaskIndex + 1].title, title: parentTask.subtasks[subtaskIndex + 1].title,
status: parentTask.subtasks[subtaskIndex + 1].status status: parentTask.subtasks[subtaskIndex + 1].status
} }
: null; : undefined;
// Build prompts using PromptManager // Build prompts using PromptManager
const promptManager = getPromptManager(); const promptManager = getPromptManager();

View File

@@ -218,7 +218,16 @@ export function initTaskMaster(overrides = {}) {
); );
} }
// Remaining paths - only resolve if key exists in overrides // Always set default paths first
// These can be overridden below if needed
paths.configPath = path.join(paths.projectRoot, TASKMASTER_CONFIG_FILE);
paths.statePath = path.join(
paths.taskMasterDir || path.join(paths.projectRoot, TASKMASTER_DIR),
'state.json'
);
paths.tasksPath = path.join(paths.projectRoot, TASKMASTER_TASKS_FILE);
// Handle overrides - only validate/resolve if explicitly provided
if ('configPath' in overrides) { if ('configPath' in overrides) {
paths.configPath = resolvePath( paths.configPath = resolvePath(
'config file', 'config file',

View File

@@ -557,7 +557,10 @@ describe('getConfig Tests', () => {
// Assert // Assert
expect(config).toEqual(DEFAULT_CONFIG); expect(config).toEqual(DEFAULT_CONFIG);
expect(mockFindProjectRoot).not.toHaveBeenCalled(); // Explicit root provided expect(mockFindProjectRoot).not.toHaveBeenCalled(); // Explicit root provided
expect(fsExistsSyncSpy).toHaveBeenCalledWith(MOCK_CONFIG_PATH); // The implementation checks for .taskmaster directory first
expect(fsExistsSyncSpy).toHaveBeenCalledWith(
path.join(MOCK_PROJECT_ROOT, '.taskmaster')
);
expect(fsReadFileSyncSpy).not.toHaveBeenCalled(); // No read if file doesn't exist expect(fsReadFileSyncSpy).not.toHaveBeenCalled(); // No read if file doesn't exist
expect(consoleWarnSpy).toHaveBeenCalledWith( expect(consoleWarnSpy).toHaveBeenCalledWith(
expect.stringContaining('not found at provided project root') expect.stringContaining('not found at provided project root')

View File

@@ -184,7 +184,7 @@ jest.unstable_mockModule(
); );
// Import the mocked modules // Import the mocked modules
const { readJSON, writeJSON, log, CONFIG } = await import( const { readJSON, writeJSON, log, CONFIG, findTaskById } = await import(
'../../../../../scripts/modules/utils.js' '../../../../../scripts/modules/utils.js'
); );
@@ -265,6 +265,13 @@ describe('analyzeTaskComplexity', () => {
_rawTaggedData: sampleTasks _rawTaggedData: sampleTasks
}; };
}); });
// Mock findTaskById to return the expected structure
findTaskById.mockImplementation((tasks, taskId) => {
const task = tasks?.find((t) => t.id === parseInt(taskId));
return { task: task || null, originalSubtaskCount: null };
});
generateTextService.mockResolvedValue(sampleApiResponse); generateTextService.mockResolvedValue(sampleApiResponse);
}); });

View File

@@ -248,7 +248,7 @@ describe('initTaskMaster', () => {
expect(taskMaster.getTasksPath()).toBeNull(); expect(taskMaster.getTasksPath()).toBeNull();
}); });
test('should return null when optional files not specified in overrides', () => { test('should return default paths when optional files not specified in overrides', () => {
// Arrange - Remove all optional files // Arrange - Remove all optional files
fs.unlinkSync(tasksPath); fs.unlinkSync(tasksPath);
fs.unlinkSync(configPath); fs.unlinkSync(configPath);
@@ -257,10 +257,16 @@ describe('initTaskMaster', () => {
// Act - Don't specify any optional paths // Act - Don't specify any optional paths
const taskMaster = initTaskMaster({}); const taskMaster = initTaskMaster({});
// Assert // Assert - Should return absolute paths with default locations
expect(taskMaster.getTasksPath()).toBeUndefined(); expect(taskMaster.getTasksPath()).toBe(
expect(taskMaster.getConfigPath()).toBeUndefined(); path.join(tempDir, TASKMASTER_TASKS_FILE)
expect(taskMaster.getStatePath()).toBeUndefined(); );
expect(taskMaster.getConfigPath()).toBe(
path.join(tempDir, TASKMASTER_CONFIG_FILE)
);
expect(taskMaster.getStatePath()).toBe(
path.join(tempDir, TASKMASTER_DIR, 'state.json')
);
}); });
}); });
@@ -415,11 +421,19 @@ describe('initTaskMaster', () => {
// Assert // Assert
expect(taskMaster.getProjectRoot()).toBe(tempDir); expect(taskMaster.getProjectRoot()).toBe(tempDir);
expect(taskMaster.getTaskMasterDir()).toBe(taskMasterDir); expect(taskMaster.getTaskMasterDir()).toBe(taskMasterDir);
expect(taskMaster.getTasksPath()).toBeUndefined(); // Default paths are always set for tasks, config, and state
expect(taskMaster.getTasksPath()).toBe(
path.join(tempDir, TASKMASTER_TASKS_FILE)
);
expect(taskMaster.getConfigPath()).toBe(
path.join(tempDir, TASKMASTER_CONFIG_FILE)
);
expect(taskMaster.getStatePath()).toBe(
path.join(taskMasterDir, 'state.json')
);
// PRD and complexity report paths are undefined when not provided
expect(taskMaster.getPrdPath()).toBeUndefined(); expect(taskMaster.getPrdPath()).toBeUndefined();
expect(taskMaster.getComplexityReportPath()).toBeUndefined(); expect(taskMaster.getComplexityReportPath()).toBeUndefined();
expect(taskMaster.getConfigPath()).toBeUndefined();
expect(taskMaster.getStatePath()).toBeUndefined();
}); });
}); });
}); });