bc-code-intelligence-mcp
Version:
BC Code Intelligence MCP Server - Complete Specialist Bundle with AI-driven expert consultation, seamless handoffs, and context-preserving workflows
612 lines (604 loc) ⢠24.7 kB
JavaScript
/**
* Embedded Knowledge Layer
*
* Loads knowledge content from the embedded git submodule (embedded-knowledge/).
* This is the base layer (Layer 0) that provides the core BC knowledge content.
*/
import { fileURLToPath } from 'url';
import { readFile, stat, access } from 'fs/promises';
import { join, basename, dirname } from 'path';
import { existsSync } from 'fs';
import * as yaml from 'yaml';
import glob from 'fast-glob';
import { AtomicTopicFrontmatterSchema } from '../types/bc-knowledge.js';
import { LayerPriority } from '../types/layer-types.js';
import { BaseKnowledgeLayer } from './base-layer.js';
export class EmbeddedKnowledgeLayer extends BaseKnowledgeLayer {
embeddedPath;
// Support for MultiContentKnowledgeLayer interface
supported_content_types = ['topics', 'specialists', 'methodologies'];
constructor(embeddedPath = (() => {
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
return join(__dirname, '../../embedded-knowledge');
})()) {
super('embedded', LayerPriority.EMBEDDED, true);
this.embeddedPath = embeddedPath;
}
/**
* Initialize the embedded layer by loading topics and indexes from submodule
*/
async initialize() {
if (this.initialized && this.loadResult) {
return this.loadResult;
}
const startTime = Date.now();
try {
console.error(`Initializing ${this.name} layer from ${this.embeddedPath}...`);
// Early validation: Check if embedded knowledge directory exists and has content
if (!existsSync(this.embeddedPath)) {
throw new Error(`
šØ BC Code Intelligence MCP Server Setup Issue
PROBLEM: Embedded knowledge directory not found
PATH: ${this.embeddedPath}
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
The embedded-knowledge directory should contain BC expertise (domains/ or topics/, specialists/, methodologies/) required for the MCP server to function.
`.trim());
}
// Check if directory has the expected structure (domains/ or topics/ are both valid)
const hasDomains = existsSync(join(this.embeddedPath, 'domains'));
const hasTopics = existsSync(join(this.embeddedPath, 'topics'));
const hasSpecialists = existsSync(join(this.embeddedPath, 'specialists'));
const hasMethodologies = existsSync(join(this.embeddedPath, 'methodologies'));
const missingDirs = [];
if (!hasDomains && !hasTopics)
missingDirs.push('domains/ or topics/');
if (!hasSpecialists)
missingDirs.push('specialists');
if (!hasMethodologies)
missingDirs.push('methodologies');
if (missingDirs.length > 0) {
throw new Error(`
šØ BC Code Intelligence MCP Server Setup Issue
PROBLEM: Incomplete embedded knowledge structure
PATH: ${this.embeddedPath}
MISSING: ${missingDirs.join(', ')}
LIKELY CAUSE: The embedded-knowledge submodule is present but incomplete or corrupted.
SOLUTIONS:
š¦ For NPM users: Update to the latest version with: npm update bc-code-intelligence-mcp
š§ For developers: Run: git submodule update --remote --force
š¢ For package maintainers: Verify submodule content before npm publish
Expected structure: (domains/ or topics/), specialists/, methodologies/ directories with BC expertise content.
`.trim());
}
// Load topics, specialists, and indexes in parallel
const [topicsLoaded, specialistsLoaded, indexesLoaded] = await Promise.all([
this.loadTopics(),
this.loadSpecialists(),
this.loadIndexes()
]);
const loadTimeMs = Date.now() - startTime;
this.initialized = true;
this.loadResult = this.createLoadResult(topicsLoaded, indexesLoaded, loadTimeMs);
console.error(`ā
${this.name} layer loaded: ${topicsLoaded} topics, ${specialistsLoaded} specialists, ${indexesLoaded} indexes (${loadTimeMs}ms)`);
return this.loadResult;
}
catch (error) {
const loadTimeMs = Date.now() - startTime;
const errorMessage = error instanceof Error ? error.message : String(error);
this.loadResult = this.createErrorResult(errorMessage, loadTimeMs);
console.error(`ā Failed to initialize ${this.name} layer: ${errorMessage}`);
return this.loadResult;
}
}
/**
* Load all atomic topics from embedded knowledge (supports both domains/ and topics/)
*/
async loadTopics() {
// Support both domains/ and topics/ directory structures
const domainsPath = join(this.embeddedPath, 'domains');
const topicsPath = join(this.embeddedPath, 'topics');
// Determine which directory exists
const useDomainsPath = existsSync(domainsPath);
const useTopicsPath = existsSync(topicsPath);
const basePath = useDomainsPath ? domainsPath : (useTopicsPath ? topicsPath : domainsPath);
const dirType = useDomainsPath ? 'domains' : (useTopicsPath ? 'topics' : 'domains (not found)');
// Diagnostic logging for path resolution across platforms
console.error(`š Loading topics from: ${basePath}`);
console.error(` Platform: ${process.platform}`);
console.error(` Directory exists: ${existsSync(basePath)} (using ${dirType})`);
// Use glob to find all markdown files, excluding samples
// Normalize path separators for cross-platform compatibility
let pattern = join(basePath, '**', '*.md');
// fast-glob expects forward slashes on all platforms
pattern = pattern.replace(/\\/g, '/');
// Handle Windows drive letter normalization (e.g., /c/ to C:/)
if (process.platform === 'win32' && pattern.startsWith('/c/')) {
pattern = 'C:' + pattern.substring(2);
}
console.error(`š Using glob pattern: ${pattern}`);
try {
const topicFiles = await glob(pattern, {
ignore: ['**/samples/**'], // Ignore sample files
absolute: true, // Return absolute paths
onlyFiles: true // Only return files, not directories
});
console.error(` Found ${topicFiles.length} topic files`);
if (topicFiles.length === 0) {
console.error(`ā ļø Warning: No topic files found. Check if embedded-knowledge is properly populated.`);
}
let loadedCount = 0;
for (const filePath of topicFiles) {
try {
const topic = await this.loadAtomicTopic(filePath);
if (topic && this.validateTopic(topic)) {
this.topics.set(topic.id, topic);
loadedCount++;
}
else {
console.error(`Invalid topic structure in ${filePath}`);
}
}
catch (error) {
console.error(`Failed to load topic ${filePath}:`, error instanceof Error ? error.message : String(error));
}
}
return loadedCount;
}
catch (error) {
console.error(`ā Fatal error loading topics from ${domainsPath}:`, error);
console.error(` Error type: ${error instanceof Error ? error.constructor.name : typeof error}`);
console.error(` Error message: ${error instanceof Error ? error.message : String(error)}`);
if (error instanceof Error && error.stack) {
console.error(` Stack trace: ${error.stack}`);
}
throw error; // Re-throw to be caught by initialize()
}
}
/**
* Load indexes from embedded knowledge
*/
async loadIndexes() {
const indexesPath = join(this.embeddedPath, 'indexes');
try {
// Load tag indexes
await this.loadTagIndexes(indexesPath);
// Load other indexes
await this.loadDomainCatalog(indexesPath);
await this.loadTopicRelationships(indexesPath);
await this.loadBCVersionMatrix(indexesPath);
return this.indexes.size;
}
catch (error) {
console.error(`Failed to load indexes from ${indexesPath}:`, error instanceof Error ? error.message : String(error));
return 0;
}
}
/**
* Load a single atomic topic from a markdown file
*/
async loadAtomicTopic(filePath) {
const content = await readFile(filePath, 'utf-8');
const stats = await stat(filePath);
// Normalize line endings
const normalizedContent = content.replace(/\r\n/g, '\n').replace(/\r/g, '\n');
// Extract YAML frontmatter
const frontmatterMatch = normalizedContent.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
if (!frontmatterMatch) {
return null;
}
const [, frontmatterContent, markdownContent] = frontmatterMatch;
// Parse and validate frontmatter
const frontmatterData = yaml.parse(frontmatterContent || '');
const frontmatter = AtomicTopicFrontmatterSchema.parse(frontmatterData);
// Generate topic ID from file path
const topicId = this.normalizeTopicId(filePath, this.embeddedPath);
// Load companion sample file if exists
let samples;
if (frontmatter.samples) {
const samplesPath = join(filePath, '..', frontmatter.samples);
try {
const sampleContent = await readFile(samplesPath, 'utf-8');
samples = { filePath: samplesPath, content: sampleContent };
}
catch {
// Sample file doesn't exist, which is OK
}
}
return {
id: topicId,
title: frontmatter.title || topicId.replace(/-/g, ' '), // Use frontmatter title or derive from ID
filePath,
frontmatter,
content: markdownContent?.trim() || '',
wordCount: markdownContent?.split(/\s+/).length || 0,
lastModified: stats.mtime,
samples: samples || undefined
};
}
/**
* Load tag indexes from JSON files
*/
async loadTagIndexes(indexesPath) {
const tagsPath = join(indexesPath, 'tags');
try {
const tagFiles = await glob('*.json', { cwd: tagsPath });
for (const tagFile of tagFiles) {
const tagName = basename(tagFile, '.json');
const filePath = join(tagsPath, tagFile);
try {
const content = await readFile(filePath, 'utf-8');
const cleanContent = content.replace(/^\uFEFF/, ''); // Remove BOM
const topics = JSON.parse(cleanContent);
this.indexes.set(`tag:${tagName}`, {
tag: tagName,
topics,
count: topics.length
});
}
catch (error) {
console.error(`Failed to load tag index ${tagFile}:`, error instanceof Error ? error.message : String(error));
}
}
}
catch (error) {
// Tags directory might not exist, which is OK
console.error(`No tags directory found at ${tagsPath}`);
}
}
/**
* Load domain catalog
*/
async loadDomainCatalog(indexesPath) {
const catalogPath = join(indexesPath, 'domain-catalog.json');
try {
const content = await readFile(catalogPath, 'utf-8');
const cleanContent = content.replace(/^\uFEFF/, '');
const catalog = JSON.parse(cleanContent);
this.indexes.set('domain-catalog', catalog);
}
catch (error) {
console.error(`Failed to load domain catalog:`, error instanceof Error ? error.message : String(error));
}
}
/**
* Load topic relationships
*/
async loadTopicRelationships(indexesPath) {
const relationshipsPath = join(indexesPath, 'topic-relationships.json');
try {
const content = await readFile(relationshipsPath, 'utf-8');
const cleanContent = content.replace(/^\uFEFF/, '');
const relationships = JSON.parse(cleanContent);
this.indexes.set('topic-relationships', relationships);
}
catch (error) {
console.error(`Failed to load topic relationships:`, error instanceof Error ? error.message : String(error));
}
}
/**
* Load BC version matrix
*/
async loadBCVersionMatrix(indexesPath) {
const matrixPath = join(indexesPath, 'bc-version-matrix.json');
try {
const content = await readFile(matrixPath, 'utf-8');
const cleanContent = content.replace(/^\uFEFF/, '');
const matrix = JSON.parse(cleanContent);
this.indexes.set('bc-version-matrix', matrix);
}
catch (error) {
console.error(`Failed to load BC version matrix:`, error instanceof Error ? error.message : String(error));
}
}
/**
* Get an index by name
*/
getIndex(indexName) {
return this.indexes.get(indexName);
}
/**
* Get all available index names
*/
getIndexNames() {
return Array.from(this.indexes.keys());
}
/**
* Load all specialists from embedded knowledge specialists folder
*/
async loadSpecialists() {
const specialistsPath = join(this.embeddedPath, 'specialists');
try {
// Check if specialists directory exists
const specialistsStats = await stat(specialistsPath);
if (!specialistsStats.isDirectory()) {
console.error(`ā ļø Specialists path is not a directory: ${specialistsPath}`);
return 0;
}
}
catch (error) {
console.error(`ā ļø Specialists directory not found: ${specialistsPath}`);
return 0;
}
// Use glob to find all markdown files in specialists
let pattern = join(specialistsPath, '*.md').replace(/\\/g, '/');
// Convert /c/path to C:/path on Windows
if (pattern.startsWith('/c/')) {
pattern = 'C:' + pattern.substring(2);
}
console.error(`š Using specialists glob pattern: ${pattern}`);
const specialistFiles = await glob(pattern);
console.error(`Found ${specialistFiles.length} specialist files in ${specialistsPath}`);
let loadedCount = 0;
for (const filePath of specialistFiles) {
try {
const specialist = await this.loadSpecialist(filePath);
if (specialist && this.validateSpecialist(specialist)) {
this.specialists.set(specialist.specialist_id, specialist);
loadedCount++;
}
else {
console.error(`Invalid specialist structure in ${filePath}`);
}
}
catch (error) {
console.error(`Failed to load specialist ${filePath}:`, error instanceof Error ? error.message : String(error));
}
}
console.error(`š Loaded ${loadedCount} specialists from embedded layer`);
return loadedCount;
}
/**
* Load a single specialist from a markdown file
*/
async loadSpecialist(filePath) {
try {
const content = await readFile(filePath, 'utf-8');
const normalizedContent = content.replace(/^\uFEFF/, '').replace(/\r\n/g, '\n');
// Extract YAML frontmatter
const frontmatterMatch = normalizedContent.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
if (!frontmatterMatch) {
console.error(`ā ļø No frontmatter found in ${filePath}`);
return null;
}
const [, frontmatterContent, markdownContent] = frontmatterMatch;
// Parse and validate frontmatter
const frontmatterData = yaml.parse(frontmatterContent || '');
// Validate required fields
if (!frontmatterData.specialist_id || !frontmatterData.title) {
console.error(`ā ļø Missing required fields in ${filePath}`);
return null;
}
// Create specialist definition
const specialist = {
title: frontmatterData.title,
specialist_id: frontmatterData.specialist_id,
emoji: frontmatterData.emoji || 'š¤',
role: frontmatterData.role || 'Specialist',
team: frontmatterData.team || 'General',
persona: {
personality: frontmatterData.persona?.personality || [],
communication_style: frontmatterData.persona?.communication_style || '',
greeting: frontmatterData.persona?.greeting || `${frontmatterData.emoji || 'š¤'} Hello!`
},
expertise: {
primary: frontmatterData.expertise?.primary || [],
secondary: frontmatterData.expertise?.secondary || []
},
domains: frontmatterData.domains || [],
when_to_use: frontmatterData.when_to_use || [],
collaboration: {
natural_handoffs: frontmatterData.collaboration?.natural_handoffs || [],
team_consultations: frontmatterData.collaboration?.team_consultations || []
},
related_specialists: frontmatterData.related_specialists || [],
content: markdownContent.trim()
};
return specialist;
}
catch (error) {
console.error(`ā Failed to parse specialist file ${filePath}:`, error);
return null;
}
}
/**
* Validate specialist definition
*/
validateSpecialist(specialist) {
return !!(specialist.specialist_id &&
specialist.title &&
specialist.persona &&
specialist.expertise);
}
/**
* Load methodologies from methodologies/ directory
*/
async loadMethodologies() {
const methodologiesPath = join(this.embeddedPath, 'methodologies');
try {
await access(methodologiesPath);
// TODO: Implement methodology loading when structure is defined
}
catch (error) {
// methodologies/ directory might not exist - that's okay
}
return this.methodologies.size;
}
/**
* Check if the layer has a specific specialist
*/
hasSpecialist(specialistId) {
return this.specialists.has(specialistId);
}
/**
* Get a specialist from this layer
*/
getSpecialist(specialistId) {
return this.specialists.get(specialistId) || null;
}
/**
* Get all specialist IDs available in this layer
*/
getSpecialistIds() {
return Array.from(this.specialists.keys());
}
/**
* Get all specialists from this layer
*/
getAllSpecialists() {
return Array.from(this.specialists.values());
}
/**
* Search for specialists within this layer
*/
searchSpecialists(query, limit) {
const queryLower = query.toLowerCase();
const matches = [];
for (const specialist of this.specialists.values()) {
let score = 0;
// Check title
if (specialist.title.toLowerCase().includes(queryLower)) {
score += 10;
}
// Check expertise
for (const expertise of [...specialist.expertise.primary, ...specialist.expertise.secondary]) {
if (expertise.toLowerCase().includes(queryLower)) {
score += specialist.expertise.primary.includes(expertise) ? 8 : 5;
}
}
// Check domains
for (const domain of specialist.domains) {
if (domain.toLowerCase().includes(queryLower)) {
score += 6;
}
}
// Check when_to_use scenarios
for (const scenario of specialist.when_to_use) {
if (scenario.toLowerCase().includes(queryLower)) {
score += 7;
}
}
if (score > 0) {
matches.push({ specialist, score });
}
}
// Sort by score and apply limit
const sortedMatches = matches
.sort((a, b) => b.score - a.score)
.slice(0, limit || matches.length);
return sortedMatches.map(m => m.specialist);
}
/**
* Get specialist statistics for this layer
*/
getSpecialistStatistics() {
const specialists = Array.from(this.specialists.values());
const teams = {};
const domains = {};
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;
}
}
return {
total_specialists: specialists.length,
teams,
domains
};
}
// MultiContentKnowledgeLayer interface implementation
/**
* Check if content exists by type and ID
*/
hasContent(type, id) {
switch (type) {
case 'topics':
return this.topics.has(id);
case 'specialists':
return this.specialists.has(id);
default:
return false;
}
}
/**
* Get content by type and ID
*/
async getContent(type, id) {
switch (type) {
case 'topics':
return this.topics.get(id) || null;
case 'specialists':
return this.specialists.get(id) || null;
default:
return null;
}
}
/**
* Get all content IDs for a specific type
*/
getContentIds(type) {
switch (type) {
case 'topics':
return Array.from(this.topics.keys());
case 'specialists':
return Array.from(this.specialists.keys());
default:
return [];
}
}
/**
* Search content within this layer by type
*/
searchContent(type, query, limit = 10) {
switch (type) {
case 'topics':
return this.searchTopics(query, limit);
case 'specialists':
return this.searchSpecialists(query, limit);
default:
return [];
}
}
/**
* Get enhanced statistics with content type breakdown
*/
getEnhancedStatistics() {
return {
name: this.name,
priority: this.priority,
content_counts: {
topics: this.topics.size,
specialists: this.specialists.size,
methodologies: 0 // Not supported yet
},
load_time_ms: this.loadResult?.loadTimeMs,
initialized: this.initialized
};
}
/**
* Convert LayerLoadResult to EnhancedLayerLoadResult
*/
convertToEnhancedResult(result) {
return {
success: result.success,
layer_name: result.layerName,
load_time_ms: result.loadTimeMs,
content_counts: {
topics: this.topics.size,
specialists: this.specialists.size,
methodologies: 0
},
topics_loaded: this.topics.size, // Use actual Map size for consistency
indexes_loaded: result.indexesLoaded || 0,
error: result.success ? undefined : 'Layer load failed'
};
}
}
//# sourceMappingURL=embedded-layer.js.map