vibe-coder-mcp
Version:
Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.
617 lines (616 loc) • 22.5 kB
JavaScript
import { extractProjectFromContext } from '../../tools/vibe-task-manager/utils/context-extractor.js';
import logger from '../../logger.js';
export class ContextAwareParameterExtractor {
toolSpecs = {
'research-manager': {
required: ['topic'],
optional: ['depth', 'focus', 'sources'],
types: {
topic: 'string',
depth: 'string',
focus: 'array',
sources: 'array'
},
defaults: {
depth: 'comprehensive',
sources: ['technical', 'documentation']
}
},
'prd-generator': {
required: ['product', 'feature'],
optional: ['stakeholders', 'timeline', 'scope'],
types: {
product: 'string',
feature: 'string',
stakeholders: 'array',
timeline: 'string',
scope: 'string'
},
defaults: {
scope: 'MVP'
},
contextFields: {
currentProject: 'product'
}
},
'user-stories-generator': {
required: ['feature'],
optional: ['persona', 'acceptance_criteria', 'priority'],
types: {
feature: 'string',
persona: 'string',
acceptance_criteria: 'boolean',
priority: 'string'
},
defaults: {
acceptance_criteria: true,
priority: 'medium'
}
},
'task-list-generator': {
required: ['requirement'],
optional: ['breakdown_level', 'include_dependencies', 'format'],
types: {
requirement: 'string',
breakdown_level: 'string',
include_dependencies: 'boolean',
format: 'string'
},
defaults: {
breakdown_level: 'detailed',
include_dependencies: true,
format: 'markdown'
}
},
'fullstack-starter-kit-generator': {
required: ['project_name'],
optional: ['frontend', 'backend', 'database', 'features'],
types: {
project_name: 'string',
frontend: 'string',
backend: 'string',
database: 'string',
features: 'array'
},
defaults: {
frontend: 'React',
backend: 'Node.js',
database: 'PostgreSQL'
},
contextFields: {
currentProject: 'project_name'
}
},
'rules-generator': {
required: ['project_type'],
optional: ['language', 'framework', 'style_guide', 'linting'],
types: {
project_type: 'string',
language: 'string',
framework: 'string',
style_guide: 'string',
linting: 'boolean'
},
defaults: {
linting: true
}
},
'map-codebase': {
required: ['path'],
optional: ['include_tests', 'max_depth', 'file_types', 'output_format'],
types: {
path: 'string',
include_tests: 'boolean',
max_depth: 'number',
file_types: 'array',
output_format: 'string'
},
defaults: {
path: '.',
include_tests: false,
max_depth: 5,
output_format: 'mermaid'
}
},
'curate-context': {
required: ['task'],
optional: ['codebase_path', 'relevance_threshold', 'max_files'],
types: {
task: 'string',
codebase_path: 'string',
relevance_threshold: 'number',
max_files: 'number'
},
defaults: {
codebase_path: '.',
relevance_threshold: 0.7,
max_files: 20
}
},
'run-workflow': {
required: ['workflowName'],
optional: ['workflowInput', 'async_execution', 'notification'],
types: {
workflowName: 'string',
workflowInput: 'object',
async_execution: 'boolean',
notification: 'boolean'
},
defaults: {
async_execution: false,
notification: true
}
},
'get-job-result': {
required: ['jobId'],
optional: ['wait_timeout', 'include_logs'],
types: {
jobId: 'string',
wait_timeout: 'number',
include_logs: 'boolean'
},
defaults: {
wait_timeout: 30000,
include_logs: false
}
},
'register-agent': {
required: ['agentId'],
optional: ['capabilities', 'transport', 'priority'],
types: {
agentId: 'string',
capabilities: 'array',
transport: 'string',
priority: 'number'
},
defaults: {
transport: 'stdio',
priority: 5
}
},
'get-agent-tasks': {
required: ['agentId'],
optional: ['capabilities', 'limit', 'timeout'],
types: {
agentId: 'string',
capabilities: 'array',
limit: 'number',
timeout: 'number'
},
defaults: {
limit: 10,
timeout: 5000
}
},
'submit-task-response': {
required: ['taskId', 'status'],
optional: ['result', 'error_message', 'completion_metadata'],
types: {
taskId: 'string',
status: 'string',
result: 'object',
error_message: 'string',
completion_metadata: 'object'
},
defaults: {}
},
'vibe-task-manager': {
required: ['command'],
optional: ['projectName', 'taskId', 'description', 'options'],
types: {
command: 'string',
projectName: 'string',
taskId: 'string',
description: 'string',
options: 'object'
},
defaults: {},
contextFields: {
currentProject: 'projectName',
currentTask: 'taskId'
}
},
'process-request': {
required: ['request'],
optional: ['context', 'preferences', 'confirmation_threshold'],
types: {
request: 'string',
context: 'object',
preferences: 'object',
confirmation_threshold: 'number'
},
defaults: {
confirmation_threshold: 0.8
}
}
};
async extractParameters(intent, input, context, toolCandidates) {
try {
const bestTool = toolCandidates[0]?.tool || 'process-request';
const spec = this.toolSpecs[bestTool];
if (!spec) {
logger.debug({ tool: bestTool }, 'No parameter spec found for tool, using basic extraction');
return this.performBasicExtraction(intent, input, context);
}
const results = await Promise.all([
this.extractFromContext(spec, context),
this.extractFromEntities(spec, intent.entities),
this.extractFromPatterns(spec, input, bestTool),
this.extractFromInference(spec, input, context)
]);
const parameters = this.combineExtractionResults(results, spec);
const enrichedParameters = await this.validateAndEnrichParameters(parameters, spec, context, bestTool);
logger.debug({
tool: bestTool,
extractedParams: Object.keys(enrichedParameters),
sources: results.map(r => r.source)
}, 'Parameter extraction completed');
return enrichedParameters;
}
catch (error) {
logger.error({ err: error, intent: intent.intent }, 'Parameter extraction failed');
return this.performBasicExtraction(intent, input, context);
}
}
async mapToToolParameters(toolName, intent, extractedParams, context) {
const spec = this.toolSpecs[toolName];
if (!spec) {
return { ...extractedParams, _intent: intent.intent };
}
const mappedParams = {};
for (const param of spec.required) {
if (extractedParams[param] !== undefined) {
mappedParams[param] = this.castParameterType(extractedParams[param], spec.types[param]);
}
else {
const alternative = this.findAlternativeParameter(param, extractedParams);
if (alternative !== null) {
mappedParams[param] = this.castParameterType(alternative, spec.types[param]);
}
}
}
for (const param of spec.optional) {
if (extractedParams[param] !== undefined) {
mappedParams[param] = this.castParameterType(extractedParams[param], spec.types[param]);
}
else {
if (spec.defaults[param] !== undefined) {
mappedParams[param] = spec.defaults[param];
}
}
}
await this.addToolSpecificEnrichments(toolName, mappedParams, context, intent);
return mappedParams;
}
async extractFromContext(spec, context) {
const parameters = {};
const confidence = 0.9;
try {
if (spec.contextFields) {
for (const [contextField, paramName] of Object.entries(spec.contextFields)) {
const contextValue = context[contextField];
if (contextValue) {
parameters[paramName] = contextValue;
}
}
}
if (spec.required.includes('path') || spec.optional.includes('path')) {
try {
const projectContext = await extractProjectFromContext({
sessionId: context.sessionId,
currentProject: context.currentProject,
config: {},
taskManagerConfig: {}
});
if (projectContext.confidence > 0.7) {
parameters.path = process.cwd();
parameters.projectName = projectContext.projectName;
}
}
catch (error) {
logger.debug({ err: error }, 'Project context extraction failed');
}
}
if (context.userPreferences && Object.keys(context.userPreferences).length > 0) {
for (const [key, value] of Object.entries(context.userPreferences)) {
if (spec.optional.includes(key) || spec.required.includes(key)) {
parameters[key] = value;
}
}
}
return {
parameters,
confidence,
source: 'context',
suggestions: []
};
}
catch (error) {
logger.debug({ err: error }, 'Context extraction failed');
return {
parameters: {},
confidence: 0,
source: 'context',
suggestions: []
};
}
}
async extractFromEntities(spec, entities) {
const parameters = {};
const suggestions = [];
let confidence = 0.8;
for (const entity of entities) {
const paramName = this.mapEntityTypeToParameter(entity.type, spec);
if (paramName && (spec.required.includes(paramName) || spec.optional.includes(paramName))) {
parameters[paramName] = entity.value;
if (entity.confidence < 0.7) {
confidence *= 0.9;
}
}
}
return {
parameters,
confidence,
source: 'entities',
suggestions
};
}
async extractFromPatterns(spec, input, toolName) {
const parameters = {};
const suggestions = [];
const confidence = 0.7;
switch (toolName) {
case 'research-manager':
Object.assign(parameters, this.extractResearchPatterns(input));
break;
case 'prd-generator':
Object.assign(parameters, this.extractPRDPatterns(input));
break;
case 'fullstack-starter-kit-generator':
Object.assign(parameters, this.extractStarterKitPatterns(input));
break;
case 'map-codebase':
Object.assign(parameters, this.extractCodebasePatterns(input));
break;
}
return {
parameters,
confidence,
source: 'patterns',
suggestions
};
}
async extractFromInference(spec, input, context) {
const parameters = {};
const suggestions = [];
const confidence = 0.6;
for (const param of spec.required) {
if (!parameters[param]) {
const inferred = await this.inferParameter(param, input, context, spec);
if (inferred) {
parameters[param] = inferred.value;
suggestions.push(inferred.suggestion);
}
}
}
return {
parameters,
confidence,
source: 'inference',
suggestions
};
}
combineExtractionResults(results, spec) {
const combined = {};
const priorityOrder = ['defaults', 'inference', 'patterns', 'entities', 'context'];
Object.assign(combined, spec.defaults);
for (const priority of priorityOrder) {
const result = results.find(r => r.source === priority);
if (result) {
Object.assign(combined, result.parameters);
}
}
return combined;
}
async validateAndEnrichParameters(parameters, spec, context, toolName) {
const enriched = { ...parameters };
for (const required of spec.required) {
if (enriched[required] === undefined || enriched[required] === null || enriched[required] === '') {
const fallback = await this.provideFallbackValue(required, toolName, context);
if (fallback) {
enriched[required] = fallback;
}
}
}
for (const [param, type] of Object.entries(spec.types)) {
if (enriched[param] !== undefined) {
enriched[param] = this.castParameterType(enriched[param], type);
}
}
return enriched;
}
performBasicExtraction(intent, input, context) {
const parameters = {
request: intent.originalInput,
intent: intent.intent,
sessionId: context.sessionId
};
for (const entity of intent.entities) {
parameters[entity.type] = entity.value;
}
if (context.currentProject) {
parameters.currentProject = context.currentProject;
}
if (context.currentTask) {
parameters.currentTask = context.currentTask;
}
return parameters;
}
mapEntityTypeToParameter(entityType, spec) {
const mappings = {
'project_name': 'project',
'product_name': 'product',
'feature_name': 'feature',
'task_name': 'task',
'file_path': 'path',
'technology': 'language',
'topic': 'topic',
'requirement': 'requirement'
};
const mapped = mappings[entityType] || entityType;
if (spec.required.includes(mapped) || spec.optional.includes(mapped)) {
return mapped;
}
return null;
}
castParameterType(value, type) {
switch (type) {
case 'string':
return String(value);
case 'number':
return typeof value === 'number' ? value : parseFloat(String(value)) || 0;
case 'boolean':
return typeof value === 'boolean' ? value :
['true', '1', 'yes', 'on'].includes(String(value).toLowerCase());
case 'array':
return Array.isArray(value) ? value : [value];
case 'object':
return typeof value === 'object' ? value : { value };
default:
return value;
}
}
findAlternativeParameter(targetParam, parameters) {
const alternatives = {
'topic': ['query', 'subject', 'research_topic'],
'product': ['project', 'application', 'system'],
'feature': ['functionality', 'capability', 'requirement'],
'path': ['directory', 'folder', 'location'],
'task': ['description', 'title', 'name']
};
const alternativeNames = alternatives[targetParam] || [];
for (const alt of alternativeNames) {
if (parameters[alt] !== undefined) {
return parameters[alt];
}
}
return null;
}
async provideFallbackValue(param, toolName, context) {
const fallbacks = {
'research-manager': {
topic: 'general technical research'
},
'map-codebase': {
path: '.'
},
'prd-generator': {
product: context.currentProject || 'New Product'
},
'fullstack-starter-kit-generator': {
project_name: context.currentProject || 'new-project'
}
};
return fallbacks[toolName]?.[param];
}
async addToolSpecificEnrichments(toolName, parameters, context, intent) {
parameters._sessionId = context.sessionId;
parameters._intent = intent.intent;
parameters._confidence = intent.confidence;
parameters._timestamp = new Date().toISOString();
switch (toolName) {
case 'vibe-task-manager':
if (!parameters.projectName && context.currentProject) {
parameters.projectName = context.currentProject;
}
break;
case 'curate-context':
if (!parameters.codebase_path) {
parameters.codebase_path = process.cwd();
}
break;
}
}
async inferParameter(param, input, context, _spec) {
switch (param) {
case 'topic': {
const topicMatch = input.match(/(?:research|about|regarding|on)\s+(.+?)(?:\s|$)/i);
if (topicMatch) {
return {
value: topicMatch[1].trim(),
suggestion: `Inferred research topic: ${topicMatch[1].trim()}`
};
}
break;
}
case 'feature': {
const featureMatch = input.match(/(?:feature|functionality|capability)\s+(.+?)(?:\s|$)/i);
if (featureMatch) {
return {
value: featureMatch[1].trim(),
suggestion: `Inferred feature: ${featureMatch[1].trim()}`
};
}
break;
}
case 'product':
if (context.currentProject) {
return {
value: context.currentProject,
suggestion: `Using current project as product: ${context.currentProject}`
};
}
break;
}
return null;
}
extractResearchPatterns(input) {
const params = {};
const topicMatch = input.match(/(?:research|study|investigate|analyze)\s+(.+?)(?:\s|$)/i);
if (topicMatch) {
params.topic = topicMatch[1].trim();
}
if (input.includes('deep') || input.includes('comprehensive') || input.includes('detailed')) {
params.depth = 'comprehensive';
}
else if (input.includes('quick') || input.includes('brief') || input.includes('overview')) {
params.depth = 'brief';
}
return params;
}
extractPRDPatterns(input) {
const params = {};
const productMatch = input.match(/(?:prd|requirements?)\s+for\s+(.+?)(?:\s|$)/i);
if (productMatch) {
params.product = productMatch[1].trim();
}
return params;
}
extractStarterKitPatterns(input) {
const params = {};
const projectMatch = input.match(/(?:create|generate|setup)\s+(?:project\s+)?(.+?)(?:\s|$)/i);
if (projectMatch) {
params.project_name = projectMatch[1].trim();
}
const techPatterns = {
frontend: /\b(react|vue|angular|svelte)\b/gi,
backend: /\b(node|express|fastify|nestjs|python|django|flask)\b/gi,
database: /\b(postgres|mysql|mongodb|sqlite|redis)\b/gi
};
for (const [key, pattern] of Object.entries(techPatterns)) {
const match = input.match(pattern);
if (match) {
params[key] = match[0].toLowerCase();
}
}
return params;
}
extractCodebasePatterns(input) {
const params = {};
const pathMatch = input.match(/(?:map|analyze)\s+(.+?)(?:\s|$)/i);
if (pathMatch && pathMatch[1].includes('/')) {
params.path = pathMatch[1].trim();
}
return params;
}
}