bc-code-intelligence-mcp
Version:
BC Code Intelligence MCP Server - Complete Specialist Bundle with AI-driven expert consultation, seamless handoffs, and context-preserving workflows
358 lines • 16.9 kB
JavaScript
/**
* Intelligent Search Engine with AI-Powered Recommendations
*
* Advanced search capabilities including semantic search, context understanding,
* intelligent topic recommendations, and learning from user patterns.
*/
import { isDomainMatch, getDomainList } from '../types/bc-knowledge.js';
import Fuse from 'fuse.js';
export class IntelligentSearchEngine {
enableSemanticSearch;
enableLearningRecommendations;
maxSearchHistory;
searchIndex = null;
topicRelationships = new Map();
searchHistory = [];
userPreferences = new Map(); // topic_id -> preference score
constructor(enableSemanticSearch = true, enableLearningRecommendations = true, maxSearchHistory = 100) {
this.enableSemanticSearch = enableSemanticSearch;
this.enableLearningRecommendations = enableLearningRecommendations;
this.maxSearchHistory = maxSearchHistory;
console.log('🧠 Intelligent search engine initialized');
}
/**
* Build intelligent search index from topics
*/
buildSearchIndex(topics) {
console.log(`🔍 Building intelligent search index with ${topics.length} topics...`);
// Build enhanced Fuse.js index with semantic weighting
this.searchIndex = new Fuse(topics, {
keys: [
{ name: 'frontmatter.title', weight: 0.25 },
{ name: 'frontmatter.tags', weight: 0.2 },
{ name: 'frontmatter.domain', weight: 0.15 },
{ name: 'content', weight: 0.15 },
{ name: 'frontmatter.prerequisites', weight: 0.1 },
{ name: 'frontmatter.related_topics', weight: 0.1 },
{ name: 'samples.content', weight: 0.05 }
],
threshold: 0.3, // More permissive for better semantic matching
includeScore: true,
includeMatches: true,
minMatchCharLength: 2,
ignoreLocation: true, // Better for semantic search
useExtendedSearch: true
});
// Build topic relationship graph
this.buildTopicRelationships(topics);
console.log('✅ Intelligent search index built successfully');
}
/**
* Perform intelligent search with context awareness
*/
async intelligentSearch(query, context, limit = 10) {
if (!this.searchIndex) {
throw new Error('Search index not initialized');
}
const startTime = Date.now();
// Enhanced query processing
const processedQuery = this.processSearchQuery(query, context);
// Perform base search
const searchResults = this.searchIndex.search(processedQuery, { limit: limit * 2 });
// Transform to smart results with AI-powered enhancements
const smartResults = [];
for (const result of searchResults.slice(0, limit)) {
const topic = result.item;
const baseResult = this.topicToSearchResult(topic, result.score || 0);
const smartResult = {
...baseResult,
relevance_reasons: this.calculateRelevanceReasons(topic, query, context, Array.from(result.matches || [])),
learning_path_position: this.calculateLearningPathPosition(topic, context),
prerequisite_satisfaction_score: this.calculatePrerequisiteSatisfaction(topic, context),
difficulty_match_score: this.calculateDifficultyMatch(topic, context),
context_relevance_score: this.calculateContextRelevance(topic, context),
recommendation_strength: this.calculateRecommendationStrength(topic, context, result.score || 0)
};
smartResults.push(smartResult);
}
// Re-rank results based on AI scoring
const rerankedResults = this.reRankResults(smartResults, context);
// Track search for learning
this.trackSearch(query, rerankedResults.map(r => r.id));
const duration = Date.now() - startTime;
console.log(`🔍 Intelligent search completed in ${duration}ms (${rerankedResults.length} results)`);
return rerankedResults;
}
/**
* Generate intelligent topic recommendations based on context
*/
async getTopicRecommendations(currentTopic, context, maxRecommendations = 5) {
const recommendations = [];
// Get related topics from relationship graph
const relatedTopics = this.topicRelationships.get(currentTopic) || [];
// Generate different types of recommendations
const prerequisiteRecs = this.generatePrerequisiteRecommendations(currentTopic, context);
const nextStepRecs = this.generateNextStepRecommendations(currentTopic, context);
const alternativeRecs = this.generateAlternativeRecommendations(currentTopic, context);
const deepDiveRecs = this.generateDeepDiveRecommendations(currentTopic, context);
recommendations.push(...prerequisiteRecs, ...nextStepRecs, ...alternativeRecs, ...deepDiveRecs);
// Score and sort recommendations
const scoredRecommendations = recommendations
.map(rec => ({
...rec,
final_score: this.calculateRecommendationScore(rec, context)
}))
.sort((a, b) => b.final_score - a.final_score)
.slice(0, maxRecommendations);
return scoredRecommendations.map(({ final_score, ...rec }) => rec);
}
/**
* Generate personalized learning path
*/
async generateLearningPath(goal, context) {
// This is a simplified implementation - in practice would use ML models
const pathTopics = await this.findPathTopics(goal, context);
const learningPath = {
path_id: `path_${Date.now()}`,
name: `Learning Path: ${goal}`,
description: `Personalized learning path to master ${goal} based on your experience level and context`,
topics: pathTopics.map((topic, index) => ({
topic_id: topic.id,
position: index + 1,
is_required: index < pathTopics.length * 0.7, // First 70% are required
estimated_time: topic.estimated_time || '15-30 minutes'
})),
total_estimated_time: this.calculateTotalTime(pathTopics),
difficulty_progression: this.calculateDifficultyProgression(pathTopics),
success_criteria: this.generateSuccessCriteria(goal, pathTopics)
};
return learningPath;
}
/**
* Update user preferences based on interaction patterns
*/
updateUserPreferences(viewedTopics, positiveInteractions, negativeInteractions = []) {
if (!this.enableLearningRecommendations)
return;
// Increase preference for positively interacted topics
for (const topicId of positiveInteractions) {
const current = this.userPreferences.get(topicId) || 0;
this.userPreferences.set(topicId, Math.min(10, current + 1));
}
// Decrease preference for negatively interacted topics
for (const topicId of negativeInteractions) {
const current = this.userPreferences.get(topicId) || 0;
this.userPreferences.set(topicId, Math.max(0, current - 1));
}
// Small positive signal for viewed topics
for (const topicId of viewedTopics) {
if (!positiveInteractions.includes(topicId)) {
const current = this.userPreferences.get(topicId) || 0;
this.userPreferences.set(topicId, Math.min(10, current + 0.1));
}
}
console.log(`🎯 Updated user preferences for ${viewedTopics.length} topics`);
}
// Private helper methods
processSearchQuery(query, context) {
let processedQuery = query.trim();
// Add context-aware query expansion
if (context.current_domain) {
processedQuery += ` domain:${context.current_domain}`;
}
if (context.user_code_context) {
// Extract relevant keywords from code context
const codeKeywords = this.extractCodeKeywords(context.user_code_context);
processedQuery += ` ${codeKeywords.join(' ')}`;
}
return processedQuery;
}
calculateRelevanceReasons(topic, query, context, matches) {
const reasons = [];
// Analyze matches to provide explanations
for (const match of matches) {
if (match.key === 'frontmatter.title') {
reasons.push('Title matches your search query');
}
else if (match.key === 'frontmatter.tags') {
reasons.push('Tags are highly relevant to your query');
}
else if (match.key === 'content') {
reasons.push('Content contains relevant information');
}
else if (match.key === 'frontmatter.domain' && context.current_domain) {
reasons.push('In your current working domain');
}
}
// Context-based reasons
if (topic.frontmatter.difficulty === context.difficulty_preference) {
reasons.push('Matches your preferred difficulty level');
}
if (context.recent_topics?.includes(topic.id)) {
reasons.push('Recently viewed topic');
}
// User preference reasons
const userPreference = this.userPreferences.get(topic.id) || 0;
if (userPreference > 7) {
reasons.push('Highly rated based on your history');
}
return reasons.length > 0 ? reasons : ['Content relevance match'];
}
calculateLearningPathPosition(topic, context) {
// Simplified calculation - would be more sophisticated in practice
const difficultyOrder = ['beginner', 'intermediate', 'advanced', 'expert'];
return difficultyOrder.indexOf(topic.frontmatter.difficulty) + 1;
}
calculatePrerequisiteSatisfaction(topic, context) {
const prerequisites = topic.frontmatter.prerequisites || [];
if (prerequisites.length === 0)
return 100;
const recentTopics = context.recent_topics || [];
const satisfiedPrereqs = prerequisites.filter(prereq => recentTopics.some(recent => recent.includes(prereq)));
return (satisfiedPrereqs.length / prerequisites.length) * 100;
}
calculateDifficultyMatch(topic, context) {
if (!context.difficulty_preference)
return 50;
const difficultyOrder = ['beginner', 'intermediate', 'advanced', 'expert'];
const topicLevel = difficultyOrder.indexOf(topic.frontmatter.difficulty);
const userLevel = difficultyOrder.indexOf(context.difficulty_preference);
// Perfect match = 100, adjacent levels = 70, further = lower
const distance = Math.abs(topicLevel - userLevel);
return Math.max(0, 100 - (distance * 30));
}
calculateContextRelevance(topic, context) {
let score = 0;
// Domain match
if (context.current_domain && isDomainMatch(topic.frontmatter.domain, context.current_domain)) {
score += 40;
}
// Code context relevance
if (context.user_code_context) {
const codeKeywords = this.extractCodeKeywords(context.user_code_context);
const topicContent = topic.content.toLowerCase();
const matchingKeywords = codeKeywords.filter(keyword => topicContent.includes(keyword.toLowerCase()));
score += (matchingKeywords.length / codeKeywords.length) * 30;
}
// Project type relevance
if (context.project_type) {
const projectKeywords = {
'new': ['setup', 'getting started', 'fundamentals', 'basics'],
'maintenance': ['debugging', 'troubleshooting', 'best practices'],
'optimization': ['performance', 'optimization', 'efficiency'],
'migration': ['migration', 'upgrade', 'transition']
};
const relevantKeywords = projectKeywords[context.project_type] || [];
const topicContent = topic.content.toLowerCase();
const matches = relevantKeywords.filter(keyword => topicContent.includes(keyword));
score += (matches.length > 0) ? 30 : 0;
}
return Math.min(100, score);
}
calculateRecommendationStrength(topic, context, searchScore) {
const contextScore = this.calculateContextRelevance(topic, context);
const difficultyScore = this.calculateDifficultyMatch(topic, context);
const prerequisiteScore = this.calculatePrerequisiteSatisfaction(topic, context);
const combinedScore = (contextScore + difficultyScore + prerequisiteScore + (100 - searchScore * 100)) / 4;
if (combinedScore > 75)
return 'high';
if (combinedScore > 50)
return 'medium';
return 'low';
}
reRankResults(results, context) {
return results.sort((a, b) => {
// Weight different factors
const scoreA = (a.context_relevance_score || 0) * 0.3 +
(a.difficulty_match_score || 0) * 0.25 +
(a.prerequisite_satisfaction_score || 0) * 0.2 +
a.relevance_score * 0.25;
const scoreB = (b.context_relevance_score || 0) * 0.3 +
(b.difficulty_match_score || 0) * 0.25 +
(b.prerequisite_satisfaction_score || 0) * 0.2 +
b.relevance_score * 0.25;
return scoreB - scoreA;
});
}
buildTopicRelationships(topics) {
for (const topic of topics) {
const relationships = [
...(topic.frontmatter.prerequisites || []),
...(topic.frontmatter.related_topics || [])
];
this.topicRelationships.set(topic.id, relationships);
}
}
extractCodeKeywords(codeContext) {
// Simple keyword extraction - could be enhanced with NLP
const keywords = codeContext
.toLowerCase()
.match(/\b(procedure|function|table|field|record|page|report|codeunit|query|flowfield|sift|calcsum|setrange|findset)\w*\b/g) || [];
return [...new Set(keywords)]; // Remove duplicates
}
topicToSearchResult(topic, relevanceScore) {
const firstParagraph = topic.content.split('\n\n')[0]?.replace(/[#*`]/g, '').trim() || '';
const summary = firstParagraph.length > 200
? firstParagraph.substring(0, 200) + '...'
: firstParagraph;
const domains = getDomainList(topic.frontmatter.domain);
return {
id: topic.id,
title: topic.frontmatter.title,
domain: domains[0] || 'unknown',
domains: domains.length > 1 ? domains : undefined,
difficulty: topic.frontmatter.difficulty,
relevance_score: 1 - relevanceScore,
summary,
tags: topic.frontmatter.tags,
prerequisites: topic.frontmatter.prerequisites || [],
estimated_time: topic.frontmatter.estimated_time
};
}
trackSearch(query, resultTopicIds) {
this.searchHistory.push({
query,
results: resultTopicIds,
timestamp: Date.now()
});
// Keep only recent history
if (this.searchHistory.length > this.maxSearchHistory) {
this.searchHistory.shift();
}
}
// Placeholder methods for recommendation generation (would be more sophisticated in practice)
generatePrerequisiteRecommendations(topicId, context) {
return []; // Implementation would analyze prerequisites and user knowledge
}
generateNextStepRecommendations(topicId, context) {
return []; // Implementation would suggest logical next topics
}
generateAlternativeRecommendations(topicId, context) {
return []; // Implementation would find alternative approaches
}
generateDeepDiveRecommendations(topicId, context) {
return []; // Implementation would suggest advanced/related topics
}
calculateRecommendationScore(rec, context) {
return rec.confidence * rec.estimated_value; // Simple scoring
}
async findPathTopics(goal, context) {
// Simplified implementation - would use more sophisticated path finding
return [];
}
calculateTotalTime(topics) {
// Simple time calculation
return topics.length > 0 ? `${topics.length * 20} minutes` : '0 minutes';
}
calculateDifficultyProgression(topics) {
return topics.map(t => t.difficulty);
}
generateSuccessCriteria(goal, topics) {
return [
`Understand core concepts of ${goal}`,
`Apply knowledge in practical scenarios`,
`Complete all required topics with comprehension`
];
}
}
//# sourceMappingURL=intelligent-search.js.map