refactor(expand/all): Implement additive expansion and complexity report integration
Refactors the `expandTask` and `expandAllTasks` features to complete subtask 61.38 and enhance functionality based on subtask 61.37's refactor.
Key Changes:
- **Additive Expansion (`expandTask`, `expandAllTasks`):**
- Modified `expandTask` default behavior to append newly generated subtasks to any existing ones.
- Added a `force` flag (passed down from CLI/MCP via `--force` option/parameter) to `expandTask` and `expandAllTasks`. When `force` is true, existing subtasks are cleared before generating new ones.
- Updated relevant CLI command (`expand`), MCP tool (`expand_task`, `expand_all`), and direct function wrappers (`expandTaskDirect`, `expandAllTasksDirect`) to handle and pass the `force` flag.
- **Complexity Report Integration (`expandTask`):**
- `expandTask` now reads `scripts/task-complexity-report.json`.
- If an analysis entry exists for the target task:
- `recommendedSubtasks` is used to determine the number of subtasks to generate (unless `--num` is explicitly provided).
- `expansionPrompt` is used as the primary prompt content for the AI.
- `reasoning` is appended to any additional context provided.
- If no report entry exists or the report is missing, it falls back to default subtask count (from config) and standard prompt generation.
- **`expandAllTasks` Orchestration:**
- Refactored `expandAllTasks` to primarily iterate through eligible tasks (pending/in-progress, considering `force` flag and existing subtasks) and call the updated `expandTask` function for each.
- Removed redundant logic (like complexity reading or explicit subtask clearing) now handled within `expandTask`.
- Ensures correct context (`session`, `mcpLog`) and flags (`useResearch`, `force`) are passed down.
- **Configuration & Cleanup:**
- Updated `.cursor/mcp.json` with new Perplexity/Anthropic API keys (old ones invalidated).
- Completed refactoring of `expandTask` started in 61.37, confirming usage of `generateTextService` and appropriate prompts.
- **Task Management:**
- Marked subtask 61.37 as complete.
- Updated `.changeset/cuddly-zebras-matter.md` to reflect user-facing changes.
These changes finalize the refactoring of the task expansion features, making them more robust, configurable via complexity analysis, and aligned with the unified AI service architecture.
This commit is contained in:
@@ -8,135 +8,91 @@ import {
|
||||
disableSilentMode,
|
||||
isSilentMode
|
||||
} from '../../../../scripts/modules/utils.js';
|
||||
import { getAnthropicClientForMCP } from '../utils/ai-client-utils.js';
|
||||
import path from 'path';
|
||||
import fs from 'fs';
|
||||
|
||||
/**
|
||||
* Expand all pending tasks with subtasks
|
||||
* Expand all pending tasks with subtasks (Direct Function Wrapper)
|
||||
* @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 {boolean} [args.research] - Enable 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 {Object} log - Logger object
|
||||
* @param {Object} log - Logger object from FastMCP
|
||||
* @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
|
||||
const { session } = context; // Extract session
|
||||
// Destructure expected args
|
||||
const { tasksJsonPath, num, research, prompt, force } = args;
|
||||
|
||||
try {
|
||||
log.info(`Expanding all tasks with args: ${JSON.stringify(args)}`);
|
||||
// Create the standard logger wrapper
|
||||
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
|
||||
};
|
||||
|
||||
// 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 {
|
||||
// Remove internal path finding
|
||||
/*
|
||||
const tasksPath = findTasksJsonPath(args, log);
|
||||
*/
|
||||
// Use provided path
|
||||
const tasksPath = tasksJsonPath;
|
||||
|
||||
// Parse parameters
|
||||
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...`
|
||||
);
|
||||
|
||||
if (useResearch) {
|
||||
log.info('Using Perplexity AI for research-backed subtask generation');
|
||||
|
||||
// Initialize AI client for research-backed expansion
|
||||
try {
|
||||
await getAnthropicClientForMCP(session, log);
|
||||
} catch (error) {
|
||||
// Ensure silent mode is disabled before returning error
|
||||
disableSilentMode();
|
||||
|
||||
log.error(`Failed to initialize AI client: ${error.message}`);
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'AI_CLIENT_ERROR',
|
||||
message: `Cannot initialize AI client: ${error.message}`
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (additionalContext) {
|
||||
log.info(`Additional context: "${additionalContext}"`);
|
||||
}
|
||||
if (forceFlag) {
|
||||
log.info('Force regeneration of subtasks is enabled');
|
||||
}
|
||||
|
||||
// Call the core function with session context for AI operations
|
||||
// and outputFormat as 'json' to prevent UI elements
|
||||
const result = await expandAllTasks(
|
||||
tasksPath,
|
||||
numSubtasks,
|
||||
useResearch,
|
||||
additionalContext,
|
||||
forceFlag,
|
||||
{ mcpLog: log, session },
|
||||
'json' // Use JSON output format to prevent UI elements
|
||||
);
|
||||
|
||||
// The expandAllTasks function now returns a result object
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: 'Successfully expanded all pending tasks with subtasks',
|
||||
details: {
|
||||
numSubtasks: numSubtasks,
|
||||
research: useResearch,
|
||||
prompt: additionalContext,
|
||||
force: forceFlag,
|
||||
tasksExpanded: result.expandedCount,
|
||||
totalEligibleTasks: result.tasksToExpand
|
||||
}
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
// Restore normal logging in finally block to ensure it runs even if there's an error
|
||||
disableSilentMode();
|
||||
}
|
||||
} catch (error) {
|
||||
// Ensure silent mode is disabled if an error occurs
|
||||
if (isSilentMode()) {
|
||||
disableSilentMode();
|
||||
}
|
||||
|
||||
log.error(`Error in expandAllTasksDirect: ${error.message}`);
|
||||
if (!tasksJsonPath) {
|
||||
log.error('expandAllTasksDirect called without tasksJsonPath');
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'CORE_FUNCTION_ERROR',
|
||||
message: error.message
|
||||
code: 'MISSING_ARGUMENT',
|
||||
message: 'tasksJsonPath is required'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
enableSilentMode(); // Enable silent mode for the core function call
|
||||
try {
|
||||
log.info(
|
||||
`Calling core expandAllTasks with args: ${JSON.stringify({ num, research, prompt, force })}`
|
||||
);
|
||||
|
||||
// Parse parameters (ensure correct types)
|
||||
const numSubtasks = num ? parseInt(num, 10) : undefined;
|
||||
const useResearch = research === true;
|
||||
const additionalContext = prompt || '';
|
||||
const forceFlag = force === true;
|
||||
|
||||
// Call the core function, passing the logger wrapper and session
|
||||
const result = await expandAllTasks(
|
||||
tasksJsonPath, // Use the provided path
|
||||
numSubtasks,
|
||||
useResearch,
|
||||
additionalContext,
|
||||
forceFlag,
|
||||
{ mcpLog: logWrapper, session }, // Pass the wrapper and session
|
||||
'json' // Explicitly request JSON output format
|
||||
);
|
||||
|
||||
// Core function now returns a summary object
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
message: `Expand all operation completed. Expanded: ${result.expandedCount}, Failed: ${result.failedCount}, Skipped: ${result.skippedCount}`,
|
||||
details: result // Include the full result details
|
||||
}
|
||||
};
|
||||
} catch (error) {
|
||||
// Log the error using the MCP logger
|
||||
log.error(`Error during core expandAllTasks execution: ${error.message}`);
|
||||
// Optionally log stack trace if available and debug enabled
|
||||
// if (error.stack && log.debug) { log.debug(error.stack); }
|
||||
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: 'CORE_FUNCTION_ERROR', // Or a more specific code if possible
|
||||
message: error.message
|
||||
}
|
||||
};
|
||||
} finally {
|
||||
disableSilentMode(); // IMPORTANT: Ensure silent mode is always disabled
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,22 +19,27 @@ import { findTasksJsonPath } from '../core/utils/path-utils.js';
|
||||
export function registerExpandAllTool(server) {
|
||||
server.addTool({
|
||||
name: 'expand_all',
|
||||
description: 'Expand all pending tasks into subtasks',
|
||||
description:
|
||||
'Expand all pending tasks into subtasks based on complexity or defaults',
|
||||
parameters: z.object({
|
||||
num: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Number of subtasks to generate for each task'),
|
||||
.describe(
|
||||
'Target number of subtasks per task (uses complexity/defaults otherwise)'
|
||||
),
|
||||
research: z
|
||||
.boolean()
|
||||
.optional()
|
||||
.describe(
|
||||
'Enable Perplexity AI for research-backed subtask generation'
|
||||
'Enable research-backed subtask generation (e.g., using Perplexity)'
|
||||
),
|
||||
prompt: z
|
||||
.string()
|
||||
.optional()
|
||||
.describe('Additional context to guide subtask generation'),
|
||||
.describe(
|
||||
'Additional context to guide subtask generation for all tasks'
|
||||
),
|
||||
force: z
|
||||
.boolean()
|
||||
.optional()
|
||||
@@ -45,34 +50,37 @@ export function registerExpandAllTool(server) {
|
||||
.string()
|
||||
.optional()
|
||||
.describe(
|
||||
'Absolute path to the tasks file (default: tasks/tasks.json)'
|
||||
'Relative path to the tasks file from project root (default: tasks/tasks.json)'
|
||||
),
|
||||
projectRoot: z
|
||||
.string()
|
||||
.describe('The directory of the project. Must be an absolute path.')
|
||||
.optional()
|
||||
.describe(
|
||||
'Absolute path to the project root directory (derived from session if possible)'
|
||||
)
|
||||
}),
|
||||
execute: async (args, { log, session }) => {
|
||||
try {
|
||||
log.info(`Expanding all tasks with args: ${JSON.stringify(args)}`);
|
||||
log.info(
|
||||
`Tool expand_all execution started with args: ${JSON.stringify(args)}`
|
||||
);
|
||||
|
||||
// Get project root from args or session
|
||||
const rootFolder =
|
||||
args.projectRoot || getProjectRootFromSession(session, log);
|
||||
|
||||
// Ensure project root was determined
|
||||
const rootFolder = getProjectRootFromSession(session, log);
|
||||
if (!rootFolder) {
|
||||
log.error('Could not determine project root from session.');
|
||||
return createErrorResponse(
|
||||
'Could not determine project root. Please provide it explicitly or ensure your session contains valid root information.'
|
||||
'Could not determine project root from session.'
|
||||
);
|
||||
}
|
||||
log.info(`Project root determined: ${rootFolder}`);
|
||||
|
||||
// Resolve the path to tasks.json
|
||||
let tasksJsonPath;
|
||||
try {
|
||||
tasksJsonPath = findTasksJsonPath(
|
||||
{ projectRoot: rootFolder, file: args.file },
|
||||
log
|
||||
);
|
||||
log.info(`Resolved tasks.json path: ${tasksJsonPath}`);
|
||||
} catch (error) {
|
||||
log.error(`Error finding tasks.json: ${error.message}`);
|
||||
return createErrorResponse(
|
||||
@@ -82,9 +90,7 @@ export function registerExpandAllTool(server) {
|
||||
|
||||
const result = await expandAllTasksDirect(
|
||||
{
|
||||
// Pass the explicitly resolved path
|
||||
tasksJsonPath: tasksJsonPath,
|
||||
// Pass other relevant args
|
||||
num: args.num,
|
||||
research: args.research,
|
||||
prompt: args.prompt,
|
||||
@@ -94,18 +100,17 @@ export function registerExpandAllTool(server) {
|
||||
{ session }
|
||||
);
|
||||
|
||||
if (result.success) {
|
||||
log.info(`Successfully expanded all tasks: ${result.data.message}`);
|
||||
} else {
|
||||
log.error(
|
||||
`Failed to expand all tasks: ${result.error?.message || 'Unknown error'}`
|
||||
);
|
||||
}
|
||||
|
||||
return handleApiResult(result, log, 'Error expanding all tasks');
|
||||
} catch (error) {
|
||||
log.error(`Error in expand-all tool: ${error.message}`);
|
||||
return createErrorResponse(error.message);
|
||||
log.error(
|
||||
`Unexpected error in expand_all tool execute: ${error.message}`
|
||||
);
|
||||
if (error.stack) {
|
||||
log.error(error.stack);
|
||||
}
|
||||
return createErrorResponse(
|
||||
`An unexpected error occurred: ${error.message}`
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user