mongodb-memory-bank-mcp
Version:
FIXED: MongoDB Memory Bank MCP with bulletproof error handling, smart operations, and session state management. Eliminates [object Object] errors and user confusion.
234 lines (233 loc) • 10.2 kB
JavaScript
/**
* Content Routing Service
* Implements intelligent content routing to maintain clean 6-file Cline structure
* Prevents content accumulation by routing new content to appropriate core files
*/
import { CLINE_CORE_FILES } from './cline-memory-structure.js';
export class ContentRoutingService {
/**
* 🎯 CORE ROUTING LOGIC: Analyze content and determine target core file
*/
static routeContent(fileName, content, existingFiles) {
// 1. If it's already a core file, route to itself
if (Object.values(CLINE_CORE_FILES).includes(fileName)) {
return {
targetFile: fileName,
shouldMerge: true,
mergeStrategy: 'merge',
confidence: 100,
reasoning: 'Direct core file match'
};
}
// 2. Analyze content to determine appropriate core file
const analysis = this.analyzeContent(fileName, content);
const targetFile = this.determineTargetFile(analysis, existingFiles);
return {
targetFile,
shouldMerge: true,
mergeStrategy: analysis.isAnalysis ? 'merge' : 'append',
confidence: this.calculateConfidence(analysis, targetFile),
reasoning: this.generateReasoning(analysis, targetFile)
};
}
/**
* 🔍 CONTENT ANALYSIS: Determine content type and characteristics
*/
static analyzeContent(fileName, content) {
const lowerFileName = fileName.toLowerCase();
const lowerContent = content.toLowerCase();
// Extract keywords for analysis
const keywords = this.extractKeywords(lowerContent);
// Determine if this is analysis content (should be merged, not appended)
const isAnalysis = lowerFileName.includes('analysis') ||
lowerContent.includes('analysis') ||
lowerContent.includes('comparison') ||
lowerContent.includes('assessment') ||
lowerContent.includes('evaluation');
// Determine if this is an update to existing content
const isUpdate = lowerContent.includes('update') ||
lowerContent.includes('progress') ||
lowerContent.includes('status');
// Determine content type based on keywords and patterns
let contentType = 'active'; // default
if (this.hasKeywords(keywords, ['mongodb', 'database', 'vector', 'search', 'api', 'configuration', 'setup'])) {
contentType = 'technical';
}
else if (this.hasKeywords(keywords, ['architecture', 'pattern', 'design', 'structure', 'implementation'])) {
contentType = 'architectural';
}
else if (this.hasKeywords(keywords, ['market', 'competitive', 'product', 'user', 'business'])) {
contentType = 'product';
}
else if (this.hasKeywords(keywords, ['project', 'goal', 'objective', 'mission', 'brief'])) {
contentType = 'project';
}
else if (this.hasKeywords(keywords, ['progress', 'status', 'milestone', 'roadmap', 'timeline'])) {
contentType = 'progress';
}
return {
contentType,
keywords,
isAnalysis,
isUpdate
};
}
/**
* 🎯 TARGET FILE DETERMINATION: Map content type to core file
*/
static determineTargetFile(analysis, existingFiles) {
const mapping = {
'technical': CLINE_CORE_FILES.TECH_CONTEXT,
'architectural': CLINE_CORE_FILES.SYSTEM_PATTERNS,
'product': CLINE_CORE_FILES.PRODUCT_CONTEXT,
'project': CLINE_CORE_FILES.PROJECT_BRIEF,
'active': CLINE_CORE_FILES.ACTIVE_CONTEXT,
'progress': CLINE_CORE_FILES.PROGRESS
};
const targetFile = mapping[analysis.contentType];
// Ensure target file exists in the project
if (existingFiles.includes(targetFile)) {
return targetFile;
}
// Fallback to activeContext.md if target doesn't exist
return existingFiles.includes(CLINE_CORE_FILES.ACTIVE_CONTEXT)
? CLINE_CORE_FILES.ACTIVE_CONTEXT
: existingFiles[0] || CLINE_CORE_FILES.ACTIVE_CONTEXT;
}
/**
* 🔍 KEYWORD EXTRACTION: Extract relevant keywords from content
*/
static extractKeywords(content) {
// Remove common words and extract meaningful terms
const words = content.toLowerCase()
.replace(/[^\w\s]/g, ' ')
.split(/\s+/)
.filter(word => word.length > 3)
.filter(word => !this.isCommonWord(word));
// Return unique keywords
return [...new Set(words)];
}
/**
* 🚫 COMMON WORD FILTER: Filter out common English words
*/
static isCommonWord(word) {
const commonWords = new Set([
'this', 'that', 'with', 'have', 'will', 'from', 'they', 'know', 'want', 'been',
'good', 'much', 'some', 'time', 'very', 'when', 'come', 'here', 'just', 'like',
'long', 'make', 'many', 'over', 'such', 'take', 'than', 'them', 'well', 'were'
]);
return commonWords.has(word);
}
/**
* ✅ KEYWORD MATCHING: Check if content has specific keywords
*/
static hasKeywords(keywords, targetKeywords) {
return targetKeywords.some(target => keywords.some(keyword => keyword.includes(target) || target.includes(keyword)));
}
/**
* 📊 CONFIDENCE CALCULATION: Calculate routing confidence score
*/
static calculateConfidence(analysis, targetFile) {
let confidence = 70; // base confidence
// Increase confidence for clear content type matches
if (analysis.contentType === 'technical' && targetFile === CLINE_CORE_FILES.TECH_CONTEXT)
confidence += 20;
if (analysis.contentType === 'architectural' && targetFile === CLINE_CORE_FILES.SYSTEM_PATTERNS)
confidence += 20;
if (analysis.contentType === 'product' && targetFile === CLINE_CORE_FILES.PRODUCT_CONTEXT)
confidence += 20;
if (analysis.contentType === 'project' && targetFile === CLINE_CORE_FILES.PROJECT_BRIEF)
confidence += 20;
if (analysis.contentType === 'progress' && targetFile === CLINE_CORE_FILES.PROGRESS)
confidence += 20;
// Increase confidence for analysis content (should be merged)
if (analysis.isAnalysis)
confidence += 10;
return Math.min(confidence, 100);
}
/**
* 💭 REASONING GENERATION: Generate human-readable routing reasoning
*/
static generateReasoning(analysis, targetFile) {
const fileNames = {
[CLINE_CORE_FILES.TECH_CONTEXT]: 'technical context',
[CLINE_CORE_FILES.SYSTEM_PATTERNS]: 'system patterns',
[CLINE_CORE_FILES.PRODUCT_CONTEXT]: 'product context',
[CLINE_CORE_FILES.PROJECT_BRIEF]: 'project brief',
[CLINE_CORE_FILES.ACTIVE_CONTEXT]: 'active context',
[CLINE_CORE_FILES.PROGRESS]: 'progress tracking'
};
const fileName = fileNames[targetFile] || 'core file';
return `Content type '${analysis.contentType}' routed to ${fileName} based on keyword analysis`;
}
/**
* 🔄 CONTENT MERGING: Intelligently merge new content with existing content
*/
static mergeContent(existingContent, newContent, strategy) {
switch (strategy) {
case 'replace':
return newContent;
case 'append':
return existingContent + '\n\n---\n\n' + newContent;
case 'merge':
default:
// Intelligent merge: avoid duplication, organize by sections
return this.intelligentMerge(existingContent, newContent);
}
}
/**
* 🧠 INTELLIGENT MERGE: Smart content merging avoiding duplication
*/
static intelligentMerge(existingContent, newContent) {
// If new content is significantly different, append it
if (this.calculateSimilarity(existingContent, newContent) < 0.3) {
return existingContent + '\n\n## Additional Content\n\n' + newContent;
}
// If similar, try to merge sections intelligently
const existingSections = this.extractSections(existingContent);
const newSections = this.extractSections(newContent);
// Merge sections, preferring newer content for duplicates
const mergedSections = { ...existingSections, ...newSections };
return Object.entries(mergedSections)
.map(([title, content]) => `## ${title}\n\n${content}`)
.join('\n\n');
}
/**
* 📊 SIMILARITY CALCULATION: Calculate content similarity (simple implementation)
*/
static calculateSimilarity(content1, content2) {
const words1 = new Set(content1.toLowerCase().split(/\s+/));
const words2 = new Set(content2.toLowerCase().split(/\s+/));
const intersection = new Set([...words1].filter(word => words2.has(word)));
const union = new Set([...words1, ...words2]);
return intersection.size / union.size;
}
/**
* 📑 SECTION EXTRACTION: Extract sections from markdown content
*/
static extractSections(content) {
const sections = {};
const lines = content.split('\n');
let currentSection = 'Introduction';
let currentContent = [];
for (const line of lines) {
if (line.startsWith('## ')) {
// Save previous section
if (currentContent.length > 0) {
sections[currentSection] = currentContent.join('\n').trim();
}
// Start new section
currentSection = line.replace('## ', '').trim();
currentContent = [];
}
else {
currentContent.push(line);
}
}
// Save last section
if (currentContent.length > 0) {
sections[currentSection] = currentContent.join('\n').trim();
}
return sections;
}
}