fix: replace tool parameter inputs with root directory paths (#147)

* wip: replace tool parameter inputs with root directory paths

* fix: moved path resolving responsibility to tools

- made path in parameters to optional for AI
- internalised path resolving using session roots

* chore: update package-lock.json

* chore: fix regressions and fix CI

* fix: make projectRoot required

* fix: add-task tool

* fix: updateTask tool

* fix: remove reportProgress

* chore: cleanup

* fix: expand-task tool

* chore: remove usless logs

* fix: dependency manager logging in mcp server
This commit is contained in:
Ralph Khreish
2025-04-11 18:57:43 +02:00
committed by GitHub
parent 30e6d47577
commit d3d9dc6ebe
51 changed files with 9476 additions and 8918 deletions

View File

@@ -4,3 +4,4 @@ coverage
.changeset
tasks
package-lock.json
tests/fixture/*.json

View File

@@ -4,7 +4,6 @@
*/
import { addDependency } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -14,19 +13,32 @@ import {
* Direct function wrapper for addDependency with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string|number} args.id - Task ID to add dependency to
* @param {string|number} args.dependsOn - Task ID that will become a dependency
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Result object with success status and data/error information
*/
export async function addDependencyDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id, dependsOn } = args;
try {
log.info(`Adding dependency with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('addDependencyDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Validate required parameters
if (!args.id) {
if (!id) {
return {
success: false,
error: {
@@ -36,7 +48,7 @@ export async function addDependencyDirect(args, log) {
};
}
if (!args.dependsOn) {
if (!dependsOn) {
return {
success: false,
error: {
@@ -46,18 +58,16 @@ export async function addDependencyDirect(args, log) {
};
}
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log);
// Use provided path
const tasksPath = tasksJsonPath;
// Format IDs for the core function
const taskId =
args.id.includes && args.id.includes('.')
? args.id
: parseInt(args.id, 10);
id && id.includes && id.includes('.') ? id : parseInt(id, 10);
const dependencyId =
args.dependsOn.includes && args.dependsOn.includes('.')
? args.dependsOn
: parseInt(args.dependsOn, 10);
dependsOn && dependsOn.includes && dependsOn.includes('.')
? dependsOn
: parseInt(dependsOn, 10);
log.info(
`Adding dependency: task ${taskId} will depend on ${dependencyId}`
@@ -66,7 +76,7 @@ export async function addDependencyDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function
// Call the core function using the provided path
await addDependency(tasksPath, taskId, dependencyId);
// Restore normal logging

View File

@@ -3,7 +3,6 @@
*/
import { addSubtask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -12,6 +11,7 @@ import {
/**
* Add a subtask to an existing task
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - Parent task ID
* @param {string} [args.taskId] - Existing task ID to convert to subtask (optional)
* @param {string} [args.title] - Title for new subtask (when creating a new subtask)
@@ -19,17 +19,39 @@ import {
* @param {string} [args.details] - Implementation details for new subtask
* @param {string} [args.status] - Status for new subtask (default: 'pending')
* @param {string} [args.dependencies] - Comma-separated list of dependency IDs
* @param {string} [args.file] - Path to the tasks file
* @param {boolean} [args.skipGenerate] - Skip regenerating task files
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: string}>}
*/
export async function addSubtaskDirect(args, log) {
// Destructure expected args
const {
tasksJsonPath,
id,
taskId,
title,
description,
details,
status,
dependencies: dependenciesStr,
skipGenerate
} = args;
try {
log.info(`Adding subtask with args: ${JSON.stringify(args)}`);
if (!args.id) {
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('addSubtaskDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
if (!id) {
return {
success: false,
error: {
@@ -40,7 +62,7 @@ export async function addSubtaskDirect(args, log) {
}
// Either taskId or title must be provided
if (!args.taskId && !args.title) {
if (!taskId && !title) {
return {
success: false,
error: {
@@ -50,26 +72,26 @@ export async function addSubtaskDirect(args, log) {
};
}
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log);
// Use provided path
const tasksPath = tasksJsonPath;
// Parse dependencies if provided
let dependencies = [];
if (args.dependencies) {
dependencies = args.dependencies.split(',').map((id) => {
if (dependenciesStr) {
dependencies = dependenciesStr.split(',').map((depId) => {
// Handle both regular IDs and dot notation
return id.includes('.') ? id.trim() : parseInt(id.trim(), 10);
return depId.includes('.') ? depId.trim() : parseInt(depId.trim(), 10);
});
}
// Convert existingTaskId to a number if provided
const existingTaskId = args.taskId ? parseInt(args.taskId, 10) : null;
const existingTaskId = taskId ? parseInt(taskId, 10) : null;
// Convert parent ID to a number
const parentId = parseInt(args.id, 10);
const parentId = parseInt(id, 10);
// Determine if we should generate files
const generateFiles = !args.skipGenerate;
const generateFiles = !skipGenerate;
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
@@ -101,10 +123,10 @@ export async function addSubtaskDirect(args, log) {
log.info(`Creating new subtask for parent task ${parentId}`);
const newSubtaskData = {
title: args.title,
description: args.description || '',
details: args.details || '',
status: args.status || 'pending',
title: title,
description: description || '',
details: details || '',
status: status || 'pending',
dependencies: dependencies
};

View File

@@ -4,7 +4,6 @@
*/
import { addTask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -38,12 +37,27 @@ import {
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
*/
export async function addTaskDirect(args, log, context = {}) {
// Destructure expected args
const { tasksJsonPath, prompt, dependencies, priority, research } = args;
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('addTaskDirect called without tasksJsonPath');
disableSilentMode(); // Disable before returning
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Use provided path
const tasksPath = tasksJsonPath;
// Check if this is manual task creation or AI-driven task creation
const isManualCreation = args.title && args.description;
@@ -65,15 +79,15 @@ export async function addTaskDirect(args, log, context = {}) {
}
// Extract and prepare parameters
const prompt = args.prompt;
const dependencies = Array.isArray(args.dependencies)
? args.dependencies
: args.dependencies
? String(args.dependencies)
const taskPrompt = prompt;
const taskDependencies = Array.isArray(dependencies)
? dependencies
: dependencies
? String(dependencies)
.split(',')
.map((id) => parseInt(id.trim(), 10))
: [];
const priority = args.priority || 'medium';
const taskPriority = priority || 'medium';
// Extract context parameters for advanced functionality
const { session } = context;
@@ -90,14 +104,14 @@ export async function addTaskDirect(args, log, context = {}) {
};
log.info(
`Adding new task manually with title: "${args.title}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`
`Adding new task manually with title: "${args.title}", dependencies: [${taskDependencies.join(', ')}], priority: ${priority}`
);
// Call the addTask function with manual task data
const newTaskId = await addTask(
tasksPath,
null, // No prompt needed for manual creation
dependencies,
taskDependencies,
priority,
{
mcpLog: log,
@@ -121,7 +135,7 @@ export async function addTaskDirect(args, log, context = {}) {
} else {
// AI-driven task creation
log.info(
`Adding new task with prompt: "${prompt}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`
`Adding new task with prompt: "${prompt}", dependencies: [${taskDependencies.join(', ')}], priority: ${priority}`
);
// Initialize AI client with session environment
@@ -207,7 +221,7 @@ export async function addTaskDirect(args, log, context = {}) {
const newTaskId = await addTask(
tasksPath,
prompt,
dependencies,
taskDependencies,
priority,
{
mcpLog: log,

View File

@@ -3,7 +3,6 @@
*/
import { analyzeTaskComplexity } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode,
@@ -16,45 +15,60 @@ import path from 'path';
/**
* Analyze task complexity and generate recommendations
* @param {Object} args - Function arguments
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.output] - Output file path for the report
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.outputPath - Explicit absolute path to save the report.
* @param {string} [args.model] - LLM model to use for analysis
* @param {string|number} [args.threshold] - Minimum complexity score to recommend expansion (1-10)
* @param {boolean} [args.research] - Use Perplexity AI for research-backed complexity analysis
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @param {Object} [context={}] - Context object containing session data
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function analyzeTaskComplexityDirect(args, log, context = {}) {
const { session } = context; // Only extract session, not reportProgress
// Destructure expected args
const { tasksJsonPath, outputPath, model, threshold, research } = args;
try {
log.info(`Analyzing task complexity with args: ${JSON.stringify(args)}`);
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log);
// Determine output path
let outputPath = args.output || 'scripts/task-complexity-report.json';
if (!path.isAbsolute(outputPath) && args.projectRoot) {
outputPath = path.join(args.projectRoot, outputPath);
// Check if required paths were provided
if (!tasksJsonPath) {
log.error('analyzeTaskComplexityDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
if (!outputPath) {
log.error('analyzeTaskComplexityDirect called without outputPath');
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: 'outputPath is required' }
};
}
log.info(`Analyzing task complexity from: ${tasksPath}`);
log.info(`Output report will be saved to: ${outputPath}`);
// Use the provided paths
const tasksPath = tasksJsonPath;
const resolvedOutputPath = outputPath;
if (args.research) {
log.info(`Analyzing task complexity from: ${tasksPath}`);
log.info(`Output report will be saved to: ${resolvedOutputPath}`);
if (research) {
log.info('Using Perplexity AI for research-backed complexity analysis');
}
// Create options object for analyzeTaskComplexity
// Create options object for analyzeTaskComplexity using provided paths
const options = {
file: tasksPath,
output: outputPath,
model: args.model,
threshold: args.threshold,
research: args.research === true
output: resolvedOutputPath,
model: model,
threshold: threshold,
research: research === true
};
// Enable silent mode to prevent console logs from interfering with JSON response
@@ -95,7 +109,7 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
}
// Verify the report file was created
if (!fs.existsSync(outputPath)) {
if (!fs.existsSync(resolvedOutputPath)) {
return {
success: false,
error: {
@@ -108,7 +122,7 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
// Read the report file
let report;
try {
report = JSON.parse(fs.readFileSync(outputPath, 'utf8'));
report = JSON.parse(fs.readFileSync(resolvedOutputPath, 'utf8'));
// Important: Handle different report formats
// The core function might return an array or an object with a complexityAnalysis property
@@ -130,8 +144,8 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
return {
success: true,
data: {
message: `Task complexity analysis complete. Report saved to ${outputPath}`,
reportPath: outputPath,
message: `Task complexity analysis complete. Report saved to ${resolvedOutputPath}`,
reportPath: resolvedOutputPath,
reportSummary: {
taskCount: analysisArray.length,
highComplexityTasks,

View File

@@ -3,7 +3,6 @@
*/
import { clearSubtasks } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -13,19 +12,32 @@ import fs from 'fs';
/**
* Clear subtasks from specified tasks
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} [args.id] - Task IDs (comma-separated) to clear subtasks from
* @param {boolean} [args.all] - Clear subtasks from all tasks
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function clearSubtasksDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id, all } = args;
try {
log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('clearSubtasksDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Either id or all must be provided
if (!args.id && !args.all) {
if (!id && !all) {
return {
success: false,
error: {
@@ -36,8 +48,8 @@ export async function clearSubtasksDirect(args, log) {
};
}
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log);
// Use provided path
const tasksPath = tasksJsonPath;
// Check if tasks.json exists
if (!fs.existsSync(tasksPath)) {
@@ -53,7 +65,7 @@ export async function clearSubtasksDirect(args, log) {
let taskIds;
// If all is specified, get all task IDs
if (args.all) {
if (all) {
log.info('Clearing subtasks from all tasks');
const data = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
if (!data || !data.tasks || data.tasks.length === 0) {
@@ -68,7 +80,7 @@ export async function clearSubtasksDirect(args, log) {
taskIds = data.tasks.map((t) => t.id).join(',');
} else {
// Use the provided task IDs
taskIds = args.id;
taskIds = id;
}
log.info(`Clearing subtasks from tasks: ${taskIds}`);

View File

@@ -8,37 +8,34 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import path from 'path';
/**
* Direct function wrapper for displaying the complexity report with error handling and caching.
*
* @param {Object} args - Command arguments containing file path option
* @param {Object} args - Command arguments containing reportPath.
* @param {string} args.reportPath - Explicit path to the complexity report file.
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Result object with success status and data/error information
*/
export async function complexityReportDirect(args, log) {
// Destructure expected args
const { reportPath } = args;
try {
log.info(`Getting complexity report with args: ${JSON.stringify(args)}`);
// Get tasks file path to determine project root for the default report location
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log);
} catch (error) {
log.warn(
`Tasks file not found, using current directory: ${error.message}`
);
// Continue with default or specified report path
// Check if reportPath was provided
if (!reportPath) {
log.error('complexityReportDirect called without reportPath');
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: 'reportPath is required' },
fromCache: false
};
}
// Get report file path from args or use default
const reportPath =
args.file ||
path.join(process.cwd(), 'scripts', 'task-complexity-report.json');
// Use the provided report path
log.info(`Looking for complexity report at: ${reportPath}`);
// Generate cache key based on report path

View File

@@ -8,7 +8,6 @@ import {
disableSilentMode,
isSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import { getAnthropicClientForMCP } from '../utils/ai-client-utils.js';
import path from 'path';
import fs from 'fs';
@@ -16,34 +15,51 @@ import fs from 'fs';
/**
* Expand all pending tasks with subtasks
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {number|string} [args.num] - Number of subtasks to generate
* @param {boolean} [args.research] - Enable Perplexity AI for research-backed subtask generation
* @param {string} [args.prompt] - Additional context to guide subtask generation
* @param {boolean} [args.force] - Force regeneration of subtasks for tasks that already have them
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @param {Object} context - Context object containing session
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function expandAllTasksDirect(args, log, context = {}) {
const { session } = context; // Only extract session, not reportProgress
// Destructure expected args
const { tasksJsonPath, num, research, prompt, force } = args;
try {
log.info(`Expanding all tasks with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('expandAllTasksDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Enable silent mode early to prevent any console output
enableSilentMode();
try {
// Find the tasks.json path
// Remove internal path finding
/*
const tasksPath = findTasksJsonPath(args, log);
*/
// Use provided path
const tasksPath = tasksJsonPath;
// Parse parameters
const numSubtasks = args.num ? parseInt(args.num, 10) : undefined;
const useResearch = args.research === true;
const additionalContext = args.prompt || '';
const forceFlag = args.force === true;
const numSubtasks = num ? parseInt(num, 10) : undefined;
const useResearch = research === true;
const additionalContext = prompt || '';
const forceFlag = force === true;
log.info(
`Expanding all tasks with ${numSubtasks || 'default'} subtasks each...`

View File

@@ -11,7 +11,6 @@ import {
disableSilentMode,
isSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
getAnthropicClientForMCP,
getModelConfig
@@ -23,12 +22,20 @@ import fs from 'fs';
* Direct function wrapper for expanding a task into subtasks with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - The ID of the task to expand.
* @param {number|string} [args.num] - Number of subtasks to generate.
* @param {boolean} [args.research] - Enable Perplexity AI for research-backed subtask generation.
* @param {string} [args.prompt] - Additional context to guide subtask generation.
* @param {boolean} [args.force] - Force expansion even if subtasks exist.
* @param {Object} log - Logger object
* @param {Object} context - Context object containing session and reportProgress
* @returns {Promise<Object>} - Task expansion result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
*/
export async function expandTaskDirect(args, log, context = {}) {
const { session } = context;
// Destructure expected args
const { tasksJsonPath, id, num, research, prompt, force } = args;
// Log session root data for debugging
log.info(
@@ -40,48 +47,26 @@ export async function expandTaskDirect(args, log, context = {}) {
})}`
);
let tasksPath;
try {
// If a direct file path is provided, use it directly
if (args.file && fs.existsSync(args.file)) {
log.info(
`[expandTaskDirect] Using explicitly provided tasks file: ${args.file}`
);
tasksPath = args.file;
} else {
// Find the tasks path through standard logic
log.info(
`[expandTaskDirect] No direct file path provided or file not found at ${args.file}, searching using findTasksJsonPath`
);
tasksPath = findTasksJsonPath(args, log);
}
} catch (error) {
log.error(
`[expandTaskDirect] Error during tasksPath determination: ${error.message}`
);
// Include session roots information in error
const sessionRootsInfo = session
? `\nSession.roots: ${JSON.stringify(session.roots)}\n` +
`Current Working Directory: ${process.cwd()}\n` +
`Args.projectRoot: ${args.projectRoot}\n` +
`Args.file: ${args.file}\n`
: '\nSession object not available';
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('expandTaskDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'FILE_NOT_FOUND_ERROR',
message: `Error determining tasksPath: ${error.message}${sessionRootsInfo}`
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
},
fromCache: false
};
}
log.info(`[expandTaskDirect] Determined tasksPath: ${tasksPath}`);
// Use provided path
const tasksPath = tasksJsonPath;
log.info(`[expandTaskDirect] Using tasksPath: ${tasksPath}`);
// Validate task ID
const taskId = args.id ? parseInt(args.id, 10) : null;
const taskId = id ? parseInt(id, 10) : null;
if (!taskId) {
log.error('Task ID is required');
return {
@@ -95,9 +80,10 @@ export async function expandTaskDirect(args, log, context = {}) {
}
// Process other parameters
const numSubtasks = args.num ? parseInt(args.num, 10) : undefined;
const useResearch = args.research === true;
const additionalContext = args.prompt || '';
const numSubtasks = num ? parseInt(num, 10) : undefined;
const useResearch = research === true;
const additionalContext = prompt || '';
const forceFlag = force === true;
// Initialize AI client if needed (for expandTask function)
try {
@@ -172,15 +158,16 @@ export async function expandTaskDirect(args, log, context = {}) {
};
}
// Check for existing subtasks
// Check for existing subtasks and force flag
const hasExistingSubtasks = task.subtasks && task.subtasks.length > 0;
// If the task already has subtasks, just return it (matching core behavior)
if (hasExistingSubtasks) {
log.info(`Task ${taskId} already has ${task.subtasks.length} subtasks`);
if (hasExistingSubtasks && !forceFlag) {
log.info(
`Task ${taskId} already has ${task.subtasks.length} subtasks. Use --force to overwrite.`
);
return {
success: true,
data: {
message: `Task ${taskId} already has subtasks. Expansion skipped.`,
task,
subtasksAdded: 0,
hasExistingSubtasks
@@ -189,6 +176,14 @@ export async function expandTaskDirect(args, log, context = {}) {
};
}
// If force flag is set, clear existing subtasks
if (hasExistingSubtasks && forceFlag) {
log.info(
`Force flag set. Clearing existing subtasks for task ${taskId}.`
);
task.subtasks = [];
}
// Keep a copy of the task before modification
const originalTask = JSON.parse(JSON.stringify(task));

View File

@@ -3,7 +3,6 @@
*/
import { fixDependenciesCommand } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -13,17 +12,30 @@ import fs from 'fs';
/**
* Fix invalid dependencies in tasks.json automatically
* @param {Object} args - Function arguments
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function fixDependenciesDirect(args, log) {
// Destructure expected args
const { tasksJsonPath } = args;
try {
log.info(`Fixing invalid dependencies in tasks...`);
log.info(`Fixing invalid dependencies in tasks: ${tasksJsonPath}`);
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('fixDependenciesDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Use provided path
const tasksPath = tasksJsonPath;
// Verify the file exists
if (!fs.existsSync(tasksPath)) {
@@ -39,7 +51,7 @@ export async function fixDependenciesDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the original command function
// Call the original command function using the provided path
await fixDependenciesCommand(tasksPath);
// Restore normal logging

View File

@@ -8,40 +8,46 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import path from 'path';
/**
* Direct function wrapper for generateTaskFiles with error handling.
*
* @param {Object} args - Command arguments containing file and output path options.
* @param {Object} args - Command arguments containing tasksJsonPath and outputDir.
* @param {Object} log - Logger object.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function generateTaskFilesDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, outputDir } = args;
try {
log.info(`Generating task files with args: ${JSON.stringify(args)}`);
// Get tasks file path
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log);
} catch (error) {
log.error(`Error finding tasks file: ${error.message}`);
// Check if paths were provided
if (!tasksJsonPath) {
const errorMessage = 'tasksJsonPath is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'TASKS_FILE_ERROR', message: error.message },
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
fromCache: false
};
}
if (!outputDir) {
const errorMessage = 'outputDir is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
fromCache: false
};
}
// Get output directory (defaults to the same directory as the tasks file)
let outputDir = args.output;
if (!outputDir) {
outputDir = path.dirname(tasksPath);
}
// Use the provided paths
const tasksPath = tasksJsonPath;
const resolvedOutputDir = outputDir;
log.info(`Generating task files from ${tasksPath} to ${outputDir}`);
log.info(`Generating task files from ${tasksPath} to ${resolvedOutputDir}`);
// Execute core generateTaskFiles function in a separate try/catch
try {
@@ -49,7 +55,7 @@ export async function generateTaskFilesDirect(args, log) {
enableSilentMode();
// The function is synchronous despite being awaited elsewhere
generateTaskFiles(tasksPath, outputDir);
generateTaskFiles(tasksPath, resolvedOutputDir);
// Restore normal logging after task generation
disableSilentMode();
@@ -70,8 +76,8 @@ export async function generateTaskFilesDirect(args, log) {
success: true,
data: {
message: `Successfully generated task files`,
tasksPath,
outputDir,
tasksPath: tasksPath,
outputDir: resolvedOutputDir,
taskFiles:
'Individual task files have been generated in the output directory'
},

View File

@@ -1,5 +1,4 @@
import path from 'path';
import { initializeProject, log as initLog } from '../../../../scripts/init.js'; // Import core function and its logger if needed separately
import { initializeProject } from '../../../../scripts/init.js'; // Import core function and its logger if needed separately
import {
enableSilentMode,
disableSilentMode

View File

@@ -5,7 +5,6 @@
import { listTasks } from '../../../../scripts/modules/task-manager.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -14,38 +13,30 @@ import {
/**
* Direct function wrapper for listTasks with error handling and caching.
*
* @param {Object} args - Command arguments (projectRoot is expected to be resolved).
* @param {Object} args - Command arguments (now expecting tasksJsonPath explicitly).
* @param {Object} log - Logger object.
* @returns {Promise<Object>} - Task list result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }.
*/
export async function listTasksDirect(args, log) {
let tasksPath;
try {
// Find the tasks path first - needed for cache key and execution
tasksPath = findTasksJsonPath(args, log);
} catch (error) {
if (error.code === 'TASKS_FILE_NOT_FOUND') {
log.error(`Tasks file not found: ${error.message}`);
// Return the error structure expected by the calling tool/handler
// Destructure the explicit tasksJsonPath from args
const { tasksJsonPath, status, withSubtasks } = args;
if (!tasksJsonPath) {
log.error('listTasksDirect called without tasksJsonPath');
return {
success: false,
error: { code: error.code, message: error.message },
fromCache: false
};
}
log.error(`Unexpected error finding tasks file: ${error.message}`);
// Re-throw for outer catch or return structured error
return {
success: false,
error: { code: 'FIND_TASKS_PATH_ERROR', message: error.message },
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
},
fromCache: false
};
}
// Generate cache key *after* finding tasksPath
const statusFilter = args.status || 'all';
const withSubtasks = args.withSubtasks || false;
const cacheKey = `listTasks:${tasksPath}:${statusFilter}:${withSubtasks}`;
// Use the explicit tasksJsonPath for cache key
const statusFilter = status || 'all';
const withSubtasksFilter = withSubtasks || false;
const cacheKey = `listTasks:${tasksJsonPath}:${statusFilter}:${withSubtasksFilter}`;
// Define the action function to be executed on cache miss
const coreListTasksAction = async () => {
@@ -54,12 +45,13 @@ export async function listTasksDirect(args, log) {
enableSilentMode();
log.info(
`Executing core listTasks function for path: ${tasksPath}, filter: ${statusFilter}, subtasks: ${withSubtasks}`
`Executing core listTasks function for path: ${tasksJsonPath}, filter: ${statusFilter}, subtasks: ${withSubtasksFilter}`
);
// Pass the explicit tasksJsonPath to the core function
const resultData = listTasks(
tasksPath,
tasksJsonPath,
statusFilter,
withSubtasks,
withSubtasksFilter,
'json'
);

View File

@@ -6,7 +6,6 @@
import { findNextTask } from '../../../../scripts/modules/task-manager.js';
import { readJSON } from '../../../../scripts/modules/utils.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -16,28 +15,28 @@ import {
* Direct function wrapper for finding the next task to work on with error handling and caching.
*
* @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Next task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
*/
export async function nextTaskDirect(args, log) {
let tasksPath;
try {
// Find the tasks path first - needed for cache key and execution
tasksPath = findTasksJsonPath(args, log);
} catch (error) {
log.error(`Tasks file not found: ${error.message}`);
// Destructure expected args
const { tasksJsonPath } = args;
if (!tasksJsonPath) {
log.error('nextTaskDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'FILE_NOT_FOUND_ERROR',
message: error.message
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
},
fromCache: false
};
}
// Generate cache key using task path
const cacheKey = `nextTask:${tasksPath}`;
// Generate cache key using the provided task path
const cacheKey = `nextTask:${tasksJsonPath}`;
// Define the action function to be executed on cache miss
const coreNextTaskAction = async () => {
@@ -45,16 +44,17 @@ export async function nextTaskDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Finding next task from ${tasksPath}`);
log.info(`Finding next task from ${tasksJsonPath}`);
// Read tasks data
const data = readJSON(tasksPath);
// Read tasks data using the provided path
const data = readJSON(tasksJsonPath);
if (!data || !data.tasks) {
disableSilentMode(); // Disable before return
return {
success: false,
error: {
code: 'INVALID_TASKS_FILE',
message: `No valid tasks found in ${tasksPath}`
message: `No valid tasks found in ${tasksJsonPath}`
}
};
}

View File

@@ -47,21 +47,9 @@ export async function parsePRDDirect(args, log, context = {}) {
};
}
// --- Parameter validation and path resolution ---
if (!args.input) {
const errorMessage =
'No input file specified. Please provide an input PRD document path.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_INPUT_FILE', message: errorMessage },
fromCache: false
};
}
// Validate projectRoot
// Validate required parameters
if (!args.projectRoot) {
const errorMessage = 'Project root is required but was not provided';
const errorMessage = 'Project root is required for parsePRDDirect';
log.error(errorMessage);
return {
success: false,
@@ -70,53 +58,32 @@ export async function parsePRDDirect(args, log, context = {}) {
};
}
const homeDir = os.homedir();
// Disallow invalid projectRoot values
if (args.projectRoot === '/' || args.projectRoot === homeDir) {
const errorMessage = `Invalid project root: ${args.projectRoot}. Cannot use root or home directory.`;
if (!args.input) {
const errorMessage = 'Input file path is required for parsePRDDirect';
log.error(errorMessage);
return {
success: false,
error: { code: 'INVALID_PROJECT_ROOT', message: errorMessage },
error: { code: 'MISSING_INPUT_PATH', message: errorMessage },
fromCache: false
};
}
// Resolve input path (relative to validated project root)
if (!args.output) {
const errorMessage = 'Output file path is required for parsePRDDirect';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_OUTPUT_PATH', message: errorMessage },
fromCache: false
};
}
// Resolve input path (expecting absolute path or path relative to project root)
const projectRoot = args.projectRoot;
log.info(`Using validated project root: ${projectRoot}`);
// Make sure the project root directory exists
if (!fs.existsSync(projectRoot)) {
const errorMessage = `Project root directory does not exist: ${projectRoot}`;
log.error(errorMessage);
return {
success: false,
error: { code: 'PROJECT_ROOT_NOT_FOUND', message: errorMessage },
fromCache: false
};
}
// Resolve input path relative to validated project root
const inputPath = path.isAbsolute(args.input)
? args.input
: path.resolve(projectRoot, args.input);
log.info(`Resolved input path: ${inputPath}`);
// Determine output path
let outputPath;
if (args.output) {
outputPath = path.isAbsolute(args.output)
? args.output
: path.resolve(projectRoot, args.output);
} else {
// Default to tasks/tasks.json in the project root
outputPath = path.resolve(projectRoot, 'tasks', 'tasks.json');
}
log.info(`Resolved output path: ${outputPath}`);
// Verify input file exists
if (!fs.existsSync(inputPath)) {
const errorMessage = `Input file not found: ${inputPath}`;
@@ -132,6 +99,18 @@ export async function parsePRDDirect(args, log, context = {}) {
};
}
// Resolve output path (expecting absolute path or path relative to project root)
const outputPath = path.isAbsolute(args.output)
? args.output
: path.resolve(projectRoot, args.output);
// Ensure output directory exists
const outputDir = path.dirname(outputPath);
if (!fs.existsSync(outputDir)) {
log.info(`Creating output directory: ${outputDir}`);
fs.mkdirSync(outputDir, { recursive: true });
}
// Parse number of tasks - handle both string and number values
let numTasks = 10; // Default
if (args.numTasks) {

View File

@@ -3,7 +3,6 @@
*/
import { removeDependency } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -12,19 +11,32 @@ import {
/**
* Remove a dependency from a task
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string|number} args.id - Task ID to remove dependency from
* @param {string|number} args.dependsOn - Task ID to remove as a dependency
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function removeDependencyDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id, dependsOn } = args;
try {
log.info(`Removing dependency with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('removeDependencyDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
// Validate required parameters
if (!args.id) {
if (!id) {
return {
success: false,
error: {
@@ -34,7 +46,7 @@ export async function removeDependencyDirect(args, log) {
};
}
if (!args.dependsOn) {
if (!dependsOn) {
return {
success: false,
error: {
@@ -44,18 +56,16 @@ export async function removeDependencyDirect(args, log) {
};
}
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log);
// Use provided path
const tasksPath = tasksJsonPath;
// Format IDs for the core function
const taskId =
args.id.includes && args.id.includes('.')
? args.id
: parseInt(args.id, 10);
id && id.includes && id.includes('.') ? id : parseInt(id, 10);
const dependencyId =
args.dependsOn.includes && args.dependsOn.includes('.')
? args.dependsOn
: parseInt(args.dependsOn, 10);
dependsOn && dependsOn.includes && dependsOn.includes('.')
? dependsOn
: parseInt(dependsOn, 10);
log.info(
`Removing dependency: task ${taskId} no longer depends on ${dependencyId}`
@@ -64,7 +74,7 @@ export async function removeDependencyDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core function
// Call the core function using the provided tasksPath
await removeDependency(tasksPath, taskId, dependencyId);
// Restore normal logging

View File

@@ -3,7 +3,6 @@
*/
import { removeSubtask } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -12,22 +11,37 @@ import {
/**
* Remove a subtask from its parent task
* @param {Object} args - Function arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - Subtask ID in format "parentId.subtaskId" (required)
* @param {boolean} [args.convert] - Whether to convert the subtask to a standalone task
* @param {string} [args.file] - Path to the tasks file
* @param {boolean} [args.skipGenerate] - Skip regenerating task files
* @param {string} [args.projectRoot] - Project root directory
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function removeSubtaskDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id, convert, skipGenerate } = args;
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
if (!args.id) {
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('removeSubtaskDirect called without tasksJsonPath');
disableSilentMode(); // Disable before returning
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
if (!id) {
disableSilentMode(); // Disable before returning
return {
success: false,
error: {
@@ -39,32 +53,34 @@ export async function removeSubtaskDirect(args, log) {
}
// Validate subtask ID format
if (!args.id.includes('.')) {
if (!id.includes('.')) {
disableSilentMode(); // Disable before returning
return {
success: false,
error: {
code: 'INPUT_VALIDATION_ERROR',
message: `Invalid subtask ID format: ${args.id}. Expected format: "parentId.subtaskId"`
message: `Invalid subtask ID format: ${id}. Expected format: "parentId.subtaskId"`
}
};
}
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log);
// Use provided path
const tasksPath = tasksJsonPath;
// Convert convertToTask to a boolean
const convertToTask = args.convert === true;
const convertToTask = convert === true;
// Determine if we should generate files
const generateFiles = !args.skipGenerate;
const generateFiles = !skipGenerate;
log.info(
`Removing subtask ${args.id} (convertToTask: ${convertToTask}, generateFiles: ${generateFiles})`
`Removing subtask ${id} (convertToTask: ${convertToTask}, generateFiles: ${generateFiles})`
);
// Use the provided tasksPath
const result = await removeSubtask(
tasksPath,
args.id,
id,
convertToTask,
generateFiles
);
@@ -77,7 +93,7 @@ export async function removeSubtaskDirect(args, log) {
return {
success: true,
data: {
message: `Subtask ${args.id} successfully converted to task #${result.id}`,
message: `Subtask ${id} successfully converted to task #${result.id}`,
task: result
}
};
@@ -86,7 +102,7 @@ export async function removeSubtaskDirect(args, log) {
return {
success: true,
data: {
message: `Subtask ${args.id} successfully removed`
message: `Subtask ${id} successfully removed`
}
};
}

View File

@@ -8,35 +8,35 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
/**
* Direct function wrapper for removeTask with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - The ID of the task or subtask to remove.
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Remove task result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: false }
*/
export async function removeTaskDirect(args, log) {
// Destructure expected args
const { tasksJsonPath, id } = args;
try {
// Find the tasks path first
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log);
} catch (error) {
log.error(`Tasks file not found: ${error.message}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
log.error('removeTaskDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'FILE_NOT_FOUND_ERROR',
message: error.message
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
},
fromCache: false
};
}
// Validate task ID parameter
const taskId = args.id;
const taskId = id;
if (!taskId) {
log.error('Task ID is required');
return {
@@ -50,14 +50,14 @@ export async function removeTaskDirect(args, log) {
}
// Skip confirmation in the direct function since it's handled by the client
log.info(`Removing task with ID: ${taskId} from ${tasksPath}`);
log.info(`Removing task with ID: ${taskId} from ${tasksJsonPath}`);
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the core removeTask function
const result = await removeTask(tasksPath, taskId);
// Call the core removeTask function using the provided path
const result = await removeTask(tasksJsonPath, taskId);
// Restore normal logging
disableSilentMode();
@@ -70,7 +70,7 @@ export async function removeTaskDirect(args, log) {
data: {
message: result.message,
taskId: taskId,
tasksPath: tasksPath,
tasksPath: tasksJsonPath,
removedTask: result.removedTask
},
fromCache: false

View File

@@ -4,7 +4,6 @@
*/
import { setTaskStatus } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode,
@@ -14,16 +13,29 @@ import {
/**
* Direct function wrapper for setTaskStatus with error handling.
*
* @param {Object} args - Command arguments containing id, status and file path options.
* @param {Object} args - Command arguments containing id, status and tasksJsonPath.
* @param {Object} log - Logger object.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function setTaskStatusDirect(args, log) {
// Destructure expected args, including the resolved tasksJsonPath
const { tasksJsonPath, id, status } = args;
try {
log.info(`Setting task status with args: ${JSON.stringify(args)}`);
// Check required parameters
if (!args.id) {
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
const errorMessage = 'tasksJsonPath is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
fromCache: false
};
}
// Check required parameters (id and status)
if (!id) {
const errorMessage =
'No task ID specified. Please provide a task ID to update.';
log.error(errorMessage);
@@ -34,7 +46,7 @@ export async function setTaskStatusDirect(args, log) {
};
}
if (!args.status) {
if (!status) {
const errorMessage =
'No status specified. Please provide a new status value.';
log.error(errorMessage);
@@ -45,32 +57,16 @@ export async function setTaskStatusDirect(args, log) {
};
}
// Get tasks file path
let tasksPath;
try {
// The enhanced findTasksJsonPath will now search in parent directories if needed
tasksPath = findTasksJsonPath(args, log);
log.info(`Found tasks file at: ${tasksPath}`);
} catch (error) {
log.error(`Error finding tasks file: ${error.message}`);
return {
success: false,
error: {
code: 'TASKS_FILE_ERROR',
message: `${error.message}\n\nPlease ensure you are in a Task Master project directory or use the --project-root parameter to specify the path to your project.`
},
fromCache: false
};
}
// Use the provided path
const tasksPath = tasksJsonPath;
// Execute core setTaskStatus function
const taskId = args.id;
const newStatus = args.status;
const taskId = id;
const newStatus = status;
log.info(`Setting task ${taskId} status to "${newStatus}"`);
// Call the core function with proper silent mode handling
let result;
enableSilentMode(); // Enable silent mode before calling core function
try {
// Call the core function
@@ -79,19 +75,20 @@ export async function setTaskStatusDirect(args, log) {
log.info(`Successfully set task ${taskId} status to ${newStatus}`);
// Return success data
result = {
const result = {
success: true,
data: {
message: `Successfully updated task ${taskId} status to "${newStatus}"`,
taskId,
status: newStatus,
tasksPath
tasksPath: tasksPath // Return the path used
},
fromCache: false // This operation always modifies state and should never be cached
};
return result;
} catch (error) {
log.error(`Error setting task status: ${error.message}`);
result = {
return {
success: false,
error: {
code: 'SET_STATUS_ERROR',
@@ -103,8 +100,6 @@ export async function setTaskStatusDirect(args, log) {
// ALWAYS restore normal logging in finally block
disableSilentMode();
}
return result;
} catch (error) {
// Ensure silent mode is disabled if there was an uncaught error in the outer try block
if (isSilentMode()) {

View File

@@ -6,7 +6,6 @@
import { findTaskById } from '../../../../scripts/modules/utils.js';
import { readJSON } from '../../../../scripts/modules/utils.js';
import { getCachedOrExecute } from '../../tools/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -16,28 +15,29 @@ import {
* Direct function wrapper for showing task details with error handling and caching.
*
* @param {Object} args - Command arguments
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {string} args.id - The ID of the task or subtask to show.
* @param {Object} log - Logger object
* @returns {Promise<Object>} - Task details result { success: boolean, data?: any, error?: { code: string, message: string }, fromCache: boolean }
*/
export async function showTaskDirect(args, log) {
let tasksPath;
try {
// Find the tasks path first - needed for cache key and execution
tasksPath = findTasksJsonPath(args, log);
} catch (error) {
log.error(`Tasks file not found: ${error.message}`);
// Destructure expected args
const { tasksJsonPath, id } = args;
if (!tasksJsonPath) {
log.error('showTaskDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'FILE_NOT_FOUND_ERROR',
message: error.message
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
},
fromCache: false
};
}
// Validate task ID
const taskId = args.id;
const taskId = id;
if (!taskId) {
log.error('Task ID is required');
return {
@@ -50,8 +50,8 @@ export async function showTaskDirect(args, log) {
};
}
// Generate cache key using task path and ID
const cacheKey = `showTask:${tasksPath}:${taskId}`;
// Generate cache key using the provided task path and ID
const cacheKey = `showTask:${tasksJsonPath}:${taskId}`;
// Define the action function to be executed on cache miss
const coreShowTaskAction = async () => {
@@ -59,16 +59,19 @@ export async function showTaskDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
log.info(`Retrieving task details for ID: ${taskId} from ${tasksPath}`);
log.info(
`Retrieving task details for ID: ${taskId} from ${tasksJsonPath}`
);
// Read tasks data
const data = readJSON(tasksPath);
// Read tasks data using the provided path
const data = readJSON(tasksJsonPath);
if (!data || !data.tasks) {
disableSilentMode(); // Disable before returning
return {
success: false,
error: {
code: 'INVALID_TASKS_FILE',
message: `No valid tasks found in ${tasksPath}`
message: `No valid tasks found in ${tasksJsonPath}`
}
};
}
@@ -77,6 +80,7 @@ export async function showTaskDirect(args, log) {
const task = findTaskById(data.tasks, taskId);
if (!task) {
disableSilentMode(); // Disable before returning
return {
success: false,
error: {

View File

@@ -8,7 +8,6 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
getAnthropicClientForMCP,
getPerplexityClientForMCP
@@ -17,19 +16,31 @@ import {
/**
* Direct function wrapper for updateSubtaskById with error handling.
*
* @param {Object} args - Command arguments containing id, prompt, useResearch and file path options.
* @param {Object} args - Command arguments containing id, prompt, useResearch and tasksJsonPath.
* @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function updateSubtaskByIdDirect(args, log, context = {}) {
const { session } = context; // Only extract session, not reportProgress
const { tasksJsonPath, id, prompt, research } = args;
try {
log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
// Check required parameters
if (!args.id) {
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
const errorMessage = 'tasksJsonPath is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
fromCache: false
};
}
// Check required parameters (id and prompt)
if (!id) {
const errorMessage =
'No subtask ID specified. Please provide a subtask ID to update.';
log.error(errorMessage);
@@ -40,7 +51,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
};
}
if (!args.prompt) {
if (!prompt) {
const errorMessage =
'No prompt specified. Please provide a prompt with information to add to the subtask.';
log.error(errorMessage);
@@ -52,7 +63,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
}
// Validate subtask ID format
const subtaskId = args.id;
const subtaskId = id;
if (typeof subtaskId !== 'string' && typeof subtaskId !== 'number') {
const errorMessage = `Invalid subtask ID type: ${typeof subtaskId}. Subtask ID must be a string or number.`;
log.error(errorMessage);
@@ -74,24 +85,14 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
};
}
// Get tasks file path
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log);
} catch (error) {
log.error(`Error finding tasks file: ${error.message}`);
return {
success: false,
error: { code: 'TASKS_FILE_ERROR', message: error.message },
fromCache: false
};
}
// Use the provided path
const tasksPath = tasksJsonPath;
// Get research flag
const useResearch = args.research === true;
const useResearch = research === true;
log.info(
`Updating subtask with ID ${subtaskIdStr} with prompt "${args.prompt}" and research: ${useResearch}`
`Updating subtask with ID ${subtaskIdStr} with prompt "${prompt}" and research: ${useResearch}`
);
// Initialize the appropriate AI client based on research flag
@@ -134,7 +135,7 @@ export async function updateSubtaskByIdDirect(args, log, context = {}) {
const updatedSubtask = await updateSubtaskById(
tasksPath,
subtaskIdStr,
args.prompt,
prompt,
useResearch,
{
session,

View File

@@ -4,7 +4,6 @@
*/
import { updateTaskById } from '../../../../scripts/modules/task-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -17,19 +16,32 @@ import {
/**
* Direct function wrapper for updateTaskById with error handling.
*
* @param {Object} args - Command arguments containing id, prompt, useResearch and file path options.
* @param {Object} args - Command arguments containing id, prompt, useResearch and tasksJsonPath.
* @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function updateTaskByIdDirect(args, log, context = {}) {
const { session } = context; // Only extract session, not reportProgress
// Destructure expected args, including the resolved tasksJsonPath
const { tasksJsonPath, id, prompt, research } = args;
try {
log.info(`Updating task with args: ${JSON.stringify(args)}`);
// Check required parameters
if (!args.id) {
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
const errorMessage = 'tasksJsonPath is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
fromCache: false
};
}
// Check required parameters (id and prompt)
if (!id) {
const errorMessage =
'No task ID specified. Please provide a task ID to update.';
log.error(errorMessage);
@@ -40,7 +52,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
};
}
if (!args.prompt) {
if (!prompt) {
const errorMessage =
'No prompt specified. Please provide a prompt with new information for the task update.';
log.error(errorMessage);
@@ -53,15 +65,15 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
// Parse taskId - handle both string and number values
let taskId;
if (typeof args.id === 'string') {
if (typeof id === 'string') {
// Handle subtask IDs (e.g., "5.2")
if (args.id.includes('.')) {
taskId = args.id; // Keep as string for subtask IDs
if (id.includes('.')) {
taskId = id; // Keep as string for subtask IDs
} else {
// Parse as integer for main task IDs
taskId = parseInt(args.id, 10);
taskId = parseInt(id, 10);
if (isNaN(taskId)) {
const errorMessage = `Invalid task ID: ${args.id}. Task ID must be a positive integer or subtask ID (e.g., "5.2").`;
const errorMessage = `Invalid task ID: ${id}. Task ID must be a positive integer or subtask ID (e.g., "5.2").`;
log.error(errorMessage);
return {
success: false,
@@ -71,24 +83,14 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
}
}
} else {
taskId = args.id;
taskId = id;
}
// Get tasks file path
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log);
} catch (error) {
log.error(`Error finding tasks file: ${error.message}`);
return {
success: false,
error: { code: 'TASKS_FILE_ERROR', message: error.message },
fromCache: false
};
}
// Use the provided path
const tasksPath = tasksJsonPath;
// Get research flag
const useResearch = args.research === true;
const useResearch = research === true;
// Initialize appropriate AI client based on research flag
let aiClient;
@@ -113,7 +115,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
}
log.info(
`Updating task with ID ${taskId} with prompt "${args.prompt}" and research: ${useResearch}`
`Updating task with ID ${taskId} with prompt "${prompt}" and research: ${useResearch}`
);
try {
@@ -133,7 +135,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
await updateTaskById(
tasksPath,
taskId,
args.prompt,
prompt,
useResearch,
{
mcpLog: logWrapper, // Use our wrapper object that has the expected method structure
@@ -149,7 +151,7 @@ export async function updateTaskByIdDirect(args, log, context = {}) {
data: {
message: `Successfully updated task with ID ${taskId} based on the prompt`,
taskId,
tasksPath,
tasksPath: tasksPath, // Return the used path
useResearch
},
fromCache: false // This operation always modifies state and should never be cached

View File

@@ -8,7 +8,6 @@ import {
enableSilentMode,
disableSilentMode
} from '../../../../scripts/modules/utils.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
getAnthropicClientForMCP,
getPerplexityClientForMCP
@@ -17,19 +16,31 @@ import {
/**
* Direct function wrapper for updating tasks based on new context/prompt.
*
* @param {Object} args - Command arguments containing fromId, prompt, useResearch and file path options.
* @param {Object} args - Command arguments containing fromId, prompt, useResearch and tasksJsonPath.
* @param {Object} log - Logger object.
* @param {Object} context - Context object containing session data.
* @returns {Promise<Object>} - Result object with success status and data/error information.
*/
export async function updateTasksDirect(args, log, context = {}) {
const { session } = context; // Only extract session, not reportProgress
const { tasksJsonPath, from, prompt, research } = args;
try {
log.info(`Updating tasks with args: ${JSON.stringify(args)}`);
// Check if tasksJsonPath was provided
if (!tasksJsonPath) {
const errorMessage = 'tasksJsonPath is required but was not provided.';
log.error(errorMessage);
return {
success: false,
error: { code: 'MISSING_ARGUMENT', message: errorMessage },
fromCache: false
};
}
// Check for the common mistake of using 'id' instead of 'from'
if (args.id !== undefined && args.from === undefined) {
if (args.id !== undefined && from === undefined) {
const errorMessage =
"You specified 'id' parameter but 'update' requires 'from' parameter. Use 'from' for this tool or use 'update_task' tool if you want to update a single task.";
log.error(errorMessage);
@@ -46,7 +57,7 @@ export async function updateTasksDirect(args, log, context = {}) {
}
// Check required parameters
if (!args.from) {
if (!from) {
const errorMessage =
'No from ID specified. Please provide a task ID to start updating from.';
log.error(errorMessage);
@@ -57,7 +68,7 @@ export async function updateTasksDirect(args, log, context = {}) {
};
}
if (!args.prompt) {
if (!prompt) {
const errorMessage =
'No prompt specified. Please provide a prompt with new context for task updates.';
log.error(errorMessage);
@@ -70,10 +81,10 @@ export async function updateTasksDirect(args, log, context = {}) {
// Parse fromId - handle both string and number values
let fromId;
if (typeof args.from === 'string') {
fromId = parseInt(args.from, 10);
if (typeof from === 'string') {
fromId = parseInt(from, 10);
if (isNaN(fromId)) {
const errorMessage = `Invalid from ID: ${args.from}. Task ID must be a positive integer.`;
const errorMessage = `Invalid from ID: ${from}. Task ID must be a positive integer.`;
log.error(errorMessage);
return {
success: false,
@@ -82,24 +93,11 @@ export async function updateTasksDirect(args, log, context = {}) {
};
}
} else {
fromId = args.from;
}
// Get tasks file path
let tasksPath;
try {
tasksPath = findTasksJsonPath(args, log);
} catch (error) {
log.error(`Error finding tasks file: ${error.message}`);
return {
success: false,
error: { code: 'TASKS_FILE_ERROR', message: error.message },
fromCache: false
};
fromId = from;
}
// Get research flag
const useResearch = args.research === true;
const useResearch = research === true;
// Initialize appropriate AI client based on research flag
let aiClient;
@@ -124,16 +122,25 @@ export async function updateTasksDirect(args, log, context = {}) {
}
log.info(
`Updating tasks from ID ${fromId} with prompt "${args.prompt}" and research: ${useResearch}`
`Updating tasks from ID ${fromId} with prompt "${prompt}" and research: ${useResearch}`
);
// Create the logger wrapper to ensure compatibility with core functions
const logWrapper = {
info: (message, ...args) => log.info(message, ...args),
warn: (message, ...args) => log.warn(message, ...args),
error: (message, ...args) => log.error(message, ...args),
debug: (message, ...args) => log.debug && log.debug(message, ...args), // Handle optional debug
success: (message, ...args) => log.info(message, ...args) // Map success to info if needed
};
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Execute core updateTasks function, passing the AI client and session
await updateTasks(tasksPath, fromId, args.prompt, useResearch, {
mcpLog: log,
await updateTasks(tasksJsonPath, fromId, prompt, useResearch, {
mcpLog: logWrapper, // Pass the wrapper instead of the raw log object
session
});
@@ -144,7 +151,7 @@ export async function updateTasksDirect(args, log, context = {}) {
data: {
message: `Successfully updated tasks from ID ${fromId} based on the prompt`,
fromId,
tasksPath,
tasksPath: tasksJsonPath,
useResearch
},
fromCache: false // This operation always modifies state and should never be cached

View File

@@ -3,7 +3,6 @@
*/
import { validateDependenciesCommand } from '../../../../scripts/modules/dependency-manager.js';
import { findTasksJsonPath } from '../utils/path-utils.js';
import {
enableSilentMode,
disableSilentMode
@@ -13,17 +12,30 @@ import fs from 'fs';
/**
* Validate dependencies in tasks.json
* @param {Object} args - Function arguments
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file.
* @param {Object} log - Logger object
* @returns {Promise<{success: boolean, data?: Object, error?: {code: string, message: string}}>}
*/
export async function validateDependenciesDirect(args, log) {
try {
log.info(`Validating dependencies in tasks...`);
// Destructure the explicit tasksJsonPath
const { tasksJsonPath } = args;
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log);
if (!tasksJsonPath) {
log.error('validateDependenciesDirect called without tasksJsonPath');
return {
success: false,
error: {
code: 'MISSING_ARGUMENT',
message: 'tasksJsonPath is required'
}
};
}
try {
log.info(`Validating dependencies in tasks: ${tasksJsonPath}`);
// Use the provided tasksJsonPath
const tasksPath = tasksJsonPath;
// Verify the file exists
if (!fs.existsSync(tasksPath)) {
@@ -39,7 +51,7 @@ export async function validateDependenciesDirect(args, log) {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
// Call the original command function
// Call the original command function using the provided tasksPath
await validateDependenciesCommand(tasksPath);
// Restore normal logging

View File

@@ -291,3 +291,103 @@ function findTasksWithNpmConsideration(startDir, log) {
}
}
}
/**
* Finds potential PRD document files based on common naming patterns
* @param {string} projectRoot - The project root directory
* @param {string|null} explicitPath - Optional explicit path provided by the user
* @param {Object} log - Logger object
* @returns {string|null} - The path to the first found PRD file, or null if none found
*/
export function findPRDDocumentPath(projectRoot, explicitPath, log) {
// If explicit path is provided, check if it exists
if (explicitPath) {
const fullPath = path.isAbsolute(explicitPath)
? explicitPath
: path.resolve(projectRoot, explicitPath);
if (fs.existsSync(fullPath)) {
log.info(`Using provided PRD document path: ${fullPath}`);
return fullPath;
} else {
log.warn(
`Provided PRD document path not found: ${fullPath}, will search for alternatives`
);
}
}
// Common locations and file patterns for PRD documents
const commonLocations = [
'', // Project root
'scripts/'
];
const commonFileNames = ['PRD.md', 'prd.md', 'PRD.txt', 'prd.txt'];
// Check all possible combinations
for (const location of commonLocations) {
for (const fileName of commonFileNames) {
const potentialPath = path.join(projectRoot, location, fileName);
if (fs.existsSync(potentialPath)) {
log.info(`Found PRD document at: ${potentialPath}`);
return potentialPath;
}
}
}
log.warn(`No PRD document found in common locations within ${projectRoot}`);
return null;
}
/**
* Resolves the tasks output directory path
* @param {string} projectRoot - The project root directory
* @param {string|null} explicitPath - Optional explicit output path provided by the user
* @param {Object} log - Logger object
* @returns {string} - The resolved tasks directory path
*/
export function resolveTasksOutputPath(projectRoot, explicitPath, log) {
// If explicit path is provided, use it
if (explicitPath) {
const outputPath = path.isAbsolute(explicitPath)
? explicitPath
: path.resolve(projectRoot, explicitPath);
log.info(`Using provided tasks output path: ${outputPath}`);
return outputPath;
}
// Default output path: tasks/tasks.json in the project root
const defaultPath = path.resolve(projectRoot, 'tasks', 'tasks.json');
log.info(`Using default tasks output path: ${defaultPath}`);
// Ensure the directory exists
const outputDir = path.dirname(defaultPath);
if (!fs.existsSync(outputDir)) {
log.info(`Creating tasks directory: ${outputDir}`);
fs.mkdirSync(outputDir, { recursive: true });
}
return defaultPath;
}
/**
* Resolves various file paths needed for MCP operations based on project root
* @param {string} projectRoot - The project root directory
* @param {Object} args - Command arguments that may contain explicit paths
* @param {Object} log - Logger object
* @returns {Object} - An object containing resolved paths
*/
export function resolveProjectPaths(projectRoot, args, log) {
const prdPath = findPRDDocumentPath(projectRoot, args.input, log);
const tasksJsonPath = resolveTasksOutputPath(projectRoot, args.output, log);
// You can add more path resolutions here as needed
return {
projectRoot,
prdPath,
tasksJsonPath
// Add additional path properties as needed
};
}

View File

@@ -1 +0,0 @@

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { addDependencyDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the addDependency tool with the MCP server
@@ -32,39 +33,52 @@ export function registerAddDependencyTool(server) {
),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session, reportProgress }) => {
execute: async (args, { log, session }) => {
try {
log.info(
`Adding dependency for task ${args.id} to depend on ${args.dependsOn}`
);
reportProgress({ progress: 0 });
// Get project root using the utility function
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
// Fallback to args.projectRoot if session didn't provide one
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Call the direct function with the resolved rootFolder
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Call the direct function with the resolved path
const result = await addDependencyDirect(
{
projectRoot: rootFolder,
...args
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
dependsOn: args.dependsOn
},
log,
{ reportProgress, mcpLog: log, session }
log
// Remove context object
);
reportProgress({ progress: 100 });
// Log result
if (result.success) {
log.info(`Successfully added dependency: ${result.data.message}`);

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { addSubtaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the addSubtask tool with the MCP server
@@ -57,29 +58,48 @@ export function registerAddSubtaskTool(server) {
.describe('Skip regenerating task files'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session, reportProgress }) => {
execute: async (args, { log, session }) => {
try {
log.info(`Adding subtask with args: ${JSON.stringify(args)}`);
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await addSubtaskDirect(
{
projectRoot: rootFolder,
...args
tasksJsonPath: tasksJsonPath,
id: args.id,
taskId: args.taskId,
title: args.title,
description: args.description,
details: args.details,
status: args.status,
dependencies: args.dependencies,
skipGenerate: args.skipGenerate
},
log,
{ reportProgress, mcpLog: log, session }
log
);
if (result.success) {

View File

@@ -12,6 +12,7 @@ import {
handleApiResult
} from './utils.js';
import { addTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the addTask tool with the MCP server
@@ -58,35 +59,54 @@ export function registerAddTaskTool(server) {
.describe('Path to the tasks file (default: tasks/tasks.json)'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
),
.describe('The directory of the project. Must be an absolute path.'),
research: z
.boolean()
.optional()
.describe('Whether to use research capabilities for task creation')
}),
execute: async (args, { log, reportProgress, session }) => {
execute: async (args, { log, session }) => {
try {
log.info(`Starting add-task with args: ${JSON.stringify(args)}`);
// Get project root from session
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Call the direct function
const result = await addTaskDirect(
{
...args,
projectRoot: rootFolder
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
prompt: args.prompt,
dependencies: args.dependencies,
priority: args.priority,
research: args.research
},
log,
{ reportProgress, session }
{ session }
);
// Return the result

View File

@@ -10,6 +10,8 @@ import {
getProjectRootFromSession
} from './utils.js';
import { analyzeTaskComplexityDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
import path from 'path';
/**
* Register the analyze tool with the MCP server
@@ -53,10 +55,7 @@ export function registerAnalyzeTool(server) {
.describe('Use Perplexity AI for research-backed complexity analysis'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session }) => {
try {
@@ -64,17 +63,40 @@ export function registerAnalyzeTool(server) {
`Analyzing task complexity with args: ${JSON.stringify(args)}`
);
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const outputPath = args.output
? path.resolve(rootFolder, args.output)
: path.resolve(rootFolder, 'scripts', 'task-complexity-report.json');
const result = await analyzeTaskComplexityDirect(
{
projectRoot: rootFolder,
...args
tasksJsonPath: tasksJsonPath,
outputPath: outputPath,
model: args.model,
threshold: args.threshold,
research: args.research
},
log,
{ session }

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { clearSubtasksDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the clearSubtasks tool with the MCP server
@@ -34,38 +35,53 @@ export function registerClearSubtasksTool(server) {
),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
})
.refine((data) => data.id || data.all, {
message: "Either 'id' or 'all' parameter must be provided",
path: ['id', 'all']
}),
execute: async (args, { log, session, reportProgress }) => {
execute: async (args, { log, session }) => {
try {
log.info(`Clearing subtasks with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await clearSubtasksDirect(
{
projectRoot: rootFolder,
...args
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
all: args.all
},
log,
{ reportProgress, mcpLog: log, session }
log
// Remove context object as clearSubtasksDirect likely doesn't need session/reportProgress
);
reportProgress({ progress: 100 });
if (result.success) {
log.info(`Subtasks cleared successfully: ${result.data.message}`);
} else {

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { complexityReportDirect } from '../core/task-master-core.js';
import path from 'path';
/**
* Register the complexityReport tool with the MCP server
@@ -28,35 +29,40 @@ export function registerComplexityReportTool(server) {
),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session, reportProgress }) => {
execute: async (args, { log, session }) => {
try {
log.info(
`Getting complexity report with args: ${JSON.stringify(args)}`
);
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to the complexity report file
// Default to scripts/task-complexity-report.json relative to root
const reportPath = args.file
? path.resolve(rootFolder, args.file)
: path.resolve(rootFolder, 'scripts', 'task-complexity-report.json');
const result = await complexityReportDirect(
{
projectRoot: rootFolder,
...args
// Pass the explicitly resolved path
reportPath: reportPath
// No other args specific to this tool
},
log /*, { reportProgress, mcpLog: log, session}*/
log
);
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(
`Successfully retrieved complexity report${result.fromCache ? ' (from cache)' : ''}`

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { expandAllTasksDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the expandAll tool with the MCP server
@@ -48,26 +49,46 @@ export function registerExpandAllTool(server) {
),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session }) => {
try {
log.info(`Expanding all tasks with args: ${JSON.stringify(args)}`);
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await expandAllTasksDirect(
{
projectRoot: rootFolder,
...args
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
num: args.num,
research: args.research,
prompt: args.prompt,
force: args.force
},
log,
{ session }

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { expandTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
import fs from 'fs';
import path from 'path';
@@ -23,10 +24,7 @@ export function registerExpandTaskTool(server) {
description: 'Expand a task into subtasks for detailed implementation',
parameters: z.object({
id: z.string().describe('ID of task to expand'),
num: z
.union([z.string(), z.number()])
.optional()
.describe('Number of subtasks to generate'),
num: z.string().optional().describe('Number of subtasks to generate'),
research: z
.boolean()
.optional()
@@ -38,42 +36,52 @@ export function registerExpandTaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.'),
force: z.boolean().optional().describe('Force the expansion')
}),
execute: async (args, { log, session }) => {
try {
log.info(`Starting expand-task with args: ${JSON.stringify(args)}`);
// Get project root from session
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
log.info(`Project root resolved to: ${rootFolder}`);
// Check for tasks.json in the standard locations
const tasksJsonPath = path.join(rootFolder, 'tasks', 'tasks.json');
if (fs.existsSync(tasksJsonPath)) {
log.info(`Found tasks.json at ${tasksJsonPath}`);
// Add the file parameter directly to args
args.file = tasksJsonPath;
} else {
log.warn(`Could not find tasks.json at ${tasksJsonPath}`);
// Resolve the path to tasks.json using the utility
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Call direct function with only session in the context, not reportProgress
// Use the pattern recommended in the MCP guidelines
const result = await expandTaskDirect(
{
...args,
projectRoot: rootFolder
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
num: args.num,
research: args.research,
prompt: args.prompt,
force: args.force // Need to add force to parameters
},
log,
{ session }

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { fixDependenciesDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the fixDependencies tool with the MCP server
@@ -23,34 +24,42 @@ export function registerFixDependenciesTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session, reportProgress }) => {
execute: async (args, { log, session }) => {
try {
log.info(`Fixing dependencies with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await fixDependenciesDirect(
{
projectRoot: rootFolder,
...args
tasksJsonPath: tasksJsonPath
},
log,
{ reportProgress, mcpLog: log, session }
log
);
await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully fixed dependencies: ${result.data.message}`);
} else {

View File

@@ -10,6 +10,8 @@ import {
getProjectRootFromSession
} from './utils.js';
import { generateTaskFilesDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
import path from 'path';
/**
* Register the generate tool with the MCP server
@@ -28,33 +30,52 @@ export function registerGenerateTool(server) {
.describe('Output directory (default: same directory as tasks file)'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session, reportProgress }) => {
execute: async (args, { log, session }) => {
try {
log.info(`Generating task files with args: ${JSON.stringify(args)}`);
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Determine output directory: use explicit arg or default to tasks.json directory
const outputDir = args.output
? path.resolve(rootFolder, args.output) // Resolve relative to root if needed
: path.dirname(tasksJsonPath);
const result = await generateTaskFilesDirect(
{
projectRoot: rootFolder,
...args
// Pass the explicitly resolved paths
tasksJsonPath: tasksJsonPath,
outputDir: outputDir
// No other args specific to this tool
},
log /*, { reportProgress, mcpLog: log, session}*/
log
);
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully generated task files: ${result.data.message}`);
} else {

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { showTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Custom processor function that removes allTasks from the response
@@ -42,12 +43,9 @@ export function registerShowTaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session, reportProgress }) => {
execute: async (args, { log, session }) => {
// Log the session right at the start of execute
log.info(
`Session object received in execute: ${JSON.stringify(session)}`
@@ -60,26 +58,43 @@ export function registerShowTaskTool(server) {
`Session object received in execute: ${JSON.stringify(session)}`
); // Use JSON.stringify for better visibility
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
} else if (!rootFolder) {
// Ensure we always have *some* root, even if session failed and args didn't provide one
rootFolder = process.cwd();
log.warn(
`Session and args failed to provide root, using CWD: ${rootFolder}`
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
log.info(`Attempting to use project root: ${rootFolder}`); // Log the final resolved root
log.info(`Root folder: ${rootFolder}`); // Log the final resolved root
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
log.info(`Attempting to use tasks file path: ${tasksJsonPath}`);
const result = await showTaskDirect(
{
projectRoot: rootFolder,
...args
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id
},
log
);

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { listTasksDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the getTasks tool with the MCP server
@@ -39,33 +40,47 @@ export function registerListTasksTool(server) {
),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: automatically detected from session or CWD)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session, reportProgress }) => {
execute: async (args, { log, session }) => {
try {
log.info(`Getting tasks with filters: ${JSON.stringify(args)}`);
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
// Use the error message from findTasksJsonPath for better context
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await listTasksDirect(
{
projectRoot: rootFolder,
...args
tasksJsonPath: tasksJsonPath,
status: args.status,
withSubtasks: args.withSubtasks
},
log /*, { reportProgress, mcpLog: log, session}*/
log
);
// await reportProgress({ progress: 100 });
log.info(
`Retrieved ${result.success ? result.data?.tasks?.length || 0 : 0} tasks${result.fromCache ? ' (from cache)' : ''}`
);

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { nextTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the next-task tool with the MCP server
@@ -24,33 +25,46 @@ export function registerNextTaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session, reportProgress }) => {
execute: async (args, { log, session }) => {
try {
log.info(`Finding next task with args: ${JSON.stringify(args)}`);
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await nextTaskDirect(
{
projectRoot: rootFolder,
...args
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath
// No other args specific to this tool
},
log /*, { reportProgress, mcpLog: log, session}*/
log
);
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(
`Successfully found next task: ${result.data?.task?.id || 'No available tasks'}`

View File

@@ -5,11 +5,16 @@
import { z } from 'zod';
import {
getProjectRootFromSession,
handleApiResult,
createErrorResponse,
getProjectRootFromSession
createErrorResponse
} from './utils.js';
import { parsePRDDirect } from '../core/task-master-core.js';
import {
resolveProjectPaths,
findPRDDocumentPath,
resolveTasksOutputPath
} from '../core/utils/path-utils.js';
/**
* Register the parsePRD tool with the MCP server
@@ -23,6 +28,7 @@ export function registerParsePRDTool(server) {
parameters: z.object({
input: z
.string()
.optional()
.default('scripts/prd.txt')
.describe('Absolute path to the PRD document file (.txt, .md, etc.)'),
numTasks: z
@@ -35,7 +41,7 @@ export function registerParsePRDTool(server) {
.string()
.optional()
.describe(
'Output absolute path for tasks.json file (default: tasks/tasks.json)'
'Output path for tasks.json file (default: tasks/tasks.json)'
),
force: z
.boolean()
@@ -43,39 +49,44 @@ export function registerParsePRDTool(server) {
.describe('Allow overwriting an existing tasks.json file.'),
projectRoot: z
.string()
.describe(
'Absolute path to the root directory of the project. Required - ALWAYS SET THIS TO THE PROJECT ROOT DIRECTORY.'
)
.describe('The directory of the project. Must be absolute path.')
}),
execute: async (args, { log, session }) => {
try {
log.info(`Parsing PRD with args: ${JSON.stringify(args)}`);
// Make sure projectRoot is passed directly in args or derive from session
// We prioritize projectRoot from args over session-derived path
let rootFolder = args.projectRoot;
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
// Only if args.projectRoot is undefined or null, try to get it from session
if (!rootFolder) {
log.warn(
'projectRoot not provided in args, attempting to derive from session'
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
rootFolder = getProjectRootFromSession(session, log);
if (!rootFolder) {
const errorMessage =
'Could not determine project root directory. Please provide projectRoot parameter.';
log.error(errorMessage);
return createErrorResponse(errorMessage);
}
}
log.info(`Using project root: ${rootFolder} for PRD parsing`);
// Resolve input (PRD) and output (tasks.json) paths using the utility
const { projectRoot, prdPath, tasksJsonPath } = resolveProjectPaths(
rootFolder,
args,
log
);
// Check if PRD path was found (resolveProjectPaths returns null if not found and not provided)
if (!prdPath) {
return createErrorResponse(
'No PRD document found or provided. Please ensure a PRD file exists (e.g., PRD.md) or provide a valid input file path.'
);
}
// Call the direct function with fully resolved paths
const result = await parsePRDDirect(
{
projectRoot: rootFolder,
...args
projectRoot: projectRoot,
input: prdPath,
output: tasksJsonPath,
numTasks: args.numTasks,
force: args.force
},
log,
{ session }

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { removeDependencyDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the removeDependency tool with the MCP server
@@ -30,35 +31,50 @@ export function registerRemoveDependencyTool(server) {
),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session, reportProgress }) => {
execute: async (args, { log, session }) => {
try {
log.info(
`Removing dependency for task ${args.id} from ${args.dependsOn} with args: ${JSON.stringify(args)}`
);
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await removeDependencyDirect(
{
projectRoot: rootFolder,
...args
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
dependsOn: args.dependsOn
},
log /*, { reportProgress, mcpLog: log, session}*/
log
);
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Successfully removed dependency: ${result.data.message}`);
} else {

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { removeSubtaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the removeSubtask tool with the MCP server
@@ -43,33 +44,49 @@ export function registerRemoveSubtaskTool(server) {
.describe('Skip regenerating task files'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session, reportProgress }) => {
execute: async (args, { log, session }) => {
try {
log.info(`Removing subtask with args: ${JSON.stringify(args)}`);
// await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await removeSubtaskDirect(
{
projectRoot: rootFolder,
...args
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
convert: args.convert,
skipGenerate: args.skipGenerate
},
log /*, { reportProgress, mcpLog: log, session}*/
log
);
// await reportProgress({ progress: 100 });
if (result.success) {
log.info(`Subtask removed successfully: ${result.data.message}`);
} else {

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { removeTaskDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the remove-task tool with the MCP server
@@ -26,10 +27,7 @@ export function registerRemoveTaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
),
.describe('The directory of the project. Must be an absolute path.'),
confirm: z
.boolean()
.optional()
@@ -39,28 +37,40 @@ export function registerRemoveTaskTool(server) {
try {
log.info(`Removing task with ID: ${args.id}`);
// Get project root from session
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
} else if (!rootFolder) {
// Ensure we have a default if nothing else works
rootFolder = process.cwd();
log.warn(
`Session and args failed to provide root, using CWD: ${rootFolder}`
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
log.info(`Using project root: ${rootFolder}`);
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
log.info(`Using tasks file path: ${tasksJsonPath}`);
// Assume client has already handled confirmation if needed
const result = await removeTaskDirect(
{
id: args.id,
file: args.file,
projectRoot: rootFolder
tasksJsonPath: tasksJsonPath,
id: args.id
},
log
);

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { setTaskStatusDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the setTaskStatus tool with the MCP server
@@ -33,28 +34,45 @@ export function registerSetTaskStatusTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: automatically detected)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session }) => {
try {
log.info(`Setting status of task(s) ${args.id} to: ${args.status}`);
// Get project root from session
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Call the direct function with the project root
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
// Call the direct function with the resolved path
const result = await setTaskStatusDirect(
{
...args,
projectRoot: rootFolder
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
status: args.status
},
log
);

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { updateSubtaskByIdDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the update-subtask tool with the MCP server
@@ -34,26 +35,45 @@ export function registerUpdateSubtaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session }) => {
try {
log.info(`Updating subtask with args: ${JSON.stringify(args)}`);
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await updateSubtaskByIdDirect(
{
projectRoot: rootFolder,
...args
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
prompt: args.prompt,
research: args.research
},
log,
{ session }

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { updateTaskByIdDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the update-task tool with the MCP server
@@ -34,26 +35,45 @@ export function registerUpdateTaskTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session }) => {
try {
log.info(`Updating task with args: ${JSON.stringify(args)}`);
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await updateTaskByIdDirect(
{
projectRoot: rootFolder,
...args
// Pass the explicitly resolved path
tasksJsonPath: tasksJsonPath,
// Pass other relevant args
id: args.id,
prompt: args.prompt,
research: args.research
},
log,
{ session }

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { updateTasksDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the update tool with the MCP server
@@ -36,26 +37,43 @@ export function registerUpdateTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session }) => {
try {
log.info(`Updating tasks with args: ${JSON.stringify(args)}`);
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
// Ensure project root was determined
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
// Resolve the path to tasks.json
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await updateTasksDirect(
{
projectRoot: rootFolder,
...args
tasksJsonPath: tasksJsonPath,
from: args.from,
prompt: args.prompt,
research: args.research
},
log,
{ session }

View File

@@ -10,6 +10,7 @@ import {
getProjectRootFromSession
} from './utils.js';
import { validateDependenciesDirect } from '../core/task-master-core.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
/**
* Register the validateDependencies tool with the MCP server
@@ -24,34 +25,42 @@ export function registerValidateDependenciesTool(server) {
file: z.string().optional().describe('Absolute path to the tasks file'),
projectRoot: z
.string()
.optional()
.describe(
'Root directory of the project (default: current working directory)'
)
.describe('The directory of the project. Must be an absolute path.')
}),
execute: async (args, { log, session, reportProgress }) => {
execute: async (args, { log, session }) => {
try {
log.info(`Validating dependencies with args: ${JSON.stringify(args)}`);
await reportProgress({ progress: 0 });
let rootFolder = getProjectRootFromSession(session, log);
// Get project root from args or session
const rootFolder =
args.projectRoot || getProjectRootFromSession(session, log);
if (!rootFolder && args.projectRoot) {
rootFolder = args.projectRoot;
log.info(`Using project root from args as fallback: ${rootFolder}`);
if (!rootFolder) {
return createErrorResponse(
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
);
}
let tasksJsonPath;
try {
tasksJsonPath = findTasksJsonPath(
{ projectRoot: rootFolder, file: args.file },
log
);
} catch (error) {
log.error(`Error finding tasks.json: ${error.message}`);
return createErrorResponse(
`Failed to find tasks.json: ${error.message}`
);
}
const result = await validateDependenciesDirect(
{
projectRoot: rootFolder,
...args
tasksJsonPath: tasksJsonPath
},
log,
{ reportProgress, mcpLog: log, session }
log
);
await reportProgress({ progress: 100 });
if (result.success) {
log.info(
`Successfully validated dependencies: ${result.data.message}`

View File

@@ -23,23 +23,7 @@ import chalk from 'chalk';
import figlet from 'figlet';
import boxen from 'boxen';
import gradient from 'gradient-string';
import {
isSilentMode,
enableSilentMode,
disableSilentMode
} from './modules/utils.js';
// Only log if not in silent mode
if (!isSilentMode()) {
console.log('Starting task-master-ai...');
}
// Debug information - only log if not in silent mode
if (!isSilentMode()) {
console.log('Node version:', process.version);
console.log('Current directory:', process.cwd());
console.log('Script path:', import.meta.url);
}
import { isSilentMode } from './modules/utils.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

View File

@@ -14,7 +14,8 @@ import {
writeJSON,
taskExists,
formatTaskId,
findCycles
findCycles,
isSilentMode
} from './utils.js';
import { displayBanner } from './ui.js';
@@ -320,6 +321,7 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
`Removed dependency: Task ${formattedTaskId} no longer depends on ${formattedDependencyId}`
);
if (!isSilentMode()) {
// Display a more visually appealing success message
console.log(
boxen(
@@ -333,6 +335,7 @@ async function removeDependency(tasksPath, taskId, dependencyId) {
}
)
);
}
// Regenerate task files
await generateTaskFiles(tasksPath, 'tasks');
@@ -553,8 +556,11 @@ function cleanupSubtaskDependencies(tasksData) {
* Validate dependencies in task files
* @param {string} tasksPath - Path to tasks.json
*/
async function validateDependenciesCommand(tasksPath) {
async function validateDependenciesCommand(tasksPath, options = {}) {
// Only display banner if not in silent mode
if (!isSilentMode()) {
displayBanner();
}
log('info', 'Checking for invalid dependencies in task files...');
@@ -659,7 +665,8 @@ async function validateDependenciesCommand(tasksPath) {
if (changesDetected) {
log('success', 'Invalid dependencies were removed from tasks.json');
// Show detailed stats in a nice box
// Show detailed stats in a nice box - only if not in silent mode
if (!isSilentMode()) {
console.log(
boxen(
chalk.green(`Dependency Validation Results:\n\n`) +
@@ -685,6 +692,7 @@ async function validateDependenciesCommand(tasksPath) {
console.log(` ${warning}`);
});
}
}
// Regenerate task files to reflect the changes
await generateTaskFiles(tasksPath, path.dirname(tasksPath));
@@ -695,7 +703,8 @@ async function validateDependenciesCommand(tasksPath) {
'No invalid dependencies found - all dependencies are valid'
);
// Show validation summary
// Show validation summary - only if not in silent mode
if (!isSilentMode()) {
console.log(
boxen(
chalk.green(`All Dependencies Are Valid\n\n`) +
@@ -711,6 +720,7 @@ async function validateDependenciesCommand(tasksPath) {
)
);
}
}
} catch (error) {
log('error', 'Error validating dependencies:', error);
process.exit(1);
@@ -747,9 +757,13 @@ function countAllDependencies(tasks) {
/**
* Fixes invalid dependencies in tasks.json
* @param {string} tasksPath - Path to tasks.json
* @param {Object} options - Options object
*/
async function fixDependenciesCommand(tasksPath) {
async function fixDependenciesCommand(tasksPath, options = {}) {
// Only display banner if not in silent mode
if (!isSilentMode()) {
displayBanner();
}
log('info', 'Checking for and fixing invalid dependencies in tasks.json...');
@@ -1086,6 +1100,7 @@ async function fixDependenciesCommand(tasksPath) {
stats.duplicateDependenciesRemoved +
stats.circularDependenciesFixed;
if (!isSilentMode()) {
if (totalFixedAll > 0) {
log('success', `Fixed ${totalFixedAll} dependency issues in total!`);
@@ -1107,7 +1122,10 @@ async function fixDependenciesCommand(tasksPath) {
)
);
} else {
log('success', 'No dependency issues found - all dependencies are valid');
log(
'success',
'No dependency issues found - all dependencies are valid'
);
console.log(
boxen(
@@ -1123,6 +1141,7 @@ async function fixDependenciesCommand(tasksPath) {
)
);
}
}
} catch (error) {
log('error', 'Error in fix-dependencies command:', error);
process.exit(1);