vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
1,086 lines (1,085 loc) • 47.6 kB
JavaScript
import { IntentRecognitionEngine } from './intent-recognizer.js';
import logger from '../../../logger.js';
export class CommandGateway {
static instance;
intentRecognizer;
config;
commandHistory = new Map();
contextCache = new Map();
sessionMetrics = new Map();
intentSuccessMetrics = new Map();
constructor() {
this.intentRecognizer = IntentRecognitionEngine.getInstance();
this.config = {
maxProcessingTime: 10000,
trackHistory: true,
maxHistoryEntries: 50,
enableContextAware: true,
autoExecuteThreshold: 0.8
};
}
static getInstance() {
if (!CommandGateway.instance) {
CommandGateway.instance = new CommandGateway();
}
return CommandGateway.instance;
}
async processCommand(input, context = {}) {
const startTime = Date.now();
const sessionId = context.sessionId || 'default';
try {
logger.info({ sessionId, input: input.substring(0, 100) }, 'Processing natural language command');
const commandContext = this.getOrCreateContext(sessionId, context);
const recognitionResult = await this.intentRecognizer.recognizeIntent(input, this.buildRecognitionContext(commandContext));
if (!recognitionResult) {
return this.createFailureResult(input, 'Unable to understand the command. Please try rephrasing or use a more specific request.', ['Try: "Create a project called MyApp"', 'Try: "List all tasks"', 'Try: "Run task 123"'], startTime);
}
const recognizedIntent = {
intent: recognitionResult.intent,
confidence: recognitionResult.confidence,
confidenceLevel: recognitionResult.confidenceLevel,
entities: this.convertEntitiesToArray(recognitionResult.entities),
originalInput: input,
processedInput: input.toLowerCase().trim(),
alternatives: recognitionResult.alternatives.map(alt => ({
intent: alt.intent,
confidence: alt.confidence
})),
metadata: {
processingTime: recognitionResult.processingTime,
method: recognitionResult.strategy === 'pattern' ? 'pattern' :
recognitionResult.strategy === 'llm' ? 'llm' : 'hybrid',
timestamp: recognitionResult.metadata.timestamp
}
};
logger.info({
intent: recognizedIntent.intent,
confidence: recognizedIntent.confidence,
strategy: recognitionResult.strategy,
rawEntities: recognitionResult.entities,
convertedEntities: recognizedIntent.entities,
originalInput: input
}, 'Intent recognition and entity extraction debug');
if (this.config.trackHistory) {
this.updateCommandHistory(sessionId, recognizedIntent);
}
const validation = await this.validateCommand(recognizedIntent, commandContext);
if (!validation.isValid) {
return this.createValidationErrorResult(recognizedIntent, validation, startTime);
}
const requiresConfirmation = this.shouldRequireConfirmation(recognizedIntent, validation);
const toolParams = await this.mapIntentToToolParams(recognizedIntent, validation.normalizedParams);
logger.info({
intent: recognizedIntent.intent,
entities: recognizedIntent.entities,
normalizedParams: validation.normalizedParams,
toolParams,
originalInput: input
}, 'CommandGateway parameter extraction debug');
const processingTime = Date.now() - startTime;
this.trackIntentSuccess(recognizedIntent.intent, true, input);
return {
success: true,
intent: recognizedIntent,
toolParams,
validationErrors: [],
suggestions: validation.suggestions,
metadata: {
processingTime,
confidence: recognizedIntent.confidence,
requiresConfirmation,
ambiguousInput: recognizedIntent.confidence < 0.7
}
};
}
catch (error) {
logger.error({ err: error, sessionId, input }, 'Command processing failed');
this.trackIntentSuccess('unknown', false, input, error instanceof Error ? error.message : 'Unknown error');
return this.createFailureResult(input, `Command processing failed: ${error instanceof Error ? error.message : 'Unknown error'}`, ['Please try again with a simpler command', 'Check your input for typos'], startTime);
}
}
getOrCreateContext(sessionId, partialContext) {
let context = this.contextCache.get(sessionId);
if (!context) {
context = {
sessionId,
userId: partialContext.userId,
currentProject: partialContext.currentProject,
currentTask: partialContext.currentTask,
conversationHistory: [],
userPreferences: {}
};
this.contextCache.set(sessionId, context);
}
if (partialContext.currentProject) {
context.currentProject = partialContext.currentProject;
}
if (partialContext.currentTask) {
context.currentTask = partialContext.currentTask;
}
if (partialContext.userPreferences) {
Object.assign(context.userPreferences, partialContext.userPreferences);
}
return context;
}
buildRecognitionContext(context) {
return {
currentProject: context.currentProject,
currentTask: context.currentTask,
recentIntents: context.conversationHistory.slice(-5).map(h => h.intent),
userPreferences: context.userPreferences,
sessionId: context.sessionId
};
}
updateCommandHistory(sessionId, intent) {
let history = this.commandHistory.get(sessionId) || [];
history.push(intent);
if (history.length > this.config.maxHistoryEntries) {
history = history.slice(-this.config.maxHistoryEntries);
}
this.commandHistory.set(sessionId, history);
const context = this.contextCache.get(sessionId);
if (context) {
context.conversationHistory = history;
}
}
async validateCommand(intent, context) {
const errors = [];
const warnings = [];
const suggestions = [];
const normalizedParams = {};
for (const entity of intent.entities) {
const paramName = this.mapEntityTypeToParamName(entity.type);
normalizedParams[paramName] = entity.value;
}
switch (intent.intent) {
case 'create_project':
return this.validateCreateProject(intent, normalizedParams, errors, warnings, suggestions);
case 'create_task':
return this.validateCreateTask(intent, context, normalizedParams, errors, warnings, suggestions);
case 'list_projects':
case 'list_tasks':
return this.validateListCommand(intent, normalizedParams, errors, warnings, suggestions);
case 'run_task':
return this.validateRunTask(intent, context, normalizedParams, errors, warnings, suggestions);
case 'check_status':
return this.validateStatusCheck(intent, context, normalizedParams, errors, warnings, suggestions);
case 'decompose_task':
return this.validateDecomposeTask(intent, context, normalizedParams, errors, warnings, suggestions);
case 'decompose_epic':
return this.validateDecomposeEpic(intent, context, normalizedParams, errors, warnings, suggestions);
case 'decompose_project':
return this.validateDecomposeProject(intent, context, normalizedParams, errors, warnings, suggestions);
case 'search_files':
return this.validateSearchFiles(intent, context, normalizedParams, errors, warnings, suggestions);
case 'search_content':
return this.validateSearchContent(intent, context, normalizedParams, errors, warnings, suggestions);
case 'parse_prd':
case 'parse_tasks':
case 'import_artifact':
return this.validateArtifactOperation(intent, context, normalizedParams, errors, warnings, suggestions);
case 'update_project':
return this.validateUpdateProject(intent, context, normalizedParams, errors, warnings, suggestions);
case 'assign_task':
return this.validateAssignTask(intent, context, normalizedParams, errors, warnings, suggestions);
default:
errors.push(`Unsupported intent: ${intent.intent}`);
suggestions.push('Try using a supported command like create, list, run, status, or decompose');
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams
};
}
validateCreateProject(intent, params, errors, warnings, suggestions) {
if (!params.projectName) {
errors.push('Project name is required');
suggestions.push('Try: "Create a project called MyApp"');
}
else {
params.projectName = String(params.projectName).trim();
if (!/^[a-zA-Z0-9\-_\s]+$/.test(String(params.projectName))) {
warnings.push('Project name contains special characters that may cause issues');
suggestions.push('Consider using only letters, numbers, hyphens, and underscores');
}
}
if (!params.description) {
params.description = `Project: ${params.projectName}`;
warnings.push('No description provided, using default');
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
validateCreateTask(intent, context, params, errors, warnings, suggestions) {
const hasTaskTitle = params.taskTitle || params.taskName ||
(params.words && Array.isArray(params.words) && params.words.length > 0);
if (!hasTaskTitle) {
errors.push('Task title is required');
suggestions.push('Try: "Create a task for implementing authentication"');
}
else {
if (params.words && Array.isArray(params.words) && params.words.length > 0) {
params.taskTitle = params.words.join(' ');
warnings.push('Task title extracted from input');
}
}
if (!params.projectName && context.currentProject) {
params.projectName = context.currentProject;
warnings.push(`Using current project: ${context.currentProject}`);
}
else if (!params.projectName) {
errors.push('Project name is required when no current project is set');
suggestions.push('Specify a project or set a current project first');
}
if (params.priority) {
const validPriorities = ['low', 'medium', 'high', 'critical'];
if (!validPriorities.includes(String(params.priority).toLowerCase())) {
warnings.push('Invalid priority, using medium as default');
params.priority = 'medium';
}
else {
params.priority = String(params.priority).toLowerCase();
}
}
else {
params.priority = 'medium';
}
if (params.type) {
const validTypes = ['development', 'testing', 'documentation', 'research', 'bug', 'feature'];
if (!validTypes.includes(String(params.type).toLowerCase())) {
warnings.push('Invalid task type, using development as default');
params.type = 'development';
}
else {
params.type = String(params.type).toLowerCase();
}
}
else {
params.type = 'development';
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
validateListCommand(intent, params, errors, warnings, suggestions) {
if (params.status) {
const validStatuses = ['pending', 'in_progress', 'completed', 'blocked', 'cancelled'];
if (!validStatuses.includes(String(params.status).toLowerCase())) {
warnings.push('Invalid status filter, showing all items');
delete params.status;
}
else {
params.status = String(params.status).toLowerCase();
}
}
if (params.timeframe) {
const validTimeframes = ['today', 'tomorrow', 'this week', 'next week', 'this month'];
const timeframeStr = String(params.timeframe).toLowerCase();
if (!validTimeframes.includes(timeframeStr) && !/^\d{4}-\d{2}-\d{2}$/.test(timeframeStr)) {
warnings.push('Invalid timeframe filter, showing all items');
delete params.timeframe;
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
validateRunTask(intent, context, params, errors, warnings, suggestions) {
const hasTaskId = params.taskId || params.taskTitle ||
(params.numbers && Array.isArray(params.numbers) && params.numbers.length > 0);
if (!hasTaskId) {
errors.push('Task ID or task title is required');
suggestions.push('Try: "Run task 123" or "Run the authentication task"');
}
else {
if (params.numbers && Array.isArray(params.numbers) && params.numbers.length > 0) {
params.taskId = `task-${params.numbers[0]}`;
warnings.push('Task ID extracted from number');
}
if (params.taskTitle && !params.taskId) {
warnings.push('Task title provided, will attempt to resolve to task ID');
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
validateStatusCheck(intent, context, params, errors, warnings, suggestions) {
if (!params.projectName && !params.taskId && context.currentProject) {
params.projectName = context.currentProject;
warnings.push(`Checking status of current project: ${context.currentProject}`);
}
if (!params.projectName && !params.taskId) {
warnings.push('No specific target, showing general status');
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
validateDecomposeTask(intent, context, params, errors, warnings, suggestions) {
const hasTaskId = params.taskId || params.taskTitle;
if (!hasTaskId) {
errors.push('Task ID or task title is required for decomposition');
suggestions.push('Try: "Decompose task T001" or "Break down the authentication task"');
}
else {
if (params.taskTitle && !params.taskId) {
warnings.push('Task title provided, will attempt to resolve to task ID');
}
}
if (params.decompositionScope) {
const validScopes = ['development tasks', 'implementation steps', 'technical tasks', 'all aspects'];
const scope = String(params.decompositionScope).toLowerCase();
if (!validScopes.some(validScope => scope.includes(validScope))) {
warnings.push('Decomposition scope may be too broad or unclear');
suggestions.push('Consider specifying: development tasks, implementation steps, or technical tasks');
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
validateDecomposeEpic(intent, context, params, errors, warnings, suggestions) {
const hasEpicId = params.epicId || params.epicTitle;
if (!hasEpicId) {
errors.push('Epic ID or epic title is required for decomposition');
suggestions.push('Try: "Decompose epic E001" or "Break down the authentication epic"');
}
else {
if (params.epicTitle && !params.epicId) {
warnings.push('Epic title provided, will attempt to resolve to epic ID');
}
}
if (params.decompositionScope) {
const validScopes = ['development tasks', 'implementation steps', 'technical tasks', 'all aspects'];
const scope = String(params.decompositionScope).toLowerCase();
if (!validScopes.some(validScope => scope.includes(validScope))) {
warnings.push('Decomposition scope may be too broad or unclear');
suggestions.push('Consider specifying: development tasks, implementation steps, or technical tasks');
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
validateDecomposeProject(intent, context, params, errors, warnings, suggestions) {
if (!params.projectName) {
errors.push('Project name or ID is required for decomposition');
suggestions.push('Try: "Decompose project MyApp" or "Break down project PID-001"');
}
else {
params.projectName = String(params.projectName).trim();
}
if (params.decompositionScope) {
const validScopes = ['development tasks', 'implementation phases', 'technical components', 'all aspects'];
const scope = String(params.decompositionScope).toLowerCase();
if (!validScopes.some(validScope => scope.includes(validScope))) {
warnings.push('Decomposition scope may be too broad or unclear');
suggestions.push('Consider specifying: development tasks, implementation phases, or technical components');
}
}
if (params.decompositionDetails) {
const details = String(params.decompositionDetails);
if (details.length > 1000) {
warnings.push('Decomposition details are very long, may affect processing performance');
}
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
validateSearchFiles(intent, context, params, errors, warnings, suggestions) {
if (!params.searchPattern && !params.fileName) {
errors.push('Search pattern or file name is required');
suggestions.push('Try: "Search for *.js files" or "Find config files"');
}
else {
if (params.searchPattern) {
params.searchPattern = String(params.searchPattern).trim();
if (String(params.searchPattern).length < 2) {
warnings.push('Search pattern is very short, may return too many results');
}
}
if (params.fileName) {
params.fileName = String(params.fileName).trim();
}
}
if (params.fileExtensions) {
const extensions = Array.isArray(params.fileExtensions)
? params.fileExtensions
: [params.fileExtensions];
params.fileExtensions = extensions.map(ext => String(ext).startsWith('.') ? String(ext) : `.${ext}`);
}
if (!params.searchDirectory) {
params.searchDirectory = context.currentProject || '.';
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
validateSearchContent(intent, context, params, errors, warnings, suggestions) {
if (!params.searchQuery) {
errors.push('Search query is required');
suggestions.push('Try: "Search content for authentication" or "Find code containing API"');
}
else {
params.searchQuery = String(params.searchQuery).trim();
if (String(params.searchQuery).length < 3) {
warnings.push('Search query is very short, may return too many results');
suggestions.push('Consider using a more specific search term');
}
}
if (params.useRegex && params.regexPattern) {
try {
new RegExp(String(params.regexPattern));
params.regexPattern = String(params.regexPattern);
}
catch {
errors.push('Invalid regular expression pattern');
suggestions.push('Check your regex syntax or use simple text search');
}
}
if (params.caseSensitive !== undefined) {
params.caseSensitive = Boolean(params.caseSensitive);
}
else {
params.caseSensitive = false;
}
if (!params.searchDirectory) {
params.searchDirectory = context.currentProject || '.';
}
if (params.fileExtensions) {
const extensions = Array.isArray(params.fileExtensions)
? params.fileExtensions
: [params.fileExtensions];
params.fileExtensions = extensions.map(ext => String(ext).startsWith('.') ? String(ext) : `.${ext}`);
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
validateArtifactOperation(intent, context, params, errors, warnings, suggestions) {
if (!params.projectName) {
errors.push('Project name is required for artifact operations');
suggestions.push('Try: "Parse PRD for project MyApp" or "Import tasks for PID-001"');
}
else {
params.projectName = String(params.projectName).trim();
}
if (!params.filePath && !params.artifactContent && !params.artifactData) {
if (intent.intent === 'parse_prd') {
errors.push('PRD file path or content is required');
suggestions.push('Try: "Parse PRD from requirements.md" or provide PRD content');
}
else if (intent.intent === 'parse_tasks') {
errors.push('Task list file path or content is required');
suggestions.push('Try: "Parse tasks from todo.md" or provide task list content');
}
else if (intent.intent === 'import_artifact') {
errors.push('Artifact file path or content is required');
suggestions.push('Try: "Import artifact from data.json" or provide artifact content');
}
}
else {
if (params.filePath) {
params.filePath = String(params.filePath).trim();
if (!String(params.filePath).includes('.')) {
warnings.push('File path may be missing extension');
}
const filePath = String(params.filePath).toLowerCase();
if (intent.intent === 'parse_prd') {
if (!filePath.endsWith('.md') && !filePath.endsWith('.txt') && !filePath.endsWith('.doc')) {
warnings.push('PRD files are typically .md, .txt, or .doc files');
}
}
else if (intent.intent === 'parse_tasks') {
if (!filePath.endsWith('.md') && !filePath.endsWith('.txt') && !filePath.endsWith('.json')) {
warnings.push('Task list files are typically .md, .txt, or .json files');
}
}
}
if (params.artifactContent) {
params.artifactContent = String(params.artifactContent).trim();
if (String(params.artifactContent).length < 10) {
warnings.push('Artifact content seems very short');
}
}
}
if (params.artifactType) {
const validTypes = ['prd', 'requirements', 'tasks', 'todo', 'specifications', 'documentation'];
const artifactType = String(params.artifactType).toLowerCase();
if (!validTypes.includes(artifactType)) {
warnings.push(`Artifact type '${artifactType}' may not be recognized`);
suggestions.push(`Consider using: ${validTypes.join(', ')}`);
}
params.artifactType = artifactType;
}
else {
if (intent.intent === 'parse_prd') {
params.artifactType = 'prd';
}
else if (intent.intent === 'parse_tasks') {
params.artifactType = 'tasks';
}
}
if (params.parseOptions) {
const options = params.parseOptions;
if (options.extractTasks !== undefined) {
options.extractTasks = Boolean(options.extractTasks);
}
if (options.extractRequirements !== undefined) {
options.extractRequirements = Boolean(options.extractRequirements);
}
if (options.preserveStructure !== undefined) {
options.preserveStructure = Boolean(options.preserveStructure);
}
params.parseOptions = options;
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
validateUpdateProject(intent, context, params, errors, warnings, suggestions) {
if (!params.projectName) {
errors.push('Project name is required for update operation');
suggestions.push('Try: "Update project MyApp status to completed"');
}
else {
params.projectName = String(params.projectName).trim();
}
const updateableFields = ['status', 'priority', 'description', 'assignee', 'deadline'];
const hasUpdates = updateableFields.some(field => params[field] !== undefined);
if (!hasUpdates) {
warnings.push('No specific updates provided, will prompt for changes');
suggestions.push('Try specifying what to update: status, priority, description, assignee, or deadline');
}
if (params.status) {
const validStatuses = ['pending', 'in_progress', 'completed', 'blocked', 'cancelled'];
const status = String(params.status).toLowerCase().replace(/\s+/g, '_');
if (!validStatuses.includes(status)) {
errors.push(`Invalid status: ${params.status}`);
suggestions.push(`Valid statuses: ${validStatuses.join(', ')}`);
}
else {
params.status = status;
}
}
if (params.priority) {
const validPriorities = ['low', 'medium', 'high', 'critical'];
const priority = String(params.priority).toLowerCase();
if (!validPriorities.includes(priority)) {
warnings.push(`Priority '${params.priority}' may not be recognized`);
suggestions.push(`Valid priorities: ${validPriorities.join(', ')}`);
}
else {
params.priority = priority;
}
}
if (params.assignee) {
params.assignee = String(params.assignee).trim();
if (String(params.assignee).length < 2) {
warnings.push('Assignee name seems very short');
}
}
if (params.description) {
params.description = String(params.description).trim();
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
validateAssignTask(intent, context, params, errors, warnings, suggestions) {
if (!params.taskId && !params.taskTitle) {
errors.push('Task ID or title is required for assignment');
suggestions.push('Try: "Assign task T-001 to John" or "Assign authentication task to Sarah"');
}
else {
if (params.taskId) {
params.taskId = String(params.taskId).trim();
}
if (params.taskTitle) {
params.taskTitle = String(params.taskTitle).trim();
warnings.push('Task title provided, will attempt to resolve to task ID');
}
}
if (!params.assignee) {
errors.push('Assignee is required for task assignment');
suggestions.push('Try: "Assign task to John" or "Assign to team-frontend"');
}
else {
params.assignee = String(params.assignee).trim();
if (String(params.assignee).length < 2) {
errors.push('Assignee name is too short');
}
const assignee = String(params.assignee).toLowerCase();
if (assignee.includes('team-') || assignee.includes('group-')) {
warnings.push('Assigning to a team/group - ensure the team exists');
}
}
if (params.force !== undefined) {
params.force = Boolean(params.force);
}
else {
params.force = false;
}
if (context.currentTask && params.taskId === context.currentTask) {
warnings.push('Task may already be assigned, use force option if needed');
}
return {
isValid: errors.length === 0,
errors,
warnings,
suggestions,
normalizedParams: params
};
}
shouldRequireConfirmation(intent, validation) {
if (intent.confidence < this.config.autoExecuteThreshold) {
return true;
}
if (validation.warnings.length > 0) {
return true;
}
const destructiveIntents = ['delete_project', 'delete_task', 'archive_project'];
if (destructiveIntents.includes(intent.intent)) {
return true;
}
return false;
}
async mapIntentToToolParams(intent, normalizedParams) {
const toolParams = {};
switch (intent.intent) {
case 'create_project':
toolParams.command = 'create';
toolParams.projectName = normalizedParams.projectName;
toolParams.description = normalizedParams.description;
toolParams.options = {
priority: normalizedParams.priority || 'medium',
type: normalizedParams.type || 'development'
};
break;
case 'create_task':
toolParams.command = 'create';
toolParams.projectName = normalizedParams.projectName;
toolParams.description = normalizedParams.taskTitle;
toolParams.options = {
priority: normalizedParams.priority || 'medium',
type: normalizedParams.type || 'development',
assignee: normalizedParams.assignee
};
break;
case 'list_projects':
toolParams.command = 'list';
toolParams.options = {
type: 'projects',
status: normalizedParams.status,
timeframe: normalizedParams.timeframe
};
break;
case 'list_tasks':
toolParams.command = 'list';
toolParams.options = {
type: 'tasks',
status: normalizedParams.status,
timeframe: normalizedParams.timeframe,
assignee: normalizedParams.assignee,
project: normalizedParams.projectName
};
break;
case 'run_task':
toolParams.command = 'run';
toolParams.taskId = normalizedParams.taskId || normalizedParams.taskTitle;
toolParams.options = {
force: normalizedParams.force || false
};
break;
case 'check_status':
toolParams.command = 'status';
toolParams.projectName = normalizedParams.projectName;
toolParams.taskId = normalizedParams.taskId;
toolParams.options = {
detailed: true
};
break;
case 'decompose_task':
toolParams.command = 'decompose';
toolParams.taskId = normalizedParams.taskId || normalizedParams.taskTitle;
toolParams.description = normalizedParams.description;
toolParams.options = {
scope: normalizedParams.decompositionScope,
details: normalizedParams.decompositionDetails,
force: normalizedParams.force || false
};
break;
case 'decompose_epic':
toolParams.command = 'decompose';
toolParams.epicId = normalizedParams.epicId || normalizedParams.epicTitle;
toolParams.description = normalizedParams.description;
toolParams.options = {
scope: normalizedParams.decompositionScope,
details: normalizedParams.decompositionDetails,
force: normalizedParams.force || false
};
break;
case 'decompose_project':
toolParams.command = 'decompose';
toolParams.projectName = normalizedParams.projectName;
toolParams.description = normalizedParams.description;
toolParams.options = {
scope: normalizedParams.decompositionScope,
details: normalizedParams.decompositionDetails,
force: normalizedParams.force || false
};
break;
case 'open_project':
toolParams.command = 'open';
toolParams.projectName = normalizedParams.projectName;
toolParams.options = {};
break;
case 'update_project':
toolParams.command = 'update';
toolParams.projectName = normalizedParams.projectName;
toolParams.updates = normalizedParams.updates || {};
toolParams.options = {};
break;
case 'search_files':
toolParams.command = 'search';
toolParams.searchType = 'files';
toolParams.searchPattern = normalizedParams.searchPattern || normalizedParams.fileName;
toolParams.options = {
directory: normalizedParams.searchDirectory,
extensions: normalizedParams.fileExtensions,
recursive: true
};
break;
case 'search_content':
toolParams.command = 'search';
toolParams.searchType = 'content';
toolParams.searchQuery = normalizedParams.searchQuery;
toolParams.options = {
directory: normalizedParams.searchDirectory,
extensions: normalizedParams.fileExtensions,
useRegex: normalizedParams.useRegex || false,
caseSensitive: normalizedParams.caseSensitive || false,
regexPattern: normalizedParams.regexPattern
};
break;
case 'parse_prd':
toolParams.command = 'parse';
toolParams.artifactType = 'prd';
toolParams.projectName = normalizedParams.projectName;
toolParams.filePath = normalizedParams.filePath;
toolParams.artifactContent = normalizedParams.artifactContent;
toolParams.options = normalizedParams.parseOptions || {};
break;
case 'parse_tasks':
toolParams.command = 'parse';
toolParams.artifactType = 'tasks';
toolParams.projectName = normalizedParams.projectName;
toolParams.filePath = normalizedParams.filePath;
toolParams.artifactContent = normalizedParams.artifactContent;
toolParams.options = normalizedParams.parseOptions || {};
break;
case 'import_artifact':
toolParams.command = 'import';
toolParams.artifactType = normalizedParams.artifactType || 'generic';
toolParams.projectName = normalizedParams.projectName;
toolParams.filePath = normalizedParams.filePath;
toolParams.artifactContent = normalizedParams.artifactContent;
toolParams.options = normalizedParams.parseOptions || {};
break;
case 'assign_task':
toolParams.command = 'assign';
toolParams.taskId = normalizedParams.taskId || normalizedParams.taskTitle;
toolParams.assignee = normalizedParams.assignee;
toolParams.options = {
force: normalizedParams.force || false
};
break;
case 'refine_task':
toolParams.command = 'refine';
toolParams.taskId = normalizedParams.taskId || normalizedParams.taskTitle;
toolParams.refinements = normalizedParams.description || normalizedParams.refinements;
toolParams.options = {
scope: normalizedParams.refinementScope
};
break;
case 'get_help':
toolParams.command = 'help';
toolParams.topic = normalizedParams.helpTopic || normalizedParams.topic;
toolParams.options = {
detailed: true
};
break;
case 'unrecognized_intent':
case 'clarification_needed':
case 'unknown':
toolParams.command = 'fallback';
toolParams.originalInput = normalizedParams.originalInput;
toolParams.suggestions = normalizedParams.suggestions || [];
toolParams.options = {
intent: intent.intent,
confidence: intent.confidence
};
break;
default:
throw new Error(`Unsupported intent for tool mapping: ${intent.intent}`);
}
return toolParams;
}
createFailureResult(input, message, suggestions, startTime) {
return {
success: false,
intent: {
intent: 'unknown',
confidence: 0,
confidenceLevel: 'very_low',
entities: [],
originalInput: input,
processedInput: input.toLowerCase().trim(),
alternatives: [],
metadata: {
processingTime: Date.now() - startTime,
method: 'pattern',
timestamp: new Date()
}
},
toolParams: {},
validationErrors: [message],
suggestions,
metadata: {
processingTime: Date.now() - startTime,
confidence: 0,
requiresConfirmation: false,
ambiguousInput: true
}
};
}
createValidationErrorResult(intent, validation, startTime) {
return {
success: false,
intent,
toolParams: {},
validationErrors: validation.errors,
suggestions: validation.suggestions,
metadata: {
processingTime: Date.now() - startTime,
confidence: intent.confidence,
requiresConfirmation: false,
ambiguousInput: intent.confidence < 0.7
}
};
}
mapEntityTypeToParamName(entityType) {
const mapping = {
'project_name': 'projectName',
'task_name': 'taskName',
'task_title': 'taskTitle',
'task_id': 'taskId',
'description': 'description',
'priority': 'priority',
'type': 'type',
'status': 'status',
'assignee': 'assignee',
'timeframe': 'timeframe',
'features': 'features',
'decomposition_scope': 'decompositionScope',
'decomposition_details': 'decompositionDetails'
};
return mapping[entityType] || entityType;
}
convertEntitiesToArray(entities) {
if (Array.isArray(entities)) {
return entities.map(entity => ({
type: entity.type || 'unknown',
value: String(entity.value || ''),
confidence: entity.confidence || 1.0
}));
}
const entityArray = [];
for (const [type, value] of Object.entries(entities)) {
if (value !== undefined && value !== null) {
entityArray.push({
type,
value: String(value),
confidence: 1.0
});
}
}
return entityArray;
}
updateConfig(newConfig) {
this.config = { ...this.config, ...newConfig };
logger.info({ config: this.config }, 'Command Gateway configuration updated');
}
getConfig() {
return { ...this.config };
}
clearHistory(sessionId) {
this.commandHistory.delete(sessionId);
this.contextCache.delete(sessionId);
logger.info({ sessionId }, 'Command history cleared');
}
getHistory(sessionId) {
return this.commandHistory.get(sessionId) || [];
}
getStatistics() {
const totalSessions = this.commandHistory.size;
let totalCommands = 0;
let successfulCommands = 0;
for (const history of this.commandHistory.values()) {
totalCommands += history.length;
successfulCommands += history.filter(h => h.confidence >= 0.7).length;
}
return {
totalSessions,
totalCommands,
averageProcessingTime: this.calculateAverageProcessingTime(),
successRate: totalCommands > 0 ? successfulCommands / totalCommands : 0
};
}
calculateAverageProcessingTime() {
const allSessions = Array.from(this.sessionMetrics.values());
if (allSessions.length === 0) {
return 0;
}
const totalProcessingTime = allSessions.reduce((sum, session) => {
return sum + session.commands.reduce((cmdSum, cmd) => cmdSum + cmd.processingTime, 0);
}, 0);
const totalCommands = allSessions.reduce((sum, session) => sum + session.commands.length, 0);
return totalCommands > 0 ? totalProcessingTime / totalCommands : 0;
}
trackIntentSuccess(intent, success, input, error) {
const metrics = this.intentSuccessMetrics.get(intent) || {
total: 0,
successful: 0,
failed: 0,
lastUpdated: new Date(),
recentFailures: []
};
metrics.total++;
metrics.lastUpdated = new Date();
if (success) {
metrics.successful++;
}
else {
metrics.failed++;
metrics.recentFailures.push({
input: input.substring(0, 100),
error: error || 'Unknown error',
timestamp: new Date()
});
if (metrics.recentFailures.length > 10) {
metrics.recentFailures = metrics.recentFailures.slice(-10);
}
}
this.intentSuccessMetrics.set(intent, metrics);
const failureRate = metrics.failed / metrics.total;
if (metrics.total >= 5 && failureRate > 0.5) {
logger.warn({
intent,
total: metrics.total,
successful: metrics.successful,
failed: metrics.failed,
failureRate: Math.round(failureRate * 100),
recentFailures: metrics.recentFailures.slice(-3)
}, 'High intent recognition failure rate detected');
}
}
getIntentSuccessRates() {
const stats = {};
for (const [intent, metrics] of this.intentSuccessMetrics.entries()) {
const successRate = metrics.total > 0 ? metrics.successful / metrics.total : 0;
const failureRate = metrics.total > 0 ? metrics.failed / metrics.total : 0;
stats[intent] = {
intent,
total: metrics.total,
successRate: Math.round(successRate * 100) / 100,
failureRate: Math.round(failureRate * 100) / 100,
lastUpdated: metrics.lastUpdated
};
if (failureRate > 0.3 && metrics.recentFailures.length > 0) {
stats[intent].recentFailures = metrics.recentFailures.slice(-5);
}
}
return stats;
}
getSystemHealthMetrics() {
let totalCommands = 0;
let totalSuccessful = 0;
const problematicIntents = [];
let lastUpdated = new Date(0);
for (const [intent, metrics] of this.intentSuccessMetrics.entries()) {
totalCommands += metrics.total;
totalSuccessful += metrics.successful;
if (metrics.lastUpdated > lastUpdated) {
lastUpdated = metrics.lastUpdated;
}
const failureRate = metrics.total > 0 ? metrics.failed / metrics.total : 0;
if (metrics.total >= 3 && failureRate > 0.3) {
problematicIntents.push(intent);
}
}
const overallSuccessRate = totalCommands > 0 ? totalSuccessful / totalCommands : 0;
const intentCoverage = this.intentSuccessMetrics.size;
return {
totalCommands,
overallSuccessRate: Math.round(overallSuccessRate * 100) / 100,
intentCoverage,
problematicIntents,
lastUpdated
};
}
}