Skip to main content
Glama
patterns.ts50.6 kB
/** * Intent Pattern Engine for Natural Language Processing * Implements regex-based pattern matching for intent recognition */ import { Intent, IntentPattern, ConfidenceLevel } from '../types/nl.js'; import logger from '../../../logger.js'; /** * Entity extractor function type */ export type EntityExtractor = (text: string, match: RegExpMatchArray) => Record<string, unknown>; /** * Intent match result */ export interface IntentMatch { intent: Intent; confidence: number; confidenceLevel: ConfidenceLevel; entities: Record<string, unknown>; pattern: IntentPattern; matchedText: string; processingTime: number; } /** * Pattern engine configuration */ export interface PatternEngineConfig { /** Minimum confidence threshold for pattern matches */ minConfidence: number; /** Maximum number of patterns to check per intent */ maxPatternsPerIntent: number; /** Whether to enable fuzzy matching */ enableFuzzyMatching: boolean; /** Fuzzy matching threshold (0-1) */ fuzzyThreshold: number; } /** * Default entity extractors for common patterns */ export class EntityExtractors { /** * Extract project name from text */ static projectName(text: string, _match: RegExpMatchArray): Record<string, unknown> { const entities: Record<string, unknown> = {}; // Look for project name in quotes or after keywords const projectPatterns = [ // PID patterns (highest priority) - Format: PID-PROJECTNAME-NNN /\b(PID-[A-Z0-9-]+)\b/i, // Quoted patterns (second highest priority) /called\s+["']([^"']+)["']/i, /project\s+["']([^"']+)["']/i, /["']([^"']+)["']\s+project/i, /for\s+["']([^"']+)["']/i, // Multi-word patterns without quotes (capture until end of string or common stop words) // Limit capture to reasonable project name length (max ~5 words or until punctuation/stop words) /called\s+([A-Za-z0-9\-_]+(?:\s+[A-Za-z0-9\-_]+){0,4})(?=\s+[-–—]|\s+(?:project|task|file|document|prd|tasks?|list|into|with|for|using|through|via|detailed|comprehensive|development)|\s*$)/i, /project\s+(?!for\s)([A-Za-z0-9\-_]+(?:\s+[A-Za-z0-9\-_]+){0,4})(?=\s+[-–—]|\s+(?:project|task|file|document|prd|tasks?|list|into|with|for|using|through|via|detailed|comprehensive|development)|\s*$)/i, /for\s+(?:the\s+)?([A-Za-z0-9\-_]+(?:\s+[A-Za-z0-9\-_]+){0,4})(?=\s+[-–—]|\s+(?:project|task|file|document|prd|tasks?|list|into|with|for|using|through|via|detailed|comprehensive|development)|\s*$)/i, // Single word patterns (fallback - only if multi-word didn't match) /called\s+([A-Za-z0-9\-_]+)(?:\s|$)/i, /project\s+(?!for\s)([A-Za-z0-9\-_]+)(?:\s|$)/i, /for\s+(?:the\s+)?([A-Za-z0-9\-_]+)(?:\s|$)/i ]; // Blacklist of common verb phrases that should not be project names const blacklistedPhrases = [ 'should be set up', 'needs to be', 'must be', 'has to be', 'will be', 'would be', 'could be', 'can be', 'should be', 'is being', 'was being', 'has been', 'have been' ]; // Collect all matches with their metadata interface ProjectMatch { name: string; priority: number; position: number; hasDashSeparator: boolean; pattern: RegExp; } const allMatches: ProjectMatch[] = []; projectPatterns.forEach((pattern, priority) => { const projectMatch = text.match(pattern); if (projectMatch) { let projectName = projectMatch[1].trim(); // Clean up common artifacts projectName = projectName.replace(/\s+/g, ' '); // Normalize whitespace // Check if the name ends with a dash separator (indicates higher quality match) const hasDashSeparator = text.substring(projectMatch.index! + projectMatch[0].length).match(/^\s*[-–—]/); // Handle specific project ID patterns with dash separators const projectIdWithDescMatch = projectName.match(/^([A-Z0-9]+(?:-[A-Z0-9]+)*)\s*[-–—]\s+/i); if (projectIdWithDescMatch && projectIdWithDescMatch[1].includes('-')) { projectName = projectIdWithDescMatch[1]; } else { // Remove trailing dashes and keywords if no project ID pattern projectName = projectName.replace(/\s*[-–—]\s*$/, ''); // Remove trailing dashes projectName = projectName.replace(/\s+(project|task|file|document|prd|tasks?|list|into|with|for|using|through|via|detailed|comprehensive|development)$/i, ''); // Remove trailing keywords } // Skip if the name is in the blacklist const lowerName = projectName.toLowerCase(); const isBlacklisted = blacklistedPhrases.some(phrase => lowerName === phrase || lowerName.includes(phrase)); if (projectName.length > 0 && !isBlacklisted) { allMatches.push({ name: projectName, priority: priority, position: projectMatch.index!, hasDashSeparator: !!hasDashSeparator, pattern: pattern }); } } }); // Select the best match based on scoring if (allMatches.length > 0) { // Sort by: priority (lower is better), then position (earlier is better), then dash separator (true is better) allMatches.sort((a, b) => { // First, compare by priority (lower number = higher priority) if (a.priority !== b.priority) { return a.priority - b.priority; } // For same priority, prefer matches with dash separators if (a.hasDashSeparator !== b.hasDashSeparator) { return b.hasDashSeparator ? 1 : -1; } // Finally, prefer earlier matches in the text return a.position - b.position; }); const bestMatch = allMatches[0]; // Log when we're choosing between multiple matches if (allMatches.length > 1) { logger.debug({ selectedMatch: bestMatch.name, allMatches: allMatches.map(m => ({ name: m.name, priority: m.priority, position: m.position, hasDashSeparator: m.hasDashSeparator })), originalText: text }, 'Multiple project name matches found, selected best match'); } // Handle truncation for the best match let projectName = bestMatch.name; if (projectName.length > 50) { // Try to truncate at a word boundary const truncated = projectName.substring(0, 50); const lastSpace = truncated.lastIndexOf(' '); if (lastSpace > 30) { // Truncate at last word boundary if it's reasonable projectName = truncated.substring(0, lastSpace).trim(); } else { // Otherwise just take first 50 chars projectName = truncated.trim(); } // Log warning about truncation logger.warn({ originalLength: bestMatch.name.length, truncatedLength: projectName.length, originalName: bestMatch.name, truncatedName: projectName }, 'Project name truncated to meet 50 character limit'); } entities.projectName = projectName; } return entities; } /** * Extract task information from text */ static taskInfo(text: string, _match: RegExpMatchArray): Record<string, unknown> { const entities: Record<string, unknown> = {}; // Extract task title - try patterns with and without quotes const titlePatterns = [ /task\s+["']([^"']+)["']/i, /["']([^"']+)["']\s+task/i, /for\s+["']([^"']+)["']/i, /called\s+["']([^"']+)["']/i, /to\s+["']([^"']+)["']/i, // Patterns without quotes /called\s+(\w+)/i, /for\s+(\w+)/i, /task\s+(\w+)/i, /(\w+)\s+task/i ]; for (const pattern of titlePatterns) { const titleMatch = text.match(pattern); if (titleMatch) { entities.taskTitle = titleMatch[1].trim(); break; } } // Extract priority const priorityMatch = text.match(/\b(low|medium|high|critical)\s+priority\b/i) || text.match(/priority\s+(low|medium|high|critical)\b/i); if (priorityMatch) { entities.priority = priorityMatch[1].toLowerCase(); } // Extract task type const typeMatch = text.match(/\b(development|testing|documentation|research|bug|feature)\b/i); if (typeMatch) { entities.type = typeMatch[1].toLowerCase(); } return entities; } /** * Extract epic information from text */ static epicInfo(text: string, _match: RegExpMatchArray): Record<string, unknown> { const entities: Record<string, unknown> = {}; // Extract epic title - try patterns with and without quotes const titlePatterns = [ /epic\s+["']([^"']+)["']/i, /["']([^"']+)["']\s+epic/i, /for\s+["']([^"']+)["']/i, /called\s+["']([^"']+)["']/i, /to\s+["']([^"']+)["']/i, // Patterns without quotes /called\s+(\w+)/i, /for\s+(\w+)/i, /epic\s+(\w+)/i, /(\w+)\s+epic/i ]; for (const pattern of titlePatterns) { const titleMatch = text.match(pattern); if (titleMatch) { entities.epicTitle = titleMatch[1].trim(); break; } } // Extract epic ID patterns const epicIdPatterns = [ /epic\s+([E]\d+)/i, /([E]\d+)/i, /epic\s+id\s+(\w+)/i, /id\s+(\w+)/i ]; for (const pattern of epicIdPatterns) { const idMatch = text.match(pattern); if (idMatch) { entities.epicId = idMatch[1].trim(); break; } } // Extract priority const priorityMatch = text.match(/\b(low|medium|high|critical)\s+priority\b/i) || text.match(/priority\s+(low|medium|high|critical)\b/i); if (priorityMatch) { entities.priority = priorityMatch[1].toLowerCase(); } return entities; } /** * Extract status information from text */ static statusInfo(text: string, _match: RegExpMatchArray): Record<string, unknown> { const entities: Record<string, unknown> = {}; // Extract status const statusMatch = text.match(/\b(pending|in_progress|completed|blocked|cancelled)\b/i); if (statusMatch) { entities.status = statusMatch[1].toLowerCase(); } // Extract timeframe const timePatterns = [ /\b(today|tomorrow|this\s+week|next\s+week|this\s+month)\b/i, /\b(\d{1,2}\/\d{1,2}\/\d{4})\b/, /\b(\d{4}-\d{2}-\d{2})\b/ ]; for (const pattern of timePatterns) { const timeMatch = text.match(pattern); if (timeMatch) { entities.timeframe = timeMatch[1]; break; } } return entities; } /** * Extract agent information from text */ static agentInfo(text: string, _match: RegExpMatchArray): Record<string, unknown> { const entities: Record<string, unknown> = {}; // Extract agent name const agentPatterns = [ /agent\s+["']([^"']+)["']/i, /to\s+["']([^"']+)["']/i, /assign\s+to\s+(\w+)/i, /give\s+(\w+)\s+the/i ]; for (const pattern of agentPatterns) { const agentMatch = text.match(pattern); if (agentMatch) { entities.assignee = agentMatch[1].trim(); break; } } return entities; } /** * Extract description information from text */ static descriptionInfo(text: string, _match: RegExpMatchArray): Record<string, unknown> { const entities: Record<string, unknown> = {}; // Extract description from various patterns const descriptionPatterns = [ /with\s+focus\s+on\s+(.+)/i, /considering\s+(.+)/i, /taking\s+into\s+account\s+(.+)/i, /for\s+(.+)/i, /description\s+["']([^"']+)["']/i, /context\s+["']([^"']+)["']/i ]; for (const pattern of descriptionPatterns) { const descMatch = text.match(pattern); if (descMatch) { entities.description = descMatch[1].trim(); break; } } return entities; } /** * Extract search information from text */ static searchInfo(text: string, _match: RegExpMatchArray): Record<string, unknown> { const entities: Record<string, unknown> = {}; // Extract search pattern from various formats const searchPatterns = [ /(?:find|search\s+for|locate)\s+(.+?)\s+files?/i, /(?:find|search\s+for|locate)\s+(.+)/i, /files?\s+(?:named|called|matching)\s+(.+)/i, /(.+?)\s+files?/i, /"([^"]+)"/, /'([^']+)'/ ]; for (const pattern of searchPatterns) { const searchMatch = text.match(pattern); if (searchMatch) { entities.searchPattern = searchMatch[1].trim(); break; } } // Extract file extensions const extMatch = text.match(/\.(\w+)\s+files?|(\w+)\s+files?/); if (extMatch) { const ext = extMatch[1] || extMatch[2]; if (['js', 'ts', 'tsx', 'jsx', 'py', 'java', 'cpp', 'css', 'html'].includes(ext)) { entities.extensions = [`.${ext}`]; } } return entities; } /** * Extract content search information from text */ static contentInfo(text: string, _match: RegExpMatchArray): Record<string, unknown> { const entities: Record<string, unknown> = {}; // Extract search query from various patterns const contentPatterns = [ /(?:find|search\s+for|locate)\s+(.+?)\s+(?:in\s+files?|in\s+code)/i, /(?:find|search\s+for|locate)\s+"(.+?)"/i, /(?:find|search\s+for|locate)\s+'(.+?)'/i, /(?:find|search\s+for|locate)\s+(.+)/i, /content\s+(?:containing|with)\s+(.+)/i ]; for (const pattern of contentPatterns) { const contentMatch = text.match(pattern); if (contentMatch) { entities.searchQuery = contentMatch[1].trim(); entities.content = contentMatch[1].trim(); break; } } // Check for case sensitivity if (text.toLowerCase().includes('case sensitive') || text.toLowerCase().includes('exact case')) { entities.caseSensitive = true; } // Check for regex if (text.toLowerCase().includes('regex') || text.toLowerCase().includes('regular expression')) { entities.useRegex = true; } return entities; } /** * Extract artifact information from text */ static artifactInfo(text: string, _match: RegExpMatchArray): Record<string, unknown> { const entities: Record<string, unknown> = {}; // Extract artifact type const artifactTypePatterns = [ /\b(prd|product\s+requirements?\s+document)\b/i, /\b(task\s+list|tasks?)\b/i, /\b(task\s+breakdown)\b/i, /\b(artifact|document|file)\b/i ]; for (const pattern of artifactTypePatterns) { const typeMatch = text.match(pattern); if (typeMatch) { let artifactType = typeMatch[1].toLowerCase(); // Normalize artifact types if (artifactType.includes('prd') || artifactType.includes('product') || artifactType.includes('requirements')) { artifactType = 'prd'; } else if (artifactType.includes('task')) { artifactType = 'tasks'; } else if (artifactType.includes('artifact') || artifactType.includes('document') || artifactType.includes('file')) { artifactType = 'artifact'; } entities.artifactType = artifactType; break; } } // Extract file path const filePathPatterns = [ /from\s+["']([^"']+)["']/i, /from\s+(\S+\.(?:md|txt|json|yaml|yml))/i, /from\s+(\S+)/i, /["']([^"']*\.(?:md|txt|json|yaml|yml))["']/i, /(\S+\.(?:md|txt|json|yaml|yml))/i ]; for (const pattern of filePathPatterns) { const pathMatch = text.match(pattern); if (pathMatch) { entities.filePath = pathMatch[1].trim(); break; } } return entities; } /** * Extract general entities from text */ static general(text: string, _match: RegExpMatchArray): Record<string, unknown> { const entities: Record<string, unknown> = {}; // Extract tags const tagMatches = text.match(/#(\w+)/g); if (tagMatches) { entities.tags = tagMatches.map(tag => tag.substring(1)); } // Extract numbers (could be IDs, hours, etc.) const numberMatches = text.match(/\b\d+\b/g); if (numberMatches) { entities.numbers = numberMatches.map(num => parseInt(num, 10)); } return entities; } } /** * Intent Pattern Engine implementation */ export class IntentPatternEngine { private patterns = new Map<Intent, IntentPattern[]>(); private config: PatternEngineConfig; constructor(config: Partial<PatternEngineConfig> = {}) { this.config = { minConfidence: config.minConfidence ?? 0.3, maxPatternsPerIntent: config.maxPatternsPerIntent ?? 10, enableFuzzyMatching: config.enableFuzzyMatching ?? false, fuzzyThreshold: config.fuzzyThreshold ?? 0.7 }; this.initializeDefaultPatterns(); } /** * Initialize default patterns for common intents */ private initializeDefaultPatterns(): void { // Project creation patterns - Enhanced with more diverse variations this.addPattern('create_project', { id: 'create_project_basic', intent: 'create_project', patterns: [ 'create\\s+(?:a\\s+)?(?:new\\s+)?project', 'start\\s+(?:a\\s+)?(?:new\\s+)?project', 'set\\s+up\\s+(?:a\\s+)?(?:new\\s+)?project', 'initialize\\s+(?:a\\s+)?(?:new\\s+)?project', 'create\\s+(?:something\\s+)?(?:new\\s+)?(?:for\\s+the\\s+)?project', 'make\\s+(?:a\\s+)?(?:new\\s+)?project', // Enhanced patterns for diverse commands 'build\\s+(?:a\\s+)?(?:new\\s+)?project', 'develop\\s+(?:a\\s+)?(?:new\\s+)?project', 'generate\\s+(?:a\\s+)?(?:new\\s+)?project', 'setup\\s+(?:a\\s+)?(?:new\\s+)?project', 'begin\\s+(?:a\\s+)?(?:new\\s+)?project', 'launch\\s+(?:a\\s+)?(?:new\\s+)?project', 'establish\\s+(?:a\\s+)?(?:new\\s+)?project', 'initiate\\s+(?:a\\s+)?(?:new\\s+)?project', // Natural variations '(?:let\'s\\s+)?(?:create|start|build|make)\\s+(?:a\\s+)?(?:new\\s+)?project', 'i\\s+(?:want\\s+to\\s+|need\\s+to\\s+)?(?:create|start|build|make)\\s+(?:a\\s+)?(?:new\\s+)?project', 'can\\s+(?:you\\s+)?(?:create|start|build|make)\\s+(?:a\\s+)?(?:new\\s+)?project' ], keywords: ['create', 'start', 'setup', 'initialize', 'project', 'new', 'make', 'build', 'develop', 'generate', 'launch'], requiredEntities: [], optionalEntities: ['projectName', 'description'], priority: 10, active: true, examples: [ 'Create a new project called "Web App"', 'Start a project for the mobile app', 'Set up a new project', 'Build a new project for streaming platform', 'Let\'s create a new project', 'I want to create a project', 'Can you make a new project?' ] }); // Project opening patterns this.addPattern('open_project', { id: 'open_project_basic', intent: 'open_project', patterns: [ 'open\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'switch\\s+to\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'work\\s+on\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'continue\\s+(?:with\\s+)?(?:the\\s+)?(?:\\w+\\s+)?project', 'load\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'access\\s+(?:the\\s+)?(?:\\w+\\s+)?project', // Natural variations '(?:let\'s\\s+)?(?:open|switch|work|continue)\\s+(?:with\\s+|on\\s+)?(?:the\\s+)?(?:\\w+\\s+)?project', 'i\\s+(?:want\\s+to\\s+|need\\s+to\\s+)?(?:open|work\\s+on|switch\\s+to)\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'can\\s+(?:you\\s+)?(?:open|load|switch\\s+to)\\s+(?:the\\s+)?(?:\\w+\\s+)?project' ], keywords: ['open', 'switch', 'work', 'continue', 'load', 'access', 'project'], requiredEntities: ['projectName'], optionalEntities: [], priority: 10, active: true, examples: [ 'Open the web app project', 'Switch to mobile project', 'Work on the API project', 'Continue with project "E-commerce Platform"', 'Load the streaming project', 'I want to work on the mobile project', 'Can you open the backend project?' ] }); // Task creation patterns this.addPattern('create_task', { id: 'create_task_basic', intent: 'create_task', patterns: [ 'create\\s+(?:a\\s+)?(?:new\\s+)?(?:high\\s+priority\\s+)?(?:development\\s+)?task', 'add\\s+(?:a\\s+)?(?:new\\s+)?task', 'make\\s+(?:a\\s+)?(?:new\\s+)?task', 'need\\s+(?:a\\s+)?(?:new\\s+)?task', 'create\\s+(?:a\\s+)?(?:high|medium|low|critical)\\s+priority\\s+(?:development|testing|documentation|research|bug|feature)\\s+task', 'create\\s+(?:a\\s+)?(?:development|testing|documentation|research|bug|feature)\\s+task' ], keywords: ['create', 'add', 'make', 'need', 'task', 'new', 'priority', 'development', 'high'], requiredEntities: [], optionalEntities: ['taskTitle', 'priority', 'type', 'assignee'], priority: 10, active: true, examples: [ 'Create a task for implementing authentication', 'Add a new task to fix the login bug', 'Make a task for testing the API', 'Create a high priority development task for implementing authentication' ] }); // Listing patterns this.addPattern('list_projects', { id: 'list_projects_basic', intent: 'list_projects', patterns: [ 'list\\s+(?:all\\s+)?projects', 'show\\s+(?:me\\s+)?(?:all\\s+)?projects', 'display\\s+(?:all\\s+)?projects', 'what\\s+projects', 'show\\s+(?:me\\s+)?(?:all\\s+)?(?:completed|pending|in_progress|blocked|cancelled)\\s+projects', 'list\\s+(?:all\\s+)?(?:completed|pending|in_progress)\\s+projects', 'show\\s+(?:me\\s+)?(?:all\\s+)?(?:completed|pending|in_progress|blocked|cancelled)\\s+projects\\s+(?:from\\s+)?(?:today|tomorrow|this\\s+week|next\\s+week|this\\s+month)' ], keywords: ['list', 'show', 'display', 'projects', 'all', 'completed', 'pending', 'week'], requiredEntities: [], optionalEntities: ['status', 'timeframe'], priority: 10, active: true, examples: [ 'List all projects', 'Show me the projects', 'What projects do we have?', 'Show me all completed projects from this week' ] }); // Task listing patterns this.addPattern('list_tasks', { id: 'list_tasks_basic', intent: 'list_tasks', patterns: [ 'list\\s+(?:all\\s+)?tasks', 'show\\s+(?:me\\s+)?(?:all\\s+)?tasks', 'display\\s+(?:all\\s+)?tasks', 'what\\s+tasks', 'show\\s+(?:completed|pending|in_progress|blocked|cancelled)\\s+tasks', 'list\\s+(?:all\\s+)?(?:pending|completed|in_progress)\\s+tasks', 'show\\s+(?:completed|pending|in_progress|blocked|cancelled)\\s+tasks\\s+(?:from\\s+)?(?:today|tomorrow|this\\s+week|next\\s+week|this\\s+month)', 'list\\s+(?:all\\s+)?(?:pending|completed|in_progress)\\s+tasks\\s+assigned\\s+to\\s+me' ], keywords: ['list', 'show', 'display', 'tasks', 'all', 'completed', 'pending', 'today', 'assigned'], requiredEntities: [], optionalEntities: ['status', 'priority', 'assignee', 'timeframe'], priority: 10, active: true, examples: [ 'List all tasks', 'Show me pending tasks', 'What tasks are assigned to me?', 'Show completed tasks from today', 'List all pending tasks assigned to me' ] }); // Status checking patterns this.addPattern('check_status', { id: 'check_status_basic', intent: 'check_status', patterns: [ 'status\\s+of', 'check\\s+(?:the\\s+)?status', 'what(?:\'s|\\s+is)\\s+the\\s+status', 'how\\s+is\\s+.+\\s+(?:going|progressing)', 'show\\s+(?:project\\s+)?status', 'show\\s+(?:me\\s+)?(?:the\\s+)?(?:project\\s+)?status', 'what(?:\'s|\\s+is)\\s+the\\s+status\\s+of\\s+the\\s+.+\\s+(?:application\\s+)?project' ], keywords: ['status', 'check', 'progress', 'how', 'going', 'show', 'project'], requiredEntities: [], optionalEntities: ['projectName', 'taskId'], priority: 10, active: true, examples: [ 'What\'s the status of the web project?', 'Check the status of task 123', 'How is the development going?', 'Show project status', 'What\'s the status of the web application project?' ] }); // Task execution patterns this.addPattern('run_task', { id: 'run_task_basic', intent: 'run_task', patterns: [ 'run\\s+(?:the\\s+)?(?:\\w+\\s+)?task', 'execute\\s+(?:the\\s+)?(?:\\w+\\s+)?task', 'start\\s+(?:working\\s+on\\s+)?(?:the\\s+)?(?:\\w+\\s+)?task', 'begin\\s+(?:the\\s+)?(?:\\w+\\s+)?task', 'run\\s+(?:task\\s+)?\\d+', 'execute\\s+(?:task\\s+)?\\d+', 'run\\s+(?:the\\s+)?\\w+\\s+task', 'execute\\s+(?:the\\s+)?\\w+\\s+task' ], keywords: ['run', 'execute', 'start', 'begin', 'task', 'authentication'], requiredEntities: [], optionalEntities: ['taskId', 'taskTitle'], priority: 10, active: true, examples: [ 'Run task 123', 'Execute the authentication task', 'Start working on the login feature', 'Run the authentication task' ] }); // Task decomposition patterns this.addPattern('decompose_task', { id: 'decompose_task_basic', intent: 'decompose_task', patterns: [ 'decompose\\s+(?:the\\s+)?(?:\\w+\\s+)?task', 'break\\s+down\\s+(?:the\\s+)?(?:\\w+\\s+)?task', 'split\\s+(?:up\\s+)?(?:the\\s+)?(?:\\w+\\s+)?task', 'divide\\s+(?:the\\s+)?(?:\\w+\\s+)?task', 'breakdown\\s+(?:the\\s+)?(?:\\w+\\s+)?task', 'decompose\\s+task\\s+\\w+', 'break\\s+down\\s+task\\s+\\w+', 'split\\s+task\\s+\\w+' ], keywords: ['decompose', 'break down', 'split', 'divide', 'breakdown', 'task'], requiredEntities: [], optionalEntities: ['taskId', 'description'], priority: 10, active: true, examples: [ 'Decompose task T001', 'Break down the authentication task', 'Split up this task', 'Decompose the login feature task' ] }); // Epic decomposition patterns this.addPattern('decompose_epic', { id: 'decompose_epic_basic', intent: 'decompose_epic', patterns: [ 'decompose\\s+(?:the\\s+)?(?:\\w+\\s+)?epic', 'break\\s+down\\s+(?:the\\s+)?(?:\\w+\\s+)?epic', 'split\\s+(?:up\\s+)?(?:the\\s+)?(?:\\w+\\s+)?epic', 'divide\\s+(?:the\\s+)?(?:\\w+\\s+)?epic', 'breakdown\\s+(?:the\\s+)?(?:\\w+\\s+)?epic', 'decompose\\s+epic\\s+\\w+', 'break\\s+down\\s+epic\\s+\\w+', // Natural language variations 'analyze\\s+(?:the\\s+)?(?:\\w+\\s+)?epic', 'plan\\s+(?:out\\s+)?(?:the\\s+)?(?:\\w+\\s+)?epic', 'organize\\s+(?:the\\s+)?(?:\\w+\\s+)?epic', 'structure\\s+(?:the\\s+)?(?:\\w+\\s+)?epic', 'outline\\s+(?:the\\s+)?(?:\\w+\\s+)?epic', // Conversational patterns '(?:can\\s+you\\s+)?(?:decompose|break\\s+down|analyze)\\s+(?:this\\s+|the\\s+)?epic', 'i\\s+(?:want\\s+to\\s+|need\\s+to\\s+)?(?:decompose|break\\s+down|analyze)\\s+(?:this\\s+|the\\s+)?epic', '(?:let\'s\\s+)?(?:decompose|break\\s+down|analyze)\\s+(?:this\\s+|the\\s+)?epic', // Task-oriented patterns 'create\\s+tasks\\s+for\\s+(?:the\\s+)?(?:\\w+\\s+)?epic', 'generate\\s+tasks\\s+for\\s+(?:the\\s+)?(?:\\w+\\s+)?epic', 'break\\s+epic\\s+into\\s+tasks', 'convert\\s+epic\\s+to\\s+tasks' ], keywords: ['decompose', 'break down', 'split', 'divide', 'breakdown', 'epic', 'analyze', 'plan', 'organize', 'tasks'], requiredEntities: [], optionalEntities: ['epicId', 'epicName', 'description'], priority: 10, active: true, examples: [ 'Decompose epic E001', 'Break down the authentication epic', 'Split up this epic', 'Create tasks for the user management epic', 'Decompose the payment processing epic into tasks' ] }); // Project decomposition patterns - Enhanced with more natural variations this.addPattern('decompose_project', { id: 'decompose_project_basic', intent: 'decompose_project', patterns: [ 'decompose\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'break\\s+down\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'split\\s+(?:up\\s+)?(?:the\\s+)?(?:\\w+\\s+)?project', 'divide\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'breakdown\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'decompose\\s+project\\s+\\w+', 'break\\s+down\\s+project\\s+\\w+', // Enhanced natural language variations 'analyze\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'plan\\s+(?:out\\s+)?(?:the\\s+)?(?:\\w+\\s+)?project', 'organize\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'structure\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'outline\\s+(?:the\\s+)?(?:\\w+\\s+)?project', // Conversational patterns '(?:can\\s+you\\s+)?(?:decompose|break\\s+down|analyze)\\s+(?:this\\s+|the\\s+)?project', 'i\\s+(?:want\\s+to\\s+|need\\s+to\\s+)?(?:decompose|break\\s+down|analyze)\\s+(?:this\\s+|the\\s+)?project', '(?:let\'s\\s+)?(?:decompose|break\\s+down|analyze)\\s+(?:this\\s+|the\\s+)?project', // Task-oriented patterns 'create\\s+tasks\\s+for\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'generate\\s+tasks\\s+for\\s+(?:the\\s+)?(?:\\w+\\s+)?project' ], keywords: ['decompose', 'break down', 'split', 'divide', 'breakdown', 'project', 'analyze', 'plan', 'organize', 'tasks'], requiredEntities: [], optionalEntities: ['projectId', 'projectName', 'description'], priority: 10, active: true, examples: [ 'Decompose project PID-WEBAPP-001', 'Break down the web app project', 'Analyze this project', 'Can you decompose the project?', 'I need to break down this project', 'Create tasks for the streaming project' ] }); // File search patterns this.addPattern('search_files', { id: 'search_files_basic', intent: 'search_files', patterns: [ 'find\\s+(?:all\\s+)?(?:\\w+\\s+)?files?', 'search\\s+(?:for\\s+)?(?:all\\s+)?(?:\\w+\\s+)?files?', 'locate\\s+(?:all\\s+)?(?:\\w+\\s+)?files?', 'show\\s+(?:me\\s+)?(?:all\\s+)?(?:\\w+\\s+)?files?', 'list\\s+(?:all\\s+)?(?:\\w+\\s+)?files?', 'find\\s+files?\\s+(?:named|called|matching)\\s+\\w+', 'search\\s+files?\\s+(?:named|called|matching)\\s+\\w+' ], keywords: ['find', 'search', 'locate', 'show', 'list', 'files', 'file'], requiredEntities: [], optionalEntities: ['searchPattern', 'fileName', 'extensions'], priority: 10, active: true, examples: [ 'Find auth files', 'Search for component files', 'Locate all .ts files', 'Show me test files' ] }); // Content search patterns this.addPattern('search_content', { id: 'search_content_basic', intent: 'search_content', patterns: [ 'find\\s+(?:all\\s+)?(?:instances\\s+of\\s+)?\\w+\\s+(?:in\\s+)?(?:files?|code)', 'search\\s+(?:for\\s+)?(?:all\\s+)?(?:instances\\s+of\\s+)?\\w+\\s+(?:in\\s+)?(?:files?|code)', 'locate\\s+(?:all\\s+)?(?:instances\\s+of\\s+)?\\w+\\s+(?:in\\s+)?(?:files?|code)', 'find\\s+content\\s+(?:containing|with)\\s+\\w+', 'search\\s+content\\s+(?:containing|with)\\s+\\w+', 'find\\s+"[^"]+"\\s+(?:in\\s+)?(?:files?|code)', 'search\\s+"[^"]+"\\s+(?:in\\s+)?(?:files?|code)' ], keywords: ['find', 'search', 'locate', 'content', 'code', 'in files', 'instances'], requiredEntities: [], optionalEntities: ['searchQuery', 'content', 'extensions'], priority: 10, active: true, examples: [ 'Find useState in files', 'Search for authentication code', 'Locate all instances of "login"', 'Find content containing API' ] }); // PRD parsing patterns this.addPattern('parse_prd', { id: 'parse_prd_basic', intent: 'parse_prd', patterns: [ 'parse\\s+(?:the\\s+)?(?:prd|product\\s+requirements?\\s+document)', 'load\\s+(?:the\\s+)?(?:prd|product\\s+requirements?\\s+document)', 'read\\s+(?:the\\s+)?(?:prd|product\\s+requirements?\\s+document)', 'process\\s+(?:the\\s+)?(?:prd|product\\s+requirements?\\s+document)', 'analyze\\s+(?:the\\s+)?(?:prd|product\\s+requirements?\\s+document)', 'import\\s+(?:the\\s+)?(?:prd|product\\s+requirements?\\s+document)', 'open\\s+(?:the\\s+)?(?:prd|product\\s+requirements?\\s+document)', // With project context 'parse\\s+(?:the\\s+)?(?:prd|product\\s+requirements?\\s+document)\\s+for\\s+(?:the\\s+)?(?:project\\s+)?\\w+', 'load\\s+(?:the\\s+)?(?:prd|product\\s+requirements?\\s+document)\\s+for\\s+(?:the\\s+)?(?:project\\s+)?\\w+', 'parse\\s+(?:prd|product\\s+requirements?\\s+document)\\s+for\\s+["\'](.*?)["\']', // Shortened forms 'parse\\s+prd', 'load\\s+prd', 'read\\s+prd', 'process\\s+prd', 'analyze\\s+prd', 'import\\s+prd', 'open\\s+prd' ], keywords: ['parse', 'load', 'read', 'process', 'analyze', 'import', 'open', 'prd', 'product', 'requirements', 'document'], requiredEntities: [], optionalEntities: ['projectName', 'filePath'], priority: 10, active: true, examples: [ 'Parse the PRD', 'Load PRD for my project', 'Read the product requirements document', 'Process PRD file', 'Analyze the PRD', 'Parse PRD for "E-commerce Platform"', 'Load the product requirements document for the web app' ] }); // Project update patterns this.addPattern('update_project', { id: 'update_project_basic', intent: 'update_project', patterns: [ 'update\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'modify\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'change\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'edit\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'configure\\s+(?:the\\s+)?(?:\\w+\\s+)?project', 'update\\s+project\\s+\\w+', 'modify\\s+project\\s+\\w+', 'change\\s+project\\s+\\w+', // Enhanced natural language variations 'update\\s+(?:the\\s+)?(?:\\w+\\s+)?project\\s+(?:settings|configuration|config|properties)', 'modify\\s+(?:the\\s+)?(?:\\w+\\s+)?project\\s+(?:settings|configuration|config|properties)', 'change\\s+(?:the\\s+)?(?:\\w+\\s+)?project\\s+(?:settings|configuration|config|properties)', 'configure\\s+(?:the\\s+)?(?:\\w+\\s+)?project\\s+(?:settings|configuration|config|properties)', 'update\\s+(?:the\\s+)?(?:\\w+\\s+)?project\\s+(?:details|info|information)', 'edit\\s+(?:the\\s+)?(?:\\w+\\s+)?project\\s+(?:details|info|information|settings|configuration)' ], keywords: ['update', 'modify', 'change', 'edit', 'configure', 'project', 'settings', 'configuration', 'config', 'properties', 'details'], requiredEntities: [], optionalEntities: ['projectName', 'property', 'value'], priority: 10, active: true, examples: [ 'Update project configuration', 'Modify the project settings', 'Change project properties', 'Edit the project details', 'Configure the project', 'Update project MyApp', 'Modify project settings for WebApp' ] }); // Task list parsing patterns this.addPattern('parse_tasks', { id: 'parse_tasks_basic', intent: 'parse_tasks', patterns: [ 'parse\\s+(?:the\\s+)?(?:task\\s+list|tasks?)', 'load\\s+(?:the\\s+)?(?:task\\s+list|tasks?)', 'read\\s+(?:the\\s+)?(?:task\\s+list|tasks?)', 'process\\s+(?:the\\s+)?(?:task\\s+list|tasks?)', 'analyze\\s+(?:the\\s+)?(?:task\\s+list|tasks?)', 'import\\s+(?:the\\s+)?(?:task\\s+list|tasks?)', 'open\\s+(?:the\\s+)?(?:task\\s+list|tasks?)', // With project context 'parse\\s+(?:the\\s+)?(?:task\\s+list|tasks?)\\s+for\\s+(?:the\\s+)?(?:project\\s+)?\\w+', 'load\\s+(?:the\\s+)?(?:task\\s+list|tasks?)\\s+for\\s+(?:the\\s+)?(?:project\\s+)?\\w+', 'parse\\s+(?:task\\s+list|tasks?)\\s+for\\s+["\'](.*?)["\']', // Alternative forms 'parse\\s+(?:the\\s+)?(?:task\\s+breakdown|task\\s+file)', 'load\\s+(?:the\\s+)?(?:task\\s+breakdown|task\\s+file)', 'read\\s+(?:the\\s+)?(?:task\\s+breakdown|task\\s+file)', 'process\\s+(?:the\\s+)?(?:task\\s+breakdown|task\\s+file)', 'analyze\\s+(?:the\\s+)?(?:task\\s+breakdown|task\\s+file)' ], keywords: ['parse', 'load', 'read', 'process', 'analyze', 'import', 'open', 'task', 'tasks', 'list', 'breakdown', 'file'], requiredEntities: [], optionalEntities: ['projectName', 'filePath'], priority: 10, active: true, examples: [ 'Parse the task list', 'Load task list for project', 'Read the tasks file', 'Process task list', 'Analyze the task breakdown', 'Parse tasks for "Mobile App"', 'Load the task list for the web application' ] }); // Artifact import patterns this.addPattern('import_artifact', { id: 'import_artifact_basic', intent: 'import_artifact', patterns: [ 'import\\s+(?:prd|product\\s+requirements?\\s+document)\\s+from\\s+\\S+', 'import\\s+(?:task\\s+list|tasks?)\\s+from\\s+\\S+', 'import\\s+(?:artifact|document|file)\\s+from\\s+\\S+', 'load\\s+(?:prd|product\\s+requirements?\\s+document)\\s+from\\s+\\S+', 'load\\s+(?:task\\s+list|tasks?)\\s+from\\s+\\S+', 'load\\s+(?:artifact|document|file)\\s+from\\s+\\S+', // With file paths 'import\\s+(?:prd|product\\s+requirements?\\s+document)\\s+from\\s+["\'](.*?)["\']', 'import\\s+(?:task\\s+list|tasks?)\\s+from\\s+["\'](.*?)["\']', 'import\\s+(?:artifact|document|file)\\s+from\\s+["\'](.*?)["\']', 'load\\s+(?:prd|product\\s+requirements?\\s+document)\\s+from\\s+["\'](.*?)["\']', 'load\\s+(?:task\\s+list|tasks?)\\s+from\\s+["\'](.*?)["\']', 'load\\s+(?:artifact|document|file)\\s+from\\s+["\'](.*?)["\']', // Simplified forms 'import\\s+prd\\s+from\\s+\\S+', 'import\\s+tasks?\\s+from\\s+\\S+', 'load\\s+prd\\s+from\\s+\\S+', 'load\\s+tasks?\\s+from\\s+\\S+', 'import\\s+from\\s+\\S+', 'load\\s+from\\s+\\S+', // Forms without explicit "from" 'load\\s+(?:prd|product\\s+requirements?\\s+document)\\s+file', 'load\\s+(?:task\\s+list|tasks?)\\s+file', 'import\\s+(?:prd|product\\s+requirements?\\s+document)\\s+file', 'import\\s+(?:task\\s+list|tasks?)\\s+file', 'load\\s+prd\\s+file', 'load\\s+tasks?\\s+file', 'import\\s+prd\\s+file', 'import\\s+tasks?\\s+file' ], keywords: ['import', 'load', 'from', 'prd', 'product', 'requirements', 'document', 'task', 'tasks', 'list', 'artifact', 'file'], requiredEntities: [], optionalEntities: ['artifactType', 'filePath', 'projectName'], priority: 10, active: true, examples: [ 'Import PRD from file.md', 'Load task list from path/to/file.md', 'Import artifact from document.md', 'Load PRD file', 'Import tasks from file', 'Import PRD from "/path/to/requirements.md"', 'Load task list from "project-tasks.md"' ] }); logger.info({ patternCount: this.getTotalPatternCount() }, 'Default patterns initialized'); } /** * Add a pattern for an intent */ addPattern(intent: Intent, pattern: IntentPattern): void { if (!this.patterns.has(intent)) { this.patterns.set(intent, []); } const intentPatterns = this.patterns.get(intent)!; // Check if pattern already exists const existingPattern = intentPatterns.find(p => p.id === pattern.id); if (existingPattern) { logger.warn({ patternId: pattern.id, intent }, 'Pattern already exists, updating'); Object.assign(existingPattern, pattern); } else { intentPatterns.push(pattern); intentPatterns.sort((a, b) => b.priority - a.priority); // Sort by priority descending } logger.debug({ patternId: pattern.id, intent, priority: pattern.priority }, 'Pattern added'); } /** * Remove a pattern by ID */ removePattern(intent: Intent, patternId: string): boolean { const intentPatterns = this.patterns.get(intent); if (!intentPatterns) { return false; } const index = intentPatterns.findIndex(p => p.id === patternId); if (index === -1) { return false; } intentPatterns.splice(index, 1); logger.debug({ patternId, intent }, 'Pattern removed'); return true; } /** * Match intent from text using pattern matching */ matchIntent(text: string): IntentMatch[] { const startTime = Date.now(); const matches: IntentMatch[] = []; const normalizedText = text.toLowerCase().trim(); for (const [intent, intentPatterns] of Array.from(this.patterns.entries())) { for (const pattern of intentPatterns) { if (!pattern.active) continue; const match = this.matchPattern(normalizedText, pattern, text); if (match) { matches.push({ intent, confidence: match.confidence, confidenceLevel: this.getConfidenceLevel(match.confidence), entities: match.entities, pattern, matchedText: match.matchedText, processingTime: Date.now() - startTime }); } } } // Sort by confidence descending matches.sort((a, b) => b.confidence - a.confidence); // Filter by minimum confidence const filteredMatches = matches.filter(m => m.confidence >= this.config.minConfidence); logger.debug({ inputLength: text.length, totalMatches: matches.length, filteredMatches: filteredMatches.length, processingTime: Date.now() - startTime }, 'Intent matching completed'); return filteredMatches; } /** * Match a single pattern against text */ private matchPattern(text: string, pattern: IntentPattern, originalText: string): { confidence: number; entities: Record<string, unknown>; matchedText: string; } | null { let bestMatch: RegExpMatchArray | null = null; let bestConfidence = 0; // Try each regex pattern for (const patternStr of pattern.patterns) { try { const regex = new RegExp(patternStr, 'i'); const match = text.match(regex); if (match) { // Calculate confidence based on match quality const confidence = this.calculatePatternConfidence(text, match, pattern); if (confidence > bestConfidence) { bestMatch = match; bestConfidence = confidence; } } } catch (error) { logger.warn({ pattern: patternStr, error }, 'Invalid regex pattern'); } } if (!bestMatch || bestConfidence < this.config.minConfidence) { return null; } // Extract entities using original text to preserve case const entities = this.extractEntities(originalText, bestMatch, pattern); return { confidence: bestConfidence, entities, matchedText: bestMatch[0] }; } /** * Calculate confidence score for a pattern match */ private calculatePatternConfidence(text: string, match: RegExpMatchArray, pattern: IntentPattern): number { let confidence = 0.5; // Base confidence // Boost confidence for exact keyword matches const keywordMatches = pattern.keywords.filter(keyword => text.toLowerCase().includes(keyword.toLowerCase()) ); confidence += (keywordMatches.length / pattern.keywords.length) * 0.3; // Boost confidence for longer matches const matchRatio = match[0].length / text.length; confidence += Math.min(matchRatio * 0.2, 0.2); // Boost confidence for matches at the beginning of text if (match.index === 0) { confidence += 0.1; } // Ensure confidence is within bounds return Math.min(Math.max(confidence, 0), 1); } /** * Extract entities from matched text */ private extractEntities(originalText: string, match: RegExpMatchArray, pattern: IntentPattern): Record<string, unknown> { const entities: Record<string, unknown> = {}; // Apply built-in entity extractors based on intent using original text to preserve case switch (pattern.intent) { case 'create_project': case 'open_project': Object.assign(entities, EntityExtractors.projectName(originalText, match)); break; case 'create_task': case 'run_task': Object.assign(entities, EntityExtractors.taskInfo(originalText, match)); break; case 'list_projects': Object.assign(entities, EntityExtractors.statusInfo(originalText, match)); break; case 'list_tasks': Object.assign(entities, EntityExtractors.statusInfo(originalText, match)); Object.assign(entities, EntityExtractors.agentInfo(originalText, match)); Object.assign(entities, EntityExtractors.projectName(originalText, match)); break; case 'check_status': Object.assign(entities, EntityExtractors.statusInfo(originalText, match)); Object.assign(entities, EntityExtractors.projectName(originalText, match)); break; case 'assign_task': Object.assign(entities, EntityExtractors.agentInfo(originalText, match)); Object.assign(entities, EntityExtractors.taskInfo(originalText, match)); break; case 'decompose_task': Object.assign(entities, EntityExtractors.taskInfo(originalText, match)); Object.assign(entities, EntityExtractors.descriptionInfo(originalText, match)); break; case 'decompose_epic': Object.assign(entities, EntityExtractors.epicInfo(originalText, match)); Object.assign(entities, EntityExtractors.descriptionInfo(originalText, match)); break; case 'decompose_project': Object.assign(entities, EntityExtractors.projectName(originalText, match)); Object.assign(entities, EntityExtractors.descriptionInfo(originalText, match)); break; case 'search_files': Object.assign(entities, EntityExtractors.searchInfo(originalText, match)); break; case 'search_content': Object.assign(entities, EntityExtractors.searchInfo(originalText, match)); Object.assign(entities, EntityExtractors.contentInfo(originalText, match)); break; case 'parse_prd': Object.assign(entities, EntityExtractors.projectName(originalText, match)); Object.assign(entities, EntityExtractors.artifactInfo(originalText, match)); break; case 'parse_tasks': Object.assign(entities, EntityExtractors.projectName(originalText, match)); Object.assign(entities, EntityExtractors.artifactInfo(originalText, match)); break; case 'import_artifact': Object.assign(entities, EntityExtractors.projectName(originalText, match)); Object.assign(entities, EntityExtractors.artifactInfo(originalText, match)); break; case 'update_project': Object.assign(entities, EntityExtractors.projectName(originalText, match)); Object.assign(entities, EntityExtractors.general(originalText, match)); break; } // Always apply general extractors Object.assign(entities, EntityExtractors.general(originalText, match)); return entities; } /** * Get confidence level from numeric confidence */ private getConfidenceLevel(confidence: number): ConfidenceLevel { if (confidence >= 0.9) return 'very_high'; if (confidence >= 0.7) return 'high'; if (confidence >= 0.5) return 'medium'; if (confidence >= 0.3) return 'low'; return 'very_low'; } /** * Get total number of patterns across all intents */ getTotalPatternCount(): number { let total = 0; for (const patterns of Array.from(this.patterns.values())) { total += patterns.length; } return total; } /** * Get patterns for a specific intent */ getPatternsForIntent(intent: Intent): IntentPattern[] { return this.patterns.get(intent) || []; } /** * Get all supported intents */ getSupportedIntents(): Intent[] { return Array.from(this.patterns.keys()); } /** * Update pattern engine configuration */ updateConfig(config: Partial<PatternEngineConfig>): void { this.config = { ...this.config, ...config }; logger.info({ config: this.config }, 'Pattern engine configuration updated'); } /** * Get current configuration */ getConfig(): PatternEngineConfig { return { ...this.config }; } /** * Clear all patterns */ clearPatterns(): void { this.patterns.clear(); logger.info('All patterns cleared'); } /** * Export patterns to JSON */ exportPatterns(): Record<string, IntentPattern[]> { const exported: Record<string, IntentPattern[]> = {}; for (const [intent, patterns] of Array.from(this.patterns.entries())) { exported[intent] = patterns; } return exported; } /** * Import patterns from JSON */ importPatterns(patterns: Record<string, IntentPattern[]>): void { this.clearPatterns(); for (const [intent, intentPatterns] of Object.entries(patterns)) { for (const pattern of intentPatterns) { this.addPattern(intent as Intent, pattern); } } logger.info({ intentCount: Object.keys(patterns).length }, 'Patterns imported'); } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/freshtechbro/vibe-coder-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server