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