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

256 lines 10.3 kB
/** * Specialist Loader Service * * Loads and parses specialist persona definitions from markdown files * with rich YAML frontmatter including personality traits, collaboration * patterns, and roleplay information. */ import * as fs from 'fs/promises'; import * as path from 'path'; import * as yaml from 'yaml'; export class SpecialistLoader { specialistsPath; specialistCache = new Map(); loaded = false; constructor(specialistsPath) { this.specialistsPath = specialistsPath; } /** * Load all specialist definitions from the specialists folder */ async loadAllSpecialists() { if (this.loaded && this.specialistCache.size > 0) { return this.specialistCache; } console.error('📋 Loading specialist personas...'); try { const specialistFiles = await fs.readdir(this.specialistsPath); const markdownFiles = specialistFiles.filter(file => file.endsWith('.md')); console.error(`📋 Found ${markdownFiles.length} specialist files`); for (const file of markdownFiles) { const filePath = path.join(this.specialistsPath, file); const specialist = await this.loadSpecialist(filePath); if (specialist) { this.specialistCache.set(specialist.specialist_id, specialist); } } this.loaded = true; console.error(`🎭 Successfully loaded ${this.specialistCache.size} specialists`); return this.specialistCache; } catch (error) { console.error('❌ Failed to load specialists:', error); throw error; } } /** * Load a single specialist from a markdown file */ async loadSpecialist(filePath) { try { const fileContent = await fs.readFile(filePath, 'utf-8'); return this.parseSpecialistFile(fileContent, filePath); } catch (error) { console.error(`❌ Failed to load specialist from ${filePath}:`, error); return null; } } /** * Parse specialist markdown file with YAML frontmatter */ parseSpecialistFile(content, filePath) { try { // Extract frontmatter and content const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/); if (!frontmatterMatch) { console.error(`⚠️ No frontmatter found in ${filePath}`); return null; } const [, frontmatterStr, markdownContent] = frontmatterMatch; // Parse YAML frontmatter const frontmatter = yaml.parse(frontmatterStr); // Validate required fields if (!frontmatter.specialist_id || !frontmatter.title) { console.error(`⚠️ Missing required fields in ${filePath}`); return null; } // Create specialist definition const specialist = { title: frontmatter.title, specialist_id: frontmatter.specialist_id, emoji: frontmatter.emoji || '🤖', role: frontmatter.role || 'Specialist', team: frontmatter.team || 'General', persona: { personality: frontmatter.persona?.personality || [], communication_style: frontmatter.persona?.communication_style || '', greeting: frontmatter.persona?.greeting || `${frontmatter.emoji || '🤖'} Hello!` }, expertise: { primary: frontmatter.expertise?.primary || [], secondary: frontmatter.expertise?.secondary || [] }, domains: frontmatter.domains || [], when_to_use: frontmatter.when_to_use || [], collaboration: { natural_handoffs: frontmatter.collaboration?.natural_handoffs || [], team_consultations: frontmatter.collaboration?.team_consultations || [] }, related_specialists: frontmatter.related_specialists || [], content: markdownContent.trim() }; return specialist; } catch (error) { console.error(`❌ Failed to parse specialist file ${filePath}:`, error); return null; } } /** * Get a specific specialist by ID */ async getSpecialist(specialistId) { if (!this.loaded) { await this.loadAllSpecialists(); } return this.specialistCache.get(specialistId) || null; } /** * Get all loaded specialists */ async getAllSpecialists() { if (!this.loaded) { await this.loadAllSpecialists(); } return Array.from(this.specialistCache.values()); } /** * Get specialists by team */ async getSpecialistsByTeam(team) { const specialists = await this.getAllSpecialists(); return specialists.filter(s => s.team === team); } /** * Get specialists by domain expertise */ async getSpecialistsByDomain(domain) { const specialists = await this.getAllSpecialists(); return specialists.filter(s => s.domains && s.domains.includes(domain)); } /** * Extract character guide sections from specialist content */ extractCharacterGuide(specialist) { const content = specialist.content; // Extract major sections using markdown headers const sections = content.split(/(?=^##\s)/m); return { identity_section: this.extractSection(sections, 'Character Identity') || '', process_section: this.extractSection(sections, 'Process') || '', examples_section: this.extractSection(sections, 'Examples') || '', collaboration_section: this.extractSection(sections, 'Collaboration') || '' }; } extractSection(sections, sectionName) { const section = sections.find(s => s.toLowerCase().includes(sectionName.toLowerCase())); return section ? section.trim() : ''; } /** * Find specialists who can collaborate with the given specialist */ async getCollaborators(specialistId) { const specialist = await this.getSpecialist(specialistId); if (!specialist) { return { handoffs: [], consultations: [], related: [] }; } const allSpecialists = await this.getAllSpecialists(); const handoffs = specialist.collaboration.natural_handoffs .map(id => allSpecialists.find(s => s.specialist_id === id)) .filter(s => s); const consultations = specialist.collaboration.team_consultations .map(id => allSpecialists.find(s => s.specialist_id === id)) .filter(s => s); const related = specialist.related_specialists .map(id => allSpecialists.find(s => s.specialist_id === id)) .filter(s => s); return { handoffs, consultations, related }; } /** * Get specialist suggestions for a given context or problem */ async suggestSpecialist(context, problemType) { const specialists = await this.getAllSpecialists(); const suggestions = []; for (const specialist of specialists) { let score = 0; // Check when_to_use scenarios if (specialist.when_to_use) { for (const scenario of specialist.when_to_use) { if (context.toLowerCase().includes(scenario.toLowerCase()) || scenario.toLowerCase().includes(context.toLowerCase())) { score += 10; } } } // Check expertise areas if (specialist.expertise?.primary || specialist.expertise?.secondary) { const primary = specialist.expertise.primary || []; const secondary = specialist.expertise.secondary || []; for (const expertise of [...primary, ...secondary]) { if (context.toLowerCase().includes(expertise.toLowerCase()) || expertise.toLowerCase().includes(context.toLowerCase())) { score += primary.includes(expertise) ? 8 : 5; } } } // Check domains if (specialist.domains) { for (const domain of specialist.domains) { if (context.toLowerCase().includes(domain.toLowerCase()) || domain.toLowerCase().includes(context.toLowerCase())) { score += 6; } } } if (score > 0) { suggestions.push({ specialist, score }); } } // Sort by score and return top suggestions return suggestions .sort((a, b) => b.score - a.score) .slice(0, 5) .map(s => s.specialist); } /** * Get statistics about loaded specialists */ getStatistics() { const specialists = Array.from(this.specialistCache.values()); const teams = {}; const domains = {}; let totalCollaborators = 0; for (const specialist of specialists) { // Count teams teams[specialist.team] = (teams[specialist.team] || 0) + 1; // Count domains for (const domain of specialist.domains) { domains[domain] = (domains[domain] || 0) + 1; } // Count collaborators totalCollaborators += specialist.collaboration.natural_handoffs.length + specialist.collaboration.team_consultations.length + specialist.related_specialists.length; } return { total_specialists: specialists.length, teams, domains, average_collaborators: specialists.length > 0 ? totalCollaborators / specialists.length : 0 }; } } //# sourceMappingURL=specialist-loader.js.map