UNPKG

bc-code-intelligence-mcp

Version:

BC Code Intelligence MCP Server - Complete Specialist Bundle with AI-driven expert consultation, seamless handoffs, and context-preserving workflows

423 lines (419 loc) • 19.3 kB
/** * Methodology Service - Knowledge-Driven Implementation * Dynamic methodology loading and guidance using the layered knowledge system */ import { readFileSync, existsSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; export class MethodologyService { knowledgeService; methodologyPath; indexData; loadedPhases = {}; currentSession; constructor(knowledgeService, methodologyPath) { this.knowledgeService = knowledgeService; // Keep methodology loading from files, but add knowledge service for BC content // ES module: __dirname equivalent const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); this.methodologyPath = methodologyPath || join(__dirname, '../../embedded-knowledge', 'methodologies'); this.indexData = this.loadIndex(); this.currentSession = { intent: null, phases: [], domain: 'business-central', progress: {}, validation_status: {} }; } loadIndex() { const indexFile = join(this.methodologyPath, 'index.json'); if (!existsSync(indexFile)) { throw new Error(` 🚨 BC Code Intelligence MCP Server Setup Issue PROBLEM: Missing embedded knowledge content FILE: ${indexFile} LIKELY CAUSE: The git submodule 'embedded-knowledge' was not initialized when this package was built/installed. SOLUTIONS: šŸ“¦ For NPM users: Update to the latest version with: npm update bc-code-intelligence-mcp šŸ”§ For developers: Run: git submodule init && git submodule update šŸ¢ For package maintainers: Ensure submodules are initialized before npm publish This error indicates the embedded BC knowledge base is missing, which contains the methodology definitions, specialist knowledge, and domain expertise required for the MCP server to function. `.trim()); } try { const content = readFileSync(indexFile, 'utf-8'); const indexData = JSON.parse(content); // Log detailed information about loaded methodology console.error(`Loaded methodology index with ${Object.keys(indexData.intents || {}).length} intents`); if (indexData.intents) { // Silently load intents without verbose logging } const availablePhases = new Set(); Object.values(indexData.intents || {}).forEach((intent) => { (intent.phases || []).forEach((phase) => availablePhases.add(phase)); }); console.error(`Available phases: ${Array.from(availablePhases).join(', ')}`); return indexData; } catch (error) { throw new Error(`Failed to load methodology index: ${error}`); } } /** * Load methodology based on user request and intent analysis */ async loadMethodology(request) { // Analyze user intent const intent = this.analyzeIntent(request.user_request); const domain = request.domain || 'business-central'; // Get required phases const requiredPhases = this.indexData.intents[intent]?.phases || ['analysis', 'performance']; // Resolve dependencies const executionOrder = this.resolvePhaseDepedencies(requiredPhases); // Load methodology content with knowledge-driven domain knowledge const loadedPhases = []; for (const phase of executionOrder) { const phaseContent = await this.loadPhaseContent(phase, domain); loadedPhases.push(phaseContent); } // Set up session this.currentSession = { intent, phases: executionOrder, domain, progress: Object.fromEntries(executionOrder.map(p => [p, { status: 'pending', completion: 0 }])), validation_status: {} }; return { intent_detected: intent, phases: loadedPhases, execution_order: executionOrder, validation_criteria: this.getValidationCriteria(executionOrder), estimated_duration: this.estimateDuration(executionOrder), session_id: Date.now().toString() }; } /** * Get specific phase guidance and instructions */ async getPhaseGuidance(request) { try { // Load phase content if not already loaded if (!this.loadedPhases[request.phase_name]) { this.loadedPhases[request.phase_name] = await this.loadPhaseContent(request.phase_name, this.currentSession.domain); } const phaseContent = this.loadedPhases[request.phase_name]; if (!phaseContent) { return { error: `Phase '${request.phase_name}' content not loaded` }; } // Extract specific step if requested if (request.step) { return this.extractStepContent(phaseContent, request.step); } return phaseContent; } catch (error) { return { error: `Failed to load phase guidance: ${error instanceof Error ? error.message : String(error)}` }; } } /** * Validate methodology completion against systematic framework */ async validateCompleteness(request) { try { // Load phase requirements const phaseRequirements = await this.extractPhaseRequirements(request.phase); // Calculate completion const totalRequirements = phaseRequirements.length; const completedCount = request.completed_items.filter(item => phaseRequirements.some(req => req.toLowerCase().includes(item.toLowerCase()))).length; const completionPercentage = totalRequirements > 0 ? (completedCount / totalRequirements * 100) : 0; // Identify missing items const missingItems = phaseRequirements.filter(req => !request.completed_items.some(item => req.toLowerCase().includes(item.toLowerCase()))); // Calculate quality score const qualityScore = this.calculateQualityScore(request.phase, request.completed_items); // Update session progress this.currentSession.progress[request.phase] = { status: completionPercentage >= 90 ? 'completed' : 'in_progress', completion: completionPercentage }; // Determine next actions const nextActions = this.getNextActions(request.phase, missingItems, completionPercentage); return { phase: request.phase, completion_percentage: completionPercentage, completed_items_count: completedCount, total_requirements: totalRequirements, missing_items: missingItems, quality_score: qualityScore, next_actions: nextActions, can_proceed_to_next_phase: completionPercentage >= 80 }; } catch (error) { throw new Error(`Failed to validate completeness: ${error instanceof Error ? error.message : String(error)}`); } } /** * Find workflows by search query */ async findWorkflowsByQuery(query) { const queryLower = query.toLowerCase(); // Search through methodology workflows and return matching ones const workflows = []; // Search through methodology index for workflow-related content if (this.indexData.workflow_mappings) { for (const [workflowName, workflowData] of Object.entries(this.indexData.workflow_mappings)) { const workflowDataTyped = workflowData; if (workflowName.toLowerCase().includes(queryLower) || (workflowDataTyped.description && workflowDataTyped.description.toLowerCase().includes(queryLower)) || (workflowDataTyped.phases && workflowDataTyped.phases.some((phase) => phase.toLowerCase().includes(queryLower)))) { workflows.push({ name: workflowName, description: workflowDataTyped.description || 'BC development workflow', phases: workflowDataTyped.phases || [], methodology_type: workflowDataTyped.methodology_type || 'general' }); } } } // If no specific workflow matches, return generic BC workflow suggestions if (workflows.length === 0) { workflows.push({ name: 'bc-development-workflow', description: 'General Business Central development workflow', phases: ['analysis', 'design', 'implementation', 'testing', 'deployment'], methodology_type: 'development' }); } return workflows; } analyzeIntent(userRequest) { const userRequestLower = userRequest.toLowerCase(); // Score each intent based on keyword matches const intentScores = {}; for (const [intentName, intentData] of Object.entries(this.indexData.intents)) { let score = 0; for (const keyword of intentData.keywords) { if (userRequestLower.includes(keyword.toLowerCase())) { score += 1; } } intentScores[intentName] = score; } // Return highest scoring intent, default to performance-optimization if (!Object.keys(intentScores).length || Math.max(...Object.values(intentScores)) === 0) { return 'performance-optimization'; } return Object.entries(intentScores).reduce((a, b) => (intentScores[a[0]] || 0) > (intentScores[b[0]] || 0) ? a : b)[0]; } resolvePhaseDepedencies(requiredPhases) { const dependencies = this.indexData.phase_dependencies; const resolved = []; const remaining = new Set(requiredPhases); while (remaining.size > 0) { // Find phases with no unresolved dependencies const ready = []; for (const phase of remaining) { const deps = dependencies[phase] || []; if (deps.every((dep) => resolved.includes(dep))) { ready.push(phase); } } if (ready.length === 0) { // Circular dependency - use original order return requiredPhases; } // Add ready phases to resolved list ready.sort(); // Consistent ordering resolved.push(...ready); ready.forEach(phase => remaining.delete(phase)); } return resolved; } async loadPhaseContent(phaseName, domain) { const phaseFile = join(this.methodologyPath, 'phases', `${phaseName}.md`); if (!existsSync(phaseFile)) { throw new Error(`Phase file not found: ${phaseFile}`); } const content = readFileSync(phaseFile, 'utf-8'); // Load domain-specific knowledge dynamically const domainKnowledge = await this.loadDomainKnowledge(domain, phaseName); return { phase_name: phaseName, methodology_content: content, domain_knowledge: domainKnowledge, checklists: this.extractChecklists(content), success_criteria: this.extractSuccessCriteria(content) }; } async loadDomainKnowledge(domain, phase) { try { // Load relevant knowledge dynamically from the knowledge service const domainTopics = await this.knowledgeService.searchTopics({ domain: domain, limit: 10 }); // Get patterns and anti-patterns const codePatterns = await this.knowledgeService.findTopicsByType('code-pattern'); const goodPatterns = codePatterns.filter(p => p.frontmatter.pattern_type === 'good'); const badPatterns = codePatterns.filter(p => p.frontmatter.pattern_type === 'bad'); // Extract best practices from domain topics const bestPractices = domainTopics .filter(topic => topic.tags?.includes('best-practice')) .map(topic => topic.title) .slice(0, 5); return { patterns: goodPatterns.map(p => p.title).join(', ') || 'No patterns found', anti_patterns: badPatterns.map(p => p.title).join(', ') || 'No anti-patterns found', best_practices: bestPractices.length > 0 ? bestPractices : ['Use knowledge base tools for best practices'] }; } catch (error) { console.error('Failed to load domain knowledge:', error); // Fallback to static content return { patterns: `Use find_bc_topics tool to access ${domain} patterns from knowledge base`, anti_patterns: `Use analyze_code_patterns tool to detect ${domain} anti-patterns`, best_practices: [`Reference ${domain} best practices via knowledge base tools`] }; } } extractChecklists(content) { const checklistPattern = /- \[ \] \*\*(.*?)\*\*/g; const matches = Array.from(content.matchAll(checklistPattern)); return matches.map(match => ({ item: match[1] || '', completed: false })); } extractSuccessCriteria(content) { const criteriaPattern = /āœ… \*\*(.*?)\*\*/g; const matches = Array.from(content.matchAll(criteriaPattern)); return matches.map(match => match[1]).filter((item) => Boolean(item)); } async extractPhaseRequirements(phase) { if (!this.loadedPhases[phase]) { this.loadedPhases[phase] = await this.loadPhaseContent(phase, this.currentSession.domain); } const content = this.loadedPhases[phase]?.methodology_content || ''; // Extract checklist items as requirements const requirements = []; const checklistPattern = /- \[ \] (.*?)(?:\n|$)/gm; const matches = Array.from(content.matchAll(checklistPattern)); for (const match of matches) { if (match[1]) { // Clean up the requirement text let cleaned = match[1].replace(/\*\*(.*?)\*\*/g, '$1'); // Remove bold markers cleaned = cleaned.replace(/[šŸ“ŠšŸŸ¢šŸŸ”šŸ”“šŸšØāš ļøāœ…āŒ]/g, '').trim(); // Remove emojis if (cleaned) { requirements.push(cleaned); } } } return requirements; } calculateQualityScore(phase, completedItems) { // Basic quality score based on completion let baseScore = Math.min(completedItems.length / 10, 1.0) * 100; // Domain-specific quality adjustments for business-central if (this.currentSession.domain === 'business-central') { if (phase === 'analysis') { // Bonus for finding critical modules if (completedItems.some(item => item.includes('ReportGeneration') || item.includes('Reporting'))) { baseScore += 10; // Bonus for finding historically missed module } if (completedItems.filter(item => item.toLowerCase().includes('module')).length >= 9) { baseScore += 15; // Bonus for complete module coverage } } else if (phase === 'performance') { // Bonus for applying advanced BC patterns if (completedItems.some(item => item.includes('SIFT') || item.includes('FlowField'))) { baseScore += 20; // Bonus for advanced BC optimizations } } } return Math.min(baseScore, 100.0); } getNextActions(phase, missingItems, completion) { const actions = []; if (completion < 50) { actions.push(`Continue ${phase} phase - less than 50% complete`); actions.push(`Focus on completing: ${missingItems.slice(0, 3).join(', ')}`); // Top 3 missing items } else if (completion < 80) { actions.push(`Near completion of ${phase} phase`); actions.push(`Complete remaining items: ${missingItems.join(', ')}`); } else if (completion >= 80) { actions.push(`${phase} phase ready for completion`); const currentPhaseIndex = this.currentSession.phases.indexOf(phase); if (currentPhaseIndex < this.currentSession.phases.length - 1) { const nextPhase = this.currentSession.phases[currentPhaseIndex + 1]; actions.push(`Ready to proceed to ${nextPhase} phase`); } } return actions; } getValidationCriteria(phases) { const criteria = {}; for (const phase of phases) { const phaseCriteria = this.indexData.validation_criteria?.[phase] || {}; criteria[phase] = phaseCriteria; } return criteria; } estimateDuration(phases) { // Basic time estimates (in hours) const phaseTimes = { analysis: 2, performance: 4, architecture: 6, coding: 8, testability: 3, documentation: 2 }; const totalHours = phases.reduce((sum, phase) => sum + (phaseTimes[phase] || 2), 0); if (totalHours <= 4) { return '2-4 hours'; } else if (totalHours <= 8) { return '4-8 hours (full day)'; } else { return `${Math.floor(totalHours / 8)} days (${totalHours} hours)`; } } extractStepContent(phaseContent, step) { const content = phaseContent.methodology_content; // Try multiple patterns to find step-specific content const patterns = [ new RegExp(`### ${step}(.*?)(?=###|$)`, 'si'), // Exact match new RegExp(`### .*${step}.*?(.*?)(?=###|$)`, 'si'), // Partial match new RegExp(`### Step \\d+:? ?${step}(.*?)(?=###|$)`, 'si'), // Step N: format new RegExp(`### .*${step.replace(/\s+/g, '.*')}.*?(.*?)(?=###|$)`, 'si') // Flexible word matching ]; for (const pattern of patterns) { const match = content.match(pattern); if (match && match[1]) { return { step_name: step, content: match[1].trim(), checklists: this.extractChecklists(match[1]), success_criteria: this.extractSuccessCriteria(match[1]) }; } } // If no specific step found, return the full phase content return { step_name: step, content: `Step '${step}' not found as separate section. Here is the full phase content:`, full_phase_content: content, checklists: phaseContent.checklists, success_criteria: phaseContent.success_criteria }; } } //# sourceMappingURL=methodology-service.js.map