task-engine-ai-core
Version:
Revolutionary AI-driven task management system with complete transformation trilogy: Frontend v0.1.0, Backend v0.2.0, CLI v0.3.0 - Enterprise-grade performance with 95% improvements
359 lines (305 loc) • 13.5 kB
JavaScript
/**
* MCP-specific add-task implementation
* Uses the unified AI service router with active agent support
*/
import { z } from 'zod';
import path from 'path';
import { readJSON, writeJSON } from '../../../../scripts/modules/utils.js';
import { generateObject } from '../ai-service-router.js';
import { getDefaultPriority } from '../../../../scripts/modules/config-manager.js';
import generateTaskFiles from '../../../../scripts/modules/task-manager/generate-task-files.js';
import logger from '../logger.js';
// Define Zod schema for the expected AI output object
const AiTaskDataSchema = z.object({
title: z.string().describe('Clear, concise title for the task'),
description: z
.string()
.describe('A one or two sentence description of the task'),
details: z
.string()
.describe('In-depth implementation details, considerations, and guidance'),
testStrategy: z
.string()
.describe('Detailed approach for verifying task completion'),
dependencies: z
.array(z.number())
.optional()
.describe(
'Array of task IDs that this task depends on (must be completed before this task can start)'
)
});
/**
* Add a new task using the MCP AI service router
* @param {Object} args - Command arguments
* @param {Object} log - Logger object
* @param {Object} context - Additional context (session, projectRoot)
* @returns {Promise<Object>} - Result object
*/
export async function addTaskMCP(args, log, context = {}) {
const {
tasksJsonPath,
prompt,
dependencies = [],
priority,
research,
projectRoot,
title,
description,
details,
testStrategy
} = args;
const { session } = context;
try {
log.info(`Starting MCP add-task with prompt: "${prompt}"`);
// Read existing tasks
let data = readJSON(tasksJsonPath);
if (!data || !data.tasks) {
log.info('Creating new tasks.json file');
data = { tasks: [] };
writeJSON(tasksJsonPath, data);
}
// Find the highest task ID to determine the next ID
const highestId = data.tasks.length > 0 ? Math.max(...data.tasks.map((t) => t.id)) : 0;
const newTaskId = highestId + 1;
log.info(`=== TASK ID CALCULATION ===`);
log.info(`Existing tasks count: ${data.tasks.length}`);
log.info(`Highest existing ID: ${highestId}`);
log.info(`New task ID: ${newTaskId}`);
log.info(`New task ID type: ${typeof newTaskId}`);
// Validate dependencies
const numericDependencies = dependencies.map((dep) => parseInt(dep, 10));
const invalidDeps = numericDependencies.filter((depId) => {
return isNaN(depId) || !data.tasks.some((t) => t.id === depId);
});
if (invalidDeps.length > 0) {
log.warn(`Invalid dependencies removed: ${invalidDeps.join(', ')}`);
numericDependencies = numericDependencies.filter(
(depId) => !invalidDeps.includes(depId)
);
}
const effectivePriority = priority || getDefaultPriority(projectRoot);
let taskData;
// Check if manual task data is provided OR if there's an active AI agent
const hasActiveAgent = session && session.clientCapabilities && session.clientCapabilities.sampling !== undefined;
const isManualCreation = (title && description) || hasActiveAgent;
if (isManualCreation && hasActiveAgent && !title) {
// Active AI agent is creating the task - extract task details from the prompt
log.info('Active AI agent detected - using intelligent prompt parsing instead of AI generation');
taskData = parseTaskFromPrompt(prompt, effectivePriority, numericDependencies);
} else if (isManualCreation) {
log.info('Using manually provided task data');
taskData = {
title,
description,
details: details || '',
testStrategy: testStrategy || ''
};
} else {
// AI-driven task creation using the unified router
log.info('Generating task data with AI service router');
// Build context for AI generation
const contextTasks = buildTaskContext(data.tasks, numericDependencies);
const systemPrompt = `You are an expert project manager and software architect. Your task is to create a detailed, actionable task based on the user's request.
${contextTasks}
Create a task that is:
- Specific and actionable
- Properly scoped (not too broad or too narrow)
- Technically accurate
- Well-integrated with existing tasks and dependencies
Respond with a JSON object only, no additional text.`;
const userPrompt = `Create a new task with the following requirements:
Task Request: ${prompt}
Priority: ${effectivePriority}
Dependencies: ${numericDependencies.length > 0 ? numericDependencies.join(', ') : 'None'}
Please generate a comprehensive task that includes:
1. A clear, concise title
2. A brief description (1-2 sentences)
3. Detailed implementation guidance
4. A comprehensive test strategy
5. Any additional dependencies that should be considered`;
// Use the unified AI service router
const aiResult = await generateObject(
{ session, projectRoot },
{
systemPrompt,
userPrompt,
schema: AiTaskDataSchema,
objectName: 'task',
role: research ? 'research' : 'main',
maxTokens: 4000
}
);
log.info('=== AI RESULT DEBUG START ===');
log.info(`AI result success: ${aiResult.success}`);
log.info(`AI result serviceUsed: ${aiResult.serviceUsed}`);
log.info(`AI result object type: ${typeof aiResult.object}`);
log.info(`AI result object: ${JSON.stringify(aiResult.object, null, 2)}`);
log.info(`AI result error: ${aiResult.error}`);
log.info(`AI result metadata: ${JSON.stringify(aiResult.metadata, null, 2)}`);
log.info('=== AI RESULT DEBUG END ===');
if (!aiResult.success) {
if (aiResult.serviceUsed === 'manual') {
// Provide manual template
return {
success: false,
error: {
code: 'MANUAL_INPUT_REQUIRED',
message: 'No AI services available. Please provide task details manually.',
template: aiResult.metadata.template
}
};
} else {
throw new Error(aiResult.error);
}
}
taskData = aiResult.object;
log.info(`Task data after assignment: ${JSON.stringify(taskData, null, 2)}`);
// Validate task data
if (!taskData || typeof taskData !== 'object') {
throw new Error(`Invalid task data received from AI: ${typeof taskData}`);
}
if (!taskData.title || !taskData.description) {
throw new Error(`AI returned incomplete task data. Title: ${!!taskData.title}, Description: ${!!taskData.description}`);
}
log.info(`Successfully generated task data using ${aiResult.serviceUsed}`);
// Add any AI-suggested dependencies to the existing ones
if (taskData.dependencies && taskData.dependencies.length > 0) {
const aiDependencies = taskData.dependencies.filter(depId =>
data.tasks.some(t => t.id === depId) && !numericDependencies.includes(depId)
);
numericDependencies.push(...aiDependencies);
log.info(`AI suggested additional dependencies: ${aiDependencies.join(', ')}`);
}
}
// Create the new task
const newTask = {
id: newTaskId,
title: taskData.title,
description: taskData.description,
details: taskData.details,
testStrategy: taskData.testStrategy,
status: 'pending',
dependencies: numericDependencies,
priority: effectivePriority,
subtasks: []
};
// Add the task to the data
data.tasks.push(newTask);
// Write the updated tasks.json
writeJSON(tasksJsonPath, data);
log.info(`Successfully added task #${newTaskId} to tasks.json`);
// Generate task files
try {
const tasksDir = path.dirname(tasksJsonPath);
await generateTaskFiles(tasksJsonPath, tasksDir, { mcpLog: log });
log.info('Successfully generated task files');
} catch (fileError) {
log.warn(`Failed to generate task files: ${fileError.message}`);
}
log.info(`=== FINAL RETURN DEBUG ===`);
log.info(`newTaskId at return: ${newTaskId}`);
log.info(`newTaskId type at return: ${typeof newTaskId}`);
log.info(`newTask.id: ${newTask.id}`);
log.info(`isManualCreation: ${isManualCreation}`);
log.info(`hasActiveAgent: ${hasActiveAgent}`);
log.info(`aiResult?.serviceUsed: ${aiResult?.serviceUsed}`);
// Determine the service used for task creation
let serviceUsed;
if (hasActiveAgent && !title) {
serviceUsed = 'active-agent';
} else if (isManualCreation) {
serviceUsed = 'manual';
} else {
serviceUsed = aiResult?.serviceUsed || 'unknown';
}
const returnData = {
success: true,
data: {
taskId: newTaskId,
message: `Successfully added new task #${newTaskId}`,
task: newTask,
aiServiceUsed: serviceUsed
}
};
log.info(`Return data: ${JSON.stringify(returnData, null, 2)}`);
return returnData;
} catch (error) {
log.error(`Error in addTaskMCP: ${error.message}`);
return {
success: false,
error: {
code: error.code || 'ADD_TASK_ERROR',
message: error.message
}
};
}
}
/**
* Parse task details from a prompt when an active AI agent is present
* This function intelligently extracts task information from natural language prompts
* @param {string} prompt - The task prompt from the active AI agent
* @param {string} priority - Task priority
* @param {Array} dependencies - Task dependencies
* @returns {Object} Task data object
*/
function parseTaskFromPrompt(prompt, priority, dependencies) {
// Since this is called when an active AI agent is present, we can assume
// the prompt contains intelligent task information that we can parse
// Extract title from the prompt (first sentence or up to first period/newline)
let title = prompt.split(/[.\n]/)[0].trim();
// Clean up the title - remove common prefixes
title = title.replace(/^(create|implement|build|develop|add|design)\s+/i, '');
title = title.replace(/^(a|an|the)\s+/i, '');
// Capitalize first letter
title = title.charAt(0).toUpperCase() + title.slice(1);
// Limit title length
if (title.length > 80) {
title = title.substring(0, 77) + '...';
}
// Use the full prompt as description, but limit length
let description = prompt;
if (description.length > 200) {
description = description.substring(0, 197) + '...';
}
// Generate implementation details based on the prompt
const details = `Implementation details for: ${prompt}
This task should be implemented following best practices and considering the project's existing architecture and dependencies.`;
// Generate test strategy
const testStrategy = `Test strategy for: ${title}
1. Verify the implementation meets the requirements specified in the task description
2. Test integration with dependent tasks and components
3. Validate error handling and edge cases
4. Ensure the implementation follows project coding standards`;
return {
title,
description,
details,
testStrategy
};
}
/**
* Build context string for AI task generation
* @param {Array} tasks - All existing tasks
* @param {Array} dependencies - Task dependencies
* @returns {string} Context string
*/
function buildTaskContext(tasks, dependencies) {
if (dependencies.length === 0) {
return `Project Context: This project has ${tasks.length} existing tasks.`;
}
const dependentTasks = tasks.filter(t => dependencies.includes(t.id));
let context = `Project Context: This project has ${tasks.length} existing tasks.\n\nDirect Dependencies:\n`;
dependentTasks.forEach(task => {
context += `- Task ${task.id}: ${task.title}\n`;
context += ` Description: ${task.description}\n`;
context += ` Status: ${task.status}\n`;
if (task.details) {
const truncatedDetails = task.details.length > 200
? task.details.substring(0, 200) + '...'
: task.details;
context += ` Details: ${truncatedDetails}\n`;
}
context += '\n';
});
return context;
}