fix(commands): implement manual creation mode for add-task command

- Add support for --title/-t and --description/-d flags in add-task command
- Fix validation for manual creation mode (title + description)
- Implement proper testing for both prompt and manual creation modes
- Update testing documentation with Commander.js testing best practices
- Add guidance on handling variable hoisting and module initialization issues

Changeset: brave-doors-open.md
This commit is contained in:
Eyal Toledano
2025-04-09 18:18:13 -04:00
parent 709ea63350
commit 12519946b4
15 changed files with 1539 additions and 460 deletions

View File

@@ -23,12 +23,16 @@ import {
* Direct function wrapper for adding a new task with error handling.
*
* @param {Object} args - Command arguments
* @param {string} args.prompt - Description of the task to add
* @param {Array<number>} [args.dependencies=[]] - Task dependencies as array of IDs
* @param {string} [args.prompt] - Description of the task to add (required if not using manual fields)
* @param {string} [args.title] - Task title (for manual task creation)
* @param {string} [args.description] - Task description (for manual task creation)
* @param {string} [args.details] - Implementation details (for manual task creation)
* @param {string} [args.testStrategy] - Test strategy (for manual task creation)
* @param {string} [args.dependencies] - Comma-separated list of task IDs this task depends on
* @param {string} [args.priority='medium'] - Task priority (high, medium, low)
* @param {string} [args.file] - Path to the tasks file
* @param {string} [args.file='tasks/tasks.json'] - Path to the tasks file
* @param {string} [args.projectRoot] - Project root directory
* @param {boolean} [args.research] - Whether to use research capabilities for task creation
* @param {boolean} [args.research=false] - Whether to use research capabilities for task creation
* @param {Object} log - Logger object
* @param {Object} context - Additional context (reportProgress, session)
* @returns {Promise<Object>} - Result object { success: boolean, data?: any, error?: { code: string, message: string } }
@@ -41,15 +45,18 @@ export async function addTaskDirect(args, log, context = {}) {
// Find the tasks.json path
const tasksPath = findTasksJsonPath(args, log);
// Check if this is manual task creation or AI-driven task creation
const isManualCreation = args.title && args.description;
// Check required parameters
if (!args.prompt) {
log.error('Missing required parameter: prompt');
if (!args.prompt && !isManualCreation) {
log.error('Missing required parameters: either prompt or title+description must be provided');
disableSilentMode();
return {
success: false,
error: {
code: 'MISSING_PARAMETER',
message: 'The prompt parameter is required for adding a task'
message: 'Either the prompt parameter or both title and description parameters are required for adding a task'
}
};
}
@@ -65,120 +72,160 @@ export async function addTaskDirect(args, log, context = {}) {
: [];
const priority = args.priority || 'medium';
log.info(
`Adding new task with prompt: "${prompt}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`
);
// Extract context parameters for advanced functionality
// Commenting out reportProgress extraction
// const { reportProgress, session } = context;
const { session } = context; // Keep session
const { session } = context;
// Initialize AI client with session environment
let localAnthropic;
try {
localAnthropic = getAnthropicClientForMCP(session, log);
} catch (error) {
log.error(`Failed to initialize Anthropic client: ${error.message}`);
disableSilentMode();
return {
success: false,
error: {
code: 'AI_CLIENT_ERROR',
message: `Cannot initialize AI client: ${error.message}`
}
let manualTaskData = null;
if (isManualCreation) {
// Create manual task data object
manualTaskData = {
title: args.title,
description: args.description,
details: args.details || '',
testStrategy: args.testStrategy || ''
};
}
// Get model configuration from session
const modelConfig = getModelConfig(session);
// Read existing tasks to provide context
let tasksData;
try {
const fs = await import('fs');
tasksData = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
} catch (error) {
log.warn(`Could not read existing tasks for context: ${error.message}`);
tasksData = { tasks: [] };
}
// Build prompts for AI
const { systemPrompt, userPrompt } = _buildAddTaskPrompt(
prompt,
tasksData.tasks
);
// Make the AI call using the streaming helper
let responseText;
try {
responseText = await _handleAnthropicStream(
localAnthropic,
{
model: modelConfig.model,
max_tokens: modelConfig.maxTokens,
temperature: modelConfig.temperature,
messages: [{ role: 'user', content: userPrompt }],
system: systemPrompt
},
{
// reportProgress: context.reportProgress, // Commented out to prevent Cursor stroking out
mcpLog: log
}
log.info(
`Adding new task manually with title: "${args.title}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`
);
} catch (error) {
log.error(`AI processing failed: ${error.message}`);
// Call the addTask function with manual task data
const newTaskId = await addTask(
tasksPath,
null, // No prompt needed for manual creation
dependencies,
priority,
{
mcpLog: log,
session
},
'json', // Use JSON output format to prevent console output
null, // No custom environment
manualTaskData // Pass the manual task data
);
// Restore normal logging
disableSilentMode();
return {
success: false,
error: {
code: 'AI_PROCESSING_ERROR',
message: `Failed to generate task with AI: ${error.message}`
success: true,
data: {
taskId: newTaskId,
message: `Successfully added new task #${newTaskId}`
}
};
}
} else {
// AI-driven task creation
log.info(
`Adding new task with prompt: "${prompt}", dependencies: [${dependencies.join(', ')}], priority: ${priority}`
);
// Parse the AI response
let taskDataFromAI;
try {
taskDataFromAI = parseTaskJsonResponse(responseText);
} catch (error) {
log.error(`Failed to parse AI response: ${error.message}`);
disableSilentMode();
return {
success: false,
error: {
code: 'RESPONSE_PARSING_ERROR',
message: `Failed to parse AI response: ${error.message}`
}
};
}
// Call the addTask function with 'json' outputFormat to prevent console output when called via MCP
const newTaskId = await addTask(
tasksPath,
prompt,
dependencies,
priority,
{
// reportProgress, // Commented out
mcpLog: log,
session,
taskDataFromAI // Pass the parsed AI result
},
'json'
);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
taskId: newTaskId,
message: `Successfully added new task #${newTaskId}`
// Initialize AI client with session environment
let localAnthropic;
try {
localAnthropic = getAnthropicClientForMCP(session, log);
} catch (error) {
log.error(`Failed to initialize Anthropic client: ${error.message}`);
disableSilentMode();
return {
success: false,
error: {
code: 'AI_CLIENT_ERROR',
message: `Cannot initialize AI client: ${error.message}`
}
};
}
};
// Get model configuration from session
const modelConfig = getModelConfig(session);
// Read existing tasks to provide context
let tasksData;
try {
const fs = await import('fs');
tasksData = JSON.parse(fs.readFileSync(tasksPath, 'utf8'));
} catch (error) {
log.warn(`Could not read existing tasks for context: ${error.message}`);
tasksData = { tasks: [] };
}
// Build prompts for AI
const { systemPrompt, userPrompt } = _buildAddTaskPrompt(
prompt,
tasksData.tasks
);
// Make the AI call using the streaming helper
let responseText;
try {
responseText = await _handleAnthropicStream(
localAnthropic,
{
model: modelConfig.model,
max_tokens: modelConfig.maxTokens,
temperature: modelConfig.temperature,
messages: [{ role: 'user', content: userPrompt }],
system: systemPrompt
},
{
mcpLog: log
}
);
} catch (error) {
log.error(`AI processing failed: ${error.message}`);
disableSilentMode();
return {
success: false,
error: {
code: 'AI_PROCESSING_ERROR',
message: `Failed to generate task with AI: ${error.message}`
}
};
}
// Parse the AI response
let taskDataFromAI;
try {
taskDataFromAI = parseTaskJsonResponse(responseText);
} catch (error) {
log.error(`Failed to parse AI response: ${error.message}`);
disableSilentMode();
return {
success: false,
error: {
code: 'RESPONSE_PARSING_ERROR',
message: `Failed to parse AI response: ${error.message}`
}
};
}
// Call the addTask function with 'json' outputFormat to prevent console output when called via MCP
const newTaskId = await addTask(
tasksPath,
prompt,
dependencies,
priority,
{
mcpLog: log,
session
},
'json',
null,
taskDataFromAI // Pass the parsed AI result as the manual task data
);
// Restore normal logging
disableSilentMode();
return {
success: true,
data: {
taskId: newTaskId,
message: `Successfully added new task #${newTaskId}`
}
};
}
} catch (error) {
// Make sure to restore normal logging even if there's an error
disableSilentMode();

View File

@@ -22,7 +22,11 @@ export function registerAddTaskTool(server) {
name: 'add_task',
description: 'Add a new task using AI',
parameters: z.object({
prompt: z.string().describe('Description of the task to add'),
prompt: z.string().optional().describe('Description of the task to add (required if not using manual fields)'),
title: z.string().optional().describe('Task title (for manual task creation)'),
description: z.string().optional().describe('Task description (for manual task creation)'),
details: z.string().optional().describe('Implementation details (for manual task creation)'),
testStrategy: z.string().optional().describe('Test strategy (for manual task creation)'),
dependencies: z
.string()
.optional()
@@ -31,11 +35,11 @@ export function registerAddTaskTool(server) {
.string()
.optional()
.describe('Task priority (high, medium, low)'),
file: z.string().optional().describe('Absolute path to the tasks file'),
file: z.string().optional().describe('Path to the tasks file (default: tasks/tasks.json)'),
projectRoot: z
.string()
.optional()
.describe('Root directory of the project'),
.describe('Root directory of the project (default: current working directory)'),
research: z
.boolean()
.optional()