UNPKG

vibe-coder-mcp

Version:

Production-ready MCP server with complete agent integration, multi-transport support, and comprehensive development automation tools for AI-assisted workflows.

251 lines (250 loc) 10.4 kB
import logger from '../../../logger.js'; export class SemanticIntentMatcher { config; synonymMap = new Map(); intentKeywords = new Map(); entityPatterns = new Map(); constructor(config = {}) { this.config = { minConfidence: 0.6, useContext: true, useSynonyms: true, useEntityRelations: true, ...config }; this.initializeMaps(); } async matchIntent(text, context, existingEntities) { try { logger.debug({ text: text.substring(0, 100) }, 'Starting semantic intent matching'); const normalizedText = this.normalizeText(text); const matches = []; for (const intent of this.getSupportedIntents()) { const match = await this.analyzeIntentMatch(intent, normalizedText, context, existingEntities); if (match.confidence >= this.config.minConfidence) { matches.push(match); } } matches.sort((a, b) => b.confidence - a.confidence); logger.debug({ matchCount: matches.length, topIntent: matches[0]?.intent, topConfidence: matches[0]?.confidence }, 'Semantic matching completed'); return matches; } catch (error) { logger.error({ err: error, text }, 'Semantic intent matching failed'); return []; } } async analyzeIntentMatch(intent, text, context, existingEntities) { const reasoning = []; const semanticScore = this.calculateSemanticScore(intent, text, reasoning); const contextScore = this.config.useContext ? this.calculateContextScore(intent, text, context, reasoning) : 0; const entityScore = this.config.useEntityRelations ? this.calculateEntityScore(intent, text, existingEntities, reasoning) : 0; const confidence = this.combineScores(semanticScore, contextScore, entityScore); return { intent, confidence, semanticScore, contextScore, entityScore, reasoning }; } calculateSemanticScore(intent, text, reasoning) { const keywords = this.intentKeywords.get(intent) || []; let score = 0; let matches = 0; for (const keyword of keywords) { if (text.includes(keyword.toLowerCase())) { score += 0.8; matches++; reasoning.push(`Direct keyword match: "${keyword}"`); } } if (this.config.useSynonyms) { for (const keyword of keywords) { const synonyms = this.synonymMap.get(keyword) || []; for (const synonym of synonyms) { if (text.includes(synonym.toLowerCase())) { score += 0.6; matches++; reasoning.push(`Synonym match: "${synonym}" for "${keyword}"`); } } } } const normalizedScore = keywords.length > 0 ? score / keywords.length : 0; if (matches > 0) { reasoning.push(`Semantic score: ${normalizedScore.toFixed(3)} (${matches} matches)`); } return Math.min(normalizedScore, 1.0); } calculateContextScore(intent, text, context, reasoning = []) { if (!context) return 0; let score = 0; switch (intent) { case 'decompose_task': case 'decompose_epic': case 'decompose_project': if (context.currentTask || context.currentProject) { score += 0.3; reasoning.push('Context: Current task/project available for decomposition'); } break; case 'search_files': case 'search_content': if (context.currentProject) { score += 0.2; reasoning.push('Context: Current project available for search'); } break; case 'create_task': if (context.currentProject) { score += 0.4; reasoning.push('Context: Current project available for task creation'); } break; case 'list_tasks': if (context.currentProject || context.currentTask) { score += 0.2; reasoning.push('Context: Current project/task context for listing'); } break; } if (context.conversationHistory && Array.isArray(context.conversationHistory)) { const recentIntents = context.conversationHistory .slice(-3) .map((item) => item.intent) .filter((intent) => Boolean(intent) && typeof intent === 'string'); if (this.isRelatedIntent(intent, recentIntents)) { score += 0.2; reasoning.push('Context: Related to recent conversation'); } } return Math.min(score, 1.0); } calculateEntityScore(intent, text, existingEntities, reasoning = []) { if (!existingEntities || existingEntities.length === 0) return 0; let score = 0; const relevantEntities = this.getRelevantEntities(intent); for (const entity of existingEntities) { if (relevantEntities.includes(entity.type)) { score += 0.3; reasoning.push(`Entity match: ${entity.type} = "${entity.value}"`); } } for (const [entityType, patterns] of this.entityPatterns.entries()) { if (relevantEntities.includes(entityType)) { for (const pattern of patterns) { if (pattern.test(text)) { score += 0.2; reasoning.push(`Entity pattern match: ${entityType}`); } } } } return Math.min(score, 1.0); } combineScores(semanticScore, contextScore, entityScore) { const weights = { semantic: 0.6, context: 0.25, entity: 0.15 }; return (semanticScore * weights.semantic + contextScore * weights.context + entityScore * weights.entity); } initializeMaps() { this.intentKeywords = new Map([ ['decompose_task', ['decompose', 'break down', 'split', 'divide', 'breakdown', 'task']], ['decompose_epic', ['decompose', 'break down', 'split', 'divide', 'breakdown', 'epic']], ['decompose_project', ['decompose', 'break down', 'split', 'divide', 'breakdown', 'project']], ['search_files', ['find', 'search', 'locate', 'files', 'file']], ['search_content', ['find', 'search', 'locate', 'content', 'code', 'text']], ['create_task', ['create', 'add', 'new', 'make', 'task']], ['create_project', ['create', 'add', 'new', 'make', 'project']], ['list_tasks', ['list', 'show', 'display', 'tasks']], ['list_projects', ['list', 'show', 'display', 'projects']], ['run_task', ['run', 'execute', 'start', 'begin', 'task']], ['check_status', ['status', 'check', 'progress', 'state']] ]); this.synonymMap = new Map([ ['decompose', ['break down', 'split up', 'divide', 'separate', 'breakdown']], ['search', ['find', 'locate', 'look for', 'seek']], ['create', ['add', 'make', 'new', 'build', 'generate']], ['list', ['show', 'display', 'view', 'get']], ['run', ['execute', 'start', 'begin', 'launch']], ['task', ['todo', 'item', 'work', 'job']], ['project', ['app', 'application', 'system', 'codebase']], ['files', ['documents', 'code', 'scripts']], ['content', ['text', 'code', 'data', 'information']] ]); this.entityPatterns = new Map([ ['taskId', [/\b[Tt]\d+\b/, /\btask[-_]?\d+\b/i, /\b[A-Z]+-\d+\b/]], ['projectId', [/\bPID[-_]?\w+[-_]?\d+\b/i, /\bproject[-_]?\d+\b/i]], ['fileName', [/\w+\.\w+/, /["']([^"']+\.\w+)["']/]], ['searchPattern', [/["']([^"']+)["']/, /\b\w+\*?\b/]] ]); } getSupportedIntents() { return Array.from(this.intentKeywords.keys()); } getRelevantEntities(intent) { const entityMap = { 'decompose_task': ['taskId'], 'decompose_epic': ['epicId'], 'decompose_project': ['projectId'], 'search_files': ['fileName', 'searchPattern'], 'search_content': ['searchPattern'], 'create_task': ['projectId'], 'create_project': [], 'update_project': ['projectId'], 'list_tasks': ['projectId'], 'list_projects': [], 'run_task': ['taskId'], 'check_status': ['taskId', 'projectId'], 'unknown': [], 'open_project': ['projectId'], 'refine_task': ['taskId'], 'assign_task': ['taskId', 'assignee'], 'get_help': [], 'parse_prd': ['projectName', 'filePath'], 'parse_tasks': ['projectName', 'filePath'], 'import_artifact': ['artifactType', 'projectName', 'filePath'], 'unrecognized_intent': [], 'clarification_needed': [] }; return entityMap[intent] || []; } isRelatedIntent(intent, recentIntents) { const relatedGroups = [ ['decompose_task', 'decompose_epic', 'decompose_project', 'create_task'], ['search_files', 'search_content'], ['list_tasks', 'list_projects', 'check_status'], ['create_task', 'create_project', 'run_task'] ]; for (const group of relatedGroups) { if (group.includes(intent) && recentIntents.some(recent => group.includes(recent))) { return true; } } return false; } normalizeText(text) { return text .toLowerCase() .replace(/[^\w\s]/g, ' ') .replace(/\s+/g, ' ') .trim(); } }