refactor(expand): Align expand-task with unified AI service

Refactored the `expandTask` feature (`scripts/modules/task-manager/expand-task.js`) and related components (`commands.js`, `mcp-server/src/tools/expand-task.js`, `mcp-server/src/core/direct-functions/expand-task.js`) to integrate with the unified AI service layer (`ai-services-unified.js`) and configuration management (`config-manager.js`).

The refactor involved:

- Removing direct AI client calls and configuration fetching from `expand-task.js`.

- Attempting to use `generateObjectService` for structured subtask generation. This failed due to provider-specific errors (Perplexity internal errors, Anthropic schema translation issues).

- Reverting the core AI interaction to use `generateTextService`, asking the LLM to format its response as JSON containing a "subtasks" array.

- Re-implementing manual JSON parsing and Zod validation (`parseSubtasksFromText`) to handle the text response reliably.

- Updating prompt generation functions (`generateMainSystemPrompt`, `generateMainUserPrompt`, `generateResearchUserPrompt`) to request the correct JSON object structure within the text response.

- Ensuring the `expandTaskDirect` function handles pre-checks (force flag, task status) and correctly passes the `session` context and logger wrapper to the core `expandTask` function.

- Correcting duplicate imports in `commands.js`.

- Validating the refactored feature works correctly via both CLI (`task-master expand --id <id>`) and MCP (`expand_task` tool) for main and research roles.

This aligns the task expansion feature with the new architecture while using the more robust text generation approach due to current limitations with structured output services. Closes subtask 61.37.
This commit is contained in:
Eyal Toledano
2025-04-25 01:26:42 -04:00
parent 70cc15bc87
commit 99b1a0ad7a
11 changed files with 1741 additions and 342 deletions

View File

@@ -3,7 +3,7 @@
* Direct function implementation for expanding a task into subtasks
*/
import { expandTask } from '../../../../scripts/modules/task-manager.js';
import expandTask from '../../../../scripts/modules/task-manager/expand-task.js'; // Correct import path
import {
readJSON,
writeJSON,
@@ -11,10 +11,8 @@ import {
disableSilentMode,
isSilentMode
} from '../../../../scripts/modules/utils.js';
import {
getAnthropicClientForMCP,
getModelConfig
} from '../utils/ai-client-utils.js';
// Removed AI client imports:
// import { getAnthropicClientForMCP, getModelConfig } from '../utils/ai-client-utils.js';
import path from 'path';
import fs from 'fs';
@@ -25,15 +23,16 @@ import fs from 'fs';
* @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 {boolean} [args.research] - Enable research role for 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
* @param {Object} context - Context object containing session
* @param {Object} [context.session] - MCP Session object
* @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;
const { session } = context; // Extract session
// Destructure expected args
const { tasksJsonPath, id, num, research, prompt, force } = args;
@@ -85,28 +84,9 @@ export async function expandTaskDirect(args, log, context = {}) {
const additionalContext = prompt || '';
const forceFlag = force === true;
// Initialize AI client if needed (for expandTask function)
try {
// This ensures the AI client is available by checking it
if (useResearch) {
log.info('Verifying AI client for research-backed expansion');
await getAnthropicClientForMCP(session, log);
}
} catch (error) {
log.error(`Failed to initialize AI client: ${error.message}`);
return {
success: false,
error: {
code: 'AI_CLIENT_ERROR',
message: `Cannot initialize AI client: ${error.message}`
},
fromCache: false
};
}
try {
log.info(
`[expandTaskDirect] Expanding task ${taskId} into ${numSubtasks || 'default'} subtasks. Research: ${useResearch}`
`[expandTaskDirect] Expanding task ${taskId} into ${numSubtasks || 'default'} subtasks. Research: ${useResearch}, Force: ${forceFlag}`
);
// Read tasks data
@@ -205,7 +185,16 @@ export async function expandTaskDirect(args, log, context = {}) {
// Process the request
try {
// Enable silent mode to prevent console logs from interfering with JSON response
enableSilentMode();
const wasSilent = isSilentMode();
if (!wasSilent) enableSilentMode();
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),
success: (message, ...args) => log.info(message, ...args)
};
// Call expandTask with session context to ensure AI client is properly initialized
const result = await expandTask(
@@ -214,11 +203,11 @@ export async function expandTaskDirect(args, log, context = {}) {
numSubtasks,
useResearch,
additionalContext,
{ mcpLog: log, session } // Only pass mcpLog and session, NOT reportProgress
{ session: session, mcpLog: logWrapper }
);
// Restore normal logging
disableSilentMode();
if (!wasSilent && isSilentMode()) disableSilentMode();
// Read the updated data
const updatedData = readJSON(tasksPath);
@@ -244,7 +233,7 @@ export async function expandTaskDirect(args, log, context = {}) {
};
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();
if (!wasSilent && isSilentMode()) disableSilentMode();
log.error(`Error expanding task: ${error.message}`);
return {

View File

@@ -9,9 +9,8 @@ import {
createErrorResponse,
getProjectRootFromSession
} from './utils.js';
import { expandTaskDirect } from '../core/task-master-core.js';
import { expandTaskDirect } from '../core/direct-functions/expand-task.js';
import { findTasksJsonPath } from '../core/utils/path-utils.js';
import fs from 'fs';
import path from 'path';
/**
@@ -28,16 +27,26 @@ export function registerExpandTaskTool(server) {
research: z
.boolean()
.optional()
.describe('Use Perplexity AI for research-backed generation'),
.default(false)
.describe('Use research role for generation'),
prompt: z
.string()
.optional()
.describe('Additional context for subtask generation'),
file: z.string().optional().describe('Absolute path to the tasks file'),
file: z
.string()
.optional()
.describe(
'Path to the tasks file relative to project root (e.g., tasks/tasks.json)'
),
projectRoot: z
.string()
.describe('The directory of the project. Must be an absolute path.'),
force: z.boolean().optional().describe('Force the expansion')
force: z
.boolean()
.optional()
.default(false)
.describe('Force expansion even if subtasks exist')
}),
execute: async (args, { log, session }) => {
try {