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
JavaScript
/**
* 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