Merge pull request #567 from eyaltoledano/parse-prd-research
v0.15 improvements & new features
This commit is contained in:
@@ -18,6 +18,9 @@ import { createLogWrapper } from '../../tools/utils.js'; // Import the new utili
|
||||
* @param {string} args.outputPath - Explicit absolute path to save the report.
|
||||
* @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.ids] - Comma-separated list of task IDs to analyze
|
||||
* @param {number} [args.from] - Starting task ID in a range to analyze
|
||||
* @param {number} [args.to] - Ending task ID in a range to analyze
|
||||
* @param {string} [args.projectRoot] - Project root path.
|
||||
* @param {Object} log - Logger object
|
||||
* @param {Object} [context={}] - Context object containing session data
|
||||
@@ -26,7 +29,16 @@ import { createLogWrapper } from '../../tools/utils.js'; // Import the new utili
|
||||
*/
|
||||
export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
||||
const { session } = context;
|
||||
const { tasksJsonPath, outputPath, threshold, research, projectRoot } = args;
|
||||
const {
|
||||
tasksJsonPath,
|
||||
outputPath,
|
||||
threshold,
|
||||
research,
|
||||
projectRoot,
|
||||
ids,
|
||||
from,
|
||||
to
|
||||
} = args;
|
||||
|
||||
const logWrapper = createLogWrapper(log);
|
||||
|
||||
@@ -58,6 +70,14 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
||||
log.info(`Analyzing task complexity from: ${tasksPath}`);
|
||||
log.info(`Output report will be saved to: ${resolvedOutputPath}`);
|
||||
|
||||
if (ids) {
|
||||
log.info(`Analyzing specific task IDs: ${ids}`);
|
||||
} else if (from || to) {
|
||||
const fromStr = from !== undefined ? from : 'first';
|
||||
const toStr = to !== undefined ? to : 'last';
|
||||
log.info(`Analyzing tasks in range: ${fromStr} to ${toStr}`);
|
||||
}
|
||||
|
||||
if (research) {
|
||||
log.info('Using research role for complexity analysis');
|
||||
}
|
||||
@@ -68,7 +88,10 @@ export async function analyzeTaskComplexityDirect(args, log, context = {}) {
|
||||
output: outputPath,
|
||||
threshold: threshold,
|
||||
research: research === true, // Ensure boolean
|
||||
projectRoot: projectRoot // Pass projectRoot here
|
||||
projectRoot: projectRoot, // Pass projectRoot here
|
||||
id: ids, // Pass the ids parameter to the core function as 'id'
|
||||
from: from, // Pass from parameter
|
||||
to: to // Pass to parameter
|
||||
};
|
||||
// --- End Initial Checks ---
|
||||
|
||||
|
||||
99
mcp-server/src/core/direct-functions/move-task.js
Normal file
99
mcp-server/src/core/direct-functions/move-task.js
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* Direct function wrapper for moveTask
|
||||
*/
|
||||
|
||||
import { moveTask } from '../../../../scripts/modules/task-manager.js';
|
||||
import { findTasksJsonPath } from '../utils/path-utils.js';
|
||||
import {
|
||||
enableSilentMode,
|
||||
disableSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
|
||||
/**
|
||||
* Move a task or subtask to a new position
|
||||
* @param {Object} args - Function arguments
|
||||
* @param {string} args.tasksJsonPath - Explicit path to the tasks.json file
|
||||
* @param {string} args.sourceId - ID of the task/subtask to move (e.g., '5' or '5.2')
|
||||
* @param {string} args.destinationId - ID of the destination (e.g., '7' or '7.3')
|
||||
* @param {string} args.file - Alternative path to the tasks.json file
|
||||
* @param {string} args.projectRoot - Project root directory
|
||||
* @param {Object} log - Logger object
|
||||
* @returns {Promise<{success: boolean, data?: Object, error?: Object}>}
|
||||
*/
|
||||
export async function moveTaskDirect(args, log, context = {}) {
|
||||
const { session } = context;
|
||||
|
||||
// Validate required parameters
|
||||
if (!args.sourceId) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Source ID is required',
|
||||
code: 'MISSING_SOURCE_ID'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (!args.destinationId) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: 'Destination ID is required',
|
||||
code: 'MISSING_DESTINATION_ID'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
// Find tasks.json path if not provided
|
||||
let tasksPath = args.tasksJsonPath || args.file;
|
||||
if (!tasksPath) {
|
||||
if (!args.projectRoot) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message:
|
||||
'Project root is required if tasksJsonPath is not provided',
|
||||
code: 'MISSING_PROJECT_ROOT'
|
||||
}
|
||||
};
|
||||
}
|
||||
tasksPath = findTasksJsonPath(args, log);
|
||||
}
|
||||
|
||||
// Enable silent mode to prevent console output during MCP operation
|
||||
enableSilentMode();
|
||||
|
||||
// Call the core moveTask function, always generate files
|
||||
const result = await moveTask(
|
||||
tasksPath,
|
||||
args.sourceId,
|
||||
args.destinationId,
|
||||
true
|
||||
);
|
||||
|
||||
// Restore console output
|
||||
disableSilentMode();
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
movedTask: result.movedTask,
|
||||
message: `Successfully moved task/subtask ${args.sourceId} to ${args.destinationId}`
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Restore console output in case of error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Failed to move task: ${error.message}`);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
message: error.message,
|
||||
code: 'MOVE_TASK_ERROR'
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,7 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
numTasks: numTasksArg,
|
||||
force,
|
||||
append,
|
||||
research,
|
||||
projectRoot
|
||||
} = args;
|
||||
|
||||
@@ -114,8 +115,14 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
}
|
||||
}
|
||||
|
||||
if (research) {
|
||||
logWrapper.info(
|
||||
'Research mode enabled. Using Perplexity AI for enhanced PRD analysis.'
|
||||
);
|
||||
}
|
||||
|
||||
logWrapper.info(
|
||||
`Parsing PRD via direct function. Input: ${inputPath}, Output: ${outputPath}, NumTasks: ${numTasks}, Force: ${force}, Append: ${append}, ProjectRoot: ${projectRoot}`
|
||||
`Parsing PRD via direct function. Input: ${inputPath}, Output: ${outputPath}, NumTasks: ${numTasks}, Force: ${force}, Append: ${append}, Research: ${research}, ProjectRoot: ${projectRoot}`
|
||||
);
|
||||
|
||||
const wasSilent = isSilentMode();
|
||||
@@ -135,6 +142,7 @@ export async function parsePRDDirect(args, log, context = {}) {
|
||||
projectRoot,
|
||||
force,
|
||||
append,
|
||||
research,
|
||||
commandName: 'parse-prd',
|
||||
outputType: 'mcp'
|
||||
},
|
||||
|
||||
@@ -30,6 +30,7 @@ import { addDependencyDirect } from './direct-functions/add-dependency.js';
|
||||
import { removeTaskDirect } from './direct-functions/remove-task.js';
|
||||
import { initializeProjectDirect } from './direct-functions/initialize-project.js';
|
||||
import { modelsDirect } from './direct-functions/models.js';
|
||||
import { moveTaskDirect } from './direct-functions/move-task.js';
|
||||
|
||||
// Re-export utility functions
|
||||
export { findTasksJsonPath } from './utils/path-utils.js';
|
||||
@@ -60,7 +61,8 @@ export const directFunctions = new Map([
|
||||
['addDependencyDirect', addDependencyDirect],
|
||||
['removeTaskDirect', removeTaskDirect],
|
||||
['initializeProjectDirect', initializeProjectDirect],
|
||||
['modelsDirect', modelsDirect]
|
||||
['modelsDirect', modelsDirect],
|
||||
['moveTaskDirect', moveTaskDirect]
|
||||
]);
|
||||
|
||||
// Re-export all direct function implementations
|
||||
@@ -89,5 +91,6 @@ export {
|
||||
addDependencyDirect,
|
||||
removeTaskDirect,
|
||||
initializeProjectDirect,
|
||||
modelsDirect
|
||||
modelsDirect,
|
||||
moveTaskDirect
|
||||
};
|
||||
|
||||
@@ -49,6 +49,24 @@ export function registerAnalyzeProjectComplexityTool(server) {
|
||||
.describe(
|
||||
'Path to the tasks file relative to project root (default: tasks/tasks.json).'
|
||||
),
|
||||
ids: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Comma-separated list of task IDs to analyze specifically (e.g., "1,3,5").'
|
||||
),
|
||||
from: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.describe('Starting task ID in a range to analyze.'),
|
||||
to: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.positive()
|
||||
.optional()
|
||||
.describe('Ending task ID in a range to analyze.'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
@@ -107,7 +125,10 @@ export function registerAnalyzeProjectComplexityTool(server) {
|
||||
outputPath: outputPath,
|
||||
threshold: args.threshold,
|
||||
research: args.research,
|
||||
projectRoot: args.projectRoot
|
||||
projectRoot: args.projectRoot,
|
||||
ids: args.ids,
|
||||
from: args.from,
|
||||
to: args.to
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
|
||||
@@ -28,6 +28,7 @@ import { registerAddDependencyTool } from './add-dependency.js';
|
||||
import { registerRemoveTaskTool } from './remove-task.js';
|
||||
import { registerInitializeProjectTool } from './initialize-project.js';
|
||||
import { registerModelsTool } from './models.js';
|
||||
import { registerMoveTaskTool } from './move-task.js';
|
||||
|
||||
/**
|
||||
* Register all Task Master tools with the MCP server
|
||||
@@ -61,6 +62,7 @@ export function registerTaskMasterTools(server) {
|
||||
registerRemoveTaskTool(server);
|
||||
registerRemoveSubtaskTool(server);
|
||||
registerClearSubtasksTool(server);
|
||||
registerMoveTaskTool(server);
|
||||
|
||||
// Group 5: Task Analysis & Expansion
|
||||
registerAnalyzeProjectComplexityTool(server);
|
||||
|
||||
129
mcp-server/src/tools/move-task.js
Normal file
129
mcp-server/src/tools/move-task.js
Normal file
@@ -0,0 +1,129 @@
|
||||
/**
|
||||
* tools/move-task.js
|
||||
* Tool for moving tasks or subtasks to a new position
|
||||
*/
|
||||
|
||||
import { z } from 'zod';
|
||||
import {
|
||||
handleApiResult,
|
||||
createErrorResponse,
|
||||
withNormalizedProjectRoot
|
||||
} from './utils.js';
|
||||
import { moveTaskDirect } from '../core/task-master-core.js';
|
||||
import { findTasksJsonPath } from '../core/utils/path-utils.js';
|
||||
|
||||
/**
|
||||
* Register the moveTask tool with the MCP server
|
||||
* @param {Object} server - FastMCP server instance
|
||||
*/
|
||||
export function registerMoveTaskTool(server) {
|
||||
server.addTool({
|
||||
name: 'move_task',
|
||||
description: 'Move a task or subtask to a new position',
|
||||
parameters: z.object({
|
||||
from: z
|
||||
.string()
|
||||
.describe(
|
||||
'ID of the task/subtask to move (e.g., "5" or "5.2"). Can be comma-separated to move multiple tasks (e.g., "5,6,7")'
|
||||
),
|
||||
to: z
|
||||
.string()
|
||||
.describe(
|
||||
'ID of the destination (e.g., "7" or "7.3"). Must match the number of source IDs if comma-separated'
|
||||
),
|
||||
file: z.string().optional().describe('Custom path to tasks.json file'),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Root directory of the project (typically derived from session)'
|
||||
)
|
||||
}),
|
||||
execute: withNormalizedProjectRoot(async (args, { log, session }) => {
|
||||
try {
|
||||
// Find tasks.json path if not provided
|
||||
let tasksJsonPath = args.file;
|
||||
|
||||
if (!tasksJsonPath) {
|
||||
tasksJsonPath = findTasksJsonPath(args, log);
|
||||
}
|
||||
|
||||
// Parse comma-separated IDs
|
||||
const fromIds = args.from.split(',').map((id) => id.trim());
|
||||
const toIds = args.to.split(',').map((id) => id.trim());
|
||||
|
||||
// Validate matching IDs count
|
||||
if (fromIds.length !== toIds.length) {
|
||||
return createErrorResponse(
|
||||
'The number of source and destination IDs must match',
|
||||
'MISMATCHED_ID_COUNT'
|
||||
);
|
||||
}
|
||||
|
||||
// If moving multiple tasks
|
||||
if (fromIds.length > 1) {
|
||||
const results = [];
|
||||
// Move tasks one by one, only generate files on the last move
|
||||
for (let i = 0; i < fromIds.length; i++) {
|
||||
const fromId = fromIds[i];
|
||||
const toId = toIds[i];
|
||||
|
||||
// Skip if source and destination are the same
|
||||
if (fromId === toId) {
|
||||
log.info(`Skipping ${fromId} -> ${toId} (same ID)`);
|
||||
continue;
|
||||
}
|
||||
|
||||
const shouldGenerateFiles = i === fromIds.length - 1;
|
||||
const result = await moveTaskDirect(
|
||||
{
|
||||
sourceId: fromId,
|
||||
destinationId: toId,
|
||||
tasksJsonPath,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (!result.success) {
|
||||
log.error(
|
||||
`Failed to move ${fromId} to ${toId}: ${result.error.message}`
|
||||
);
|
||||
} else {
|
||||
results.push(result.data);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
moves: results,
|
||||
message: `Successfully moved ${results.length} tasks`
|
||||
}
|
||||
};
|
||||
} else {
|
||||
// Moving a single task
|
||||
return handleApiResult(
|
||||
await moveTaskDirect(
|
||||
{
|
||||
sourceId: args.from,
|
||||
destinationId: args.to,
|
||||
tasksJsonPath,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
{ session }
|
||||
),
|
||||
log
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
return createErrorResponse(
|
||||
`Failed to move task: ${error.message}`,
|
||||
'MOVE_TASK_ERROR'
|
||||
);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
@@ -49,6 +49,13 @@ export function registerParsePRDTool(server) {
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe('Append generated tasks to existing file.'),
|
||||
research: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.default(false)
|
||||
.describe(
|
||||
'Use the research model for research-backed task generation, providing more comprehensive, accurate and up-to-date task details.'
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
@@ -68,6 +75,7 @@ export function registerParsePRDTool(server) {
|
||||
numTasks: args.numTasks,
|
||||
force: args.force,
|
||||
append: args.append,
|
||||
research: args.research,
|
||||
projectRoot: args.projectRoot
|
||||
},
|
||||
log,
|
||||
|
||||
Reference in New Issue
Block a user