bc-code-intelligence-mcp
Version:
BC Code Intelligence MCP Server - Complete Specialist Bundle with AI-driven expert consultation, seamless handoffs, and context-preserving workflows
994 lines (991 loc) • 47.5 kB
JavaScript
/**
* BC Specialist Roleplay Engine
*
* Brings specialist personas to life through personality-driven responses,
* consistent character adoption, and context-aware knowledge integration.
*/
export class BCSpecialistRoleplayEngine {
layerService;
knowledgeService;
config;
responseTemplates = new Map();
knowledgeRetriever;
constructor(layerService, knowledgeService, config) {
this.layerService = layerService;
this.knowledgeService = knowledgeService;
this.config = {
personality_strength: 'moderate',
response_length: 'adaptive',
knowledge_integration: 'balanced',
suggest_handoffs: true,
suggest_collaborations: true,
learn_user_preferences: true,
adapt_communication_style: true,
...config
};
this.knowledgeRetriever = new BCKnowledgeRetriever(layerService, knowledgeService);
this.initializeResponseTemplates();
}
/**
* Generate a methodology-contextual response from a specialist
*/
async generateResponse(context) {
const { specialist, userMessage, session } = context;
// Check if methodology context is established
if (!session.methodology_context?.confirmed_by_user) {
return await this.establishMethodologyContext(specialist, userMessage, session);
}
// Apply knowledge within established methodology context
return await this.applyKnowledgeInMethodology(specialist, userMessage, session);
}
/**
* Establish methodology context before applying domain knowledge
*/
async establishMethodologyContext(specialist, userMessage, session) {
const personality = this.analyzePersonality(specialist);
// Check if user is asking a direct question or wants immediate help
const isDirectQuestion = this.isDirectQuestion(userMessage);
if (isDirectQuestion) {
// Provide immediate specialist response without methodology onboarding
return await this.provideDirectSpecialistResponse(specialist, userMessage, session);
}
// Suggest appropriate methodologies based on user request and specialist expertise
const suggestedMethodologies = await this.suggestMethodologies(userMessage, specialist);
// Build methodology onboarding response
const response = await this.buildMethodologyOnboardingResponse(specialist, personality, userMessage, suggestedMethodologies);
return {
content: response.content,
specialist_id: specialist.specialist_id,
personality_elements: response.personality_elements,
topics_referenced: [],
knowledge_applied: [],
suggested_handoffs: [],
context_updates: { methodology_suggested: true },
recommendations_added: response.recommendations || [],
response_type: 'methodology_onboarding',
confidence_level: 'high'
};
}
/**
* Check if user message is a direct question that should get immediate response
*/
isDirectQuestion(userMessage) {
const message = userMessage.toLowerCase().trim();
// Patterns that indicate broader learning/guidance requests (should get methodology)
const methodologyPatterns = [
/^(i want to learn|i'd like to learn|teach me|i'm new to|can you guide me|guide me through|walk me through)/i,
/^(i need help with my.*project|i need guidance|i need training)/i,
/learn about.*in general/i,
/get started with/i,
/introduction to/i
];
// If it matches methodology patterns, it's not a direct question
if (methodologyPatterns.some(pattern => pattern.test(message))) {
return false;
}
// Patterns that indicate direct questions (should get immediate response)
const directPatterns = [
/^(how|what|when|where|why|which|can|could|should|would|is|are|do|does|did)/i,
/\?/,
/tell me about.*specific/i,
/explain.*this/i,
/help me with.*\b(debug|fix|solve|resolve|optimize|review|analyze|check)\b/i,
/I need to (fix|debug|solve|resolve|optimize|review|analyze|check)/i,
/show me/i,
/review/i,
/analyze/i,
/check/i,
/look at/i,
/fix/i,
/debug/i
];
return directPatterns.some(pattern => pattern.test(message));
}
/**
* Provide immediate specialist response without methodology onboarding
*/
async provideDirectSpecialistResponse(specialist, userMessage, session) {
const personality = this.analyzePersonality(specialist);
// Search for relevant knowledge topics based on user's question and specialist's expertise
const relevantTopics = await this.knowledgeRetriever.findRelevantTopics(userMessage, specialist.expertise.primary.concat(specialist.expertise.secondary || []), 5);
// Create a minimal methodology context for the direct response
const directMethodologyContext = {
confirmed_by_user: true,
methodology_id: 'direct_consultation',
title: 'Direct Specialist Consultation',
current_phase: 'analysis',
next_steps: []
};
// Update session with direct context
session.methodology_context = directMethodologyContext;
// Generate direct response using existing machinery
const response = await this.buildMethodologyResponse(specialist, personality, userMessage, directMethodologyContext, relevantTopics);
// Apply session context updates
const contextUpdates = this.generateContextUpdates({ specialist, userMessage, session, conversationHistory: [] }, relevantTopics);
// Check for collaboration opportunities
const suggestedHandoffs = await this.suggestCollaborations({ specialist, userMessage, session, conversationHistory: [] }, relevantTopics);
return {
content: response.content,
specialist_id: specialist.specialist_id,
personality_elements: response.personality_elements,
topics_referenced: relevantTopics.map(t => t.id),
knowledge_applied: relevantTopics.map(topic => ({
topic_id: topic.id,
application_context: this.getApplicationContext(topic, userMessage)
})),
suggested_handoffs: suggestedHandoffs,
context_updates: contextUpdates,
recommendations_added: response.recommendations,
response_type: 'direct_specialist_response',
confidence_level: response.confidence_level
};
}
/**
* Apply knowledge within established methodology context
*/
async applyKnowledgeInMethodology(specialist, userMessage, session) {
const personality = this.analyzePersonality(specialist);
// Search for relevant knowledge topics based on user's question and specialist's expertise
const relevantTopics = await this.knowledgeRetriever.findRelevantTopics(userMessage, specialist.expertise.primary.concat(specialist.expertise.secondary || []), 5);
// Generate methodology-contextual response
const response = await this.buildMethodologyResponse(specialist, personality, userMessage, session.methodology_context, relevantTopics);
// Apply session context updates
const contextUpdates = this.generateContextUpdates({ specialist, userMessage, session, conversationHistory: [] }, relevantTopics);
// Check for collaboration opportunities
const suggestedHandoffs = await this.suggestCollaborations({ specialist, userMessage, session, conversationHistory: [] }, relevantTopics);
return {
content: response.content,
specialist_id: specialist.specialist_id,
personality_elements: response.personality_elements,
topics_referenced: relevantTopics.map(t => t.id),
knowledge_applied: relevantTopics.map(topic => ({
topic_id: topic.id,
application_context: this.getApplicationContext(topic, userMessage)
})),
suggested_handoffs: suggestedHandoffs,
context_updates: contextUpdates,
recommendations_added: response.recommendations,
response_type: response.response_type,
confidence_level: response.confidence_level
};
}
/**
* Suggest appropriate methodologies based on user request and specialist expertise
*/
async suggestMethodologies(userMessage, specialist) {
// Analyze user message for methodology keywords and patterns
const methodologyKeywords = this.extractMethodologyKeywords(userMessage);
const specialistMethodologies = await this.getSpecialistMethodologies(specialist);
// Return suggested methodologies with confidence scores
return specialistMethodologies.filter(methodology => methodologyKeywords.some(keyword => methodology.title.toLowerCase().includes(keyword) ||
methodology.description.toLowerCase().includes(keyword))).slice(0, 3); // Top 3 suggestions
}
/**
* Build methodology onboarding response
*/
async buildMethodologyOnboardingResponse(specialist, personality, userMessage, suggestedMethodologies) {
const greeting = specialist.persona.greeting;
if (suggestedMethodologies.length === 0) {
return {
content: `${greeting} I'd love to help! Before we dive in, could you tell me more about what you're trying to accomplish? This will help me suggest the best approach for our work together.`,
personality_elements: {},
recommendations: [],
response_type: 'clarification_needed',
confidence_level: 'medium'
};
}
const primaryMethodology = suggestedMethodologies[0];
const content = `${greeting} I can see you're interested in ${this.extractUserIntent(userMessage)}.
**Suggested Approach: "${primaryMethodology.title}"**
${primaryMethodology.description}
This methodology will help us work through this systematically. Does this approach sound right for your goals? Once we confirm this framework, I can provide targeted guidance with relevant knowledge applied in context.
What's your current experience level so I can tailor the approach accordingly?`;
return {
content,
personality_elements: { methodology_suggested: primaryMethodology.methodology_id },
recommendations: [`Follow ${primaryMethodology.title} methodology`],
response_type: 'methodology_suggestion',
confidence_level: 'high'
};
}
/**
* Build response within established methodology context
*/
async buildMethodologyResponse(specialist, personality, userMessage, methodologyContext, relevantTopics) {
// Create a proper session context for the existing method
const sessionWithContext = {
sessionId: 'temp',
specialistId: specialist.specialist_id,
userId: 'temp',
startTime: new Date(),
lastActivity: new Date(),
messageCount: 1,
status: 'active',
messages: [],
context: {
methodology_context: methodologyContext,
solutions: [],
recommendations: [],
nextSteps: [],
userPreferences: {}
}
};
// Use existing buildPersonalityResponse with methodology context
return await this.buildPersonalityResponse(specialist, personality, { specialist, userMessage, session: sessionWithContext, conversationHistory: [] }, relevantTopics);
}
/**
* Extract methodology keywords from user message
*/
extractMethodologyKeywords(userMessage) {
const keywords = [];
const message = userMessage.toLowerCase();
const patterns = {
'fundamentals': ['fundamental', 'basics', 'introduction', 'getting started', 'onboarding'],
'development': ['development', 'coding', 'programming', 'building'],
'architecture': ['architecture', 'design', 'structure', 'pattern'],
'performance': ['performance', 'optimization', 'speed', 'efficiency'],
'testing': ['testing', 'quality', 'validation', 'debugging'],
'workflow': ['workflow', 'methodology', 'process', 'approach']
};
for (const [category, terms] of Object.entries(patterns)) {
if (terms.some(term => message.includes(term))) {
keywords.push(category);
}
}
return keywords;
}
/**
* Get methodologies relevant to specialist
*/
async getSpecialistMethodologies(specialist) {
// Map specialists to their preferred methodologies
const specialistMethodologies = {
'maya-mentor': [
{ methodology_id: 'developer-introduction', title: 'BC Development Fundamentals', description: 'Introduction to Business Central development fundamentals and environment setup' },
{ methodology_id: 'skill-building', title: 'Skill Development Methodology', description: 'Structured approach to building BC development skills' }
],
'sam-coder': [
{ methodology_id: 'implementation', title: 'Implementation Methodology', description: 'Efficient code implementation and development practices' }
],
'alex-architect': [
{ methodology_id: 'architecture-design', title: 'Architecture Design Methodology', description: 'Systematic approach to BC solution architecture' }
],
'dean-debug': [
{ methodology_id: 'troubleshooting', title: 'Diagnostic Methodology', description: 'Systematic problem diagnosis and performance analysis' }
]
};
return specialistMethodologies[specialist.specialist_id] || [];
}
/**
* Extract user intent from message
*/
extractUserIntent(userMessage) {
// Simple intent extraction - could be made more sophisticated
const message = userMessage.toLowerCase();
if (message.includes('fundamental') || message.includes('getting started')) {
return 'learning fundamentals';
}
if (message.includes('workflow') || message.includes('methodology')) {
return 'following a structured approach';
}
return 'getting help with BC development';
}
/**
* Analyze user message to suggest appropriate specialist
*/
async suggestSpecialist(userMessage, currentContext) {
const specialists = await this.layerService.getAllSpecialists();
const suggestions = [];
for (const specialist of specialists) {
const confidence = await this.calculateSpecialistConfidence(userMessage, specialist, currentContext);
if (confidence > 0.15) { // Lowered threshold for token-based matching (Issue #17)
suggestions.push({
specialist_id: specialist.specialist_id,
confidence,
reasoning: this.generateSuggestionReasoning(userMessage, specialist, confidence)
});
}
}
return suggestions.sort((a, b) => b.confidence - a.confidence).slice(0, 3);
}
/**
* Generate a specialist greeting for session start
*/
async generateGreeting(specialist, context) {
const personality = this.analyzePersonality(specialist);
const greeting = this.buildGreeting(specialist, personality, context);
return {
content: greeting,
specialist_id: specialist.specialist_id,
personality_elements: {
greeting_used: true,
characteristic_phrases: [specialist.persona.greeting],
expertise_demonstrated: specialist.expertise.primary.slice(0, 2),
communication_style_applied: specialist.persona.communication_style
},
topics_referenced: [],
knowledge_applied: [],
response_type: 'greeting',
confidence_level: 'high'
};
}
/**
* Generate handoff message when transferring between specialists
*/
async generateHandoff(fromSpecialist, toSpecialist, context) {
const fromPersonality = this.analyzePersonality(fromSpecialist);
const toPersonality = this.analyzePersonality(toSpecialist);
const farewell = this.buildHandoffFarewell(fromSpecialist, toSpecialist, context);
const introduction = this.buildHandoffIntroduction(toSpecialist, fromSpecialist, context);
return {
farewell: {
content: farewell,
specialist_id: fromSpecialist.specialist_id,
personality_elements: {
greeting_used: false,
characteristic_phrases: [],
expertise_demonstrated: [],
communication_style_applied: fromSpecialist.persona.communication_style
},
topics_referenced: [],
knowledge_applied: [],
response_type: 'handoff',
confidence_level: 'high'
},
introduction: {
content: introduction,
specialist_id: toSpecialist.specialist_id,
personality_elements: {
greeting_used: true,
characteristic_phrases: [toSpecialist.persona.greeting],
expertise_demonstrated: toSpecialist.expertise.primary.slice(0, 2),
communication_style_applied: toSpecialist.persona.communication_style
},
topics_referenced: [],
knowledge_applied: [],
response_type: 'handoff',
confidence_level: 'high'
}
};
}
/**
* Update configuration
*/
updateConfig(config) {
this.config = { ...this.config, ...config };
}
/**
* Get personality analysis for a specialist
*/
analyzePersonality(specialist) {
return {
communication_style: specialist.persona.communication_style,
expertise_focus: specialist.expertise.primary,
problem_approach: this.extractProblemApproach(specialist),
collaboration_style: this.extractCollaborationStyle(specialist),
characteristic_phrases: [
specialist.persona.greeting,
...this.extractCharacteristicPhrases(specialist)
]
};
}
/**
* Build personality-driven response
*/
async buildPersonalityResponse(specialist, personality, context, relevantTopics) {
const { userMessage, session } = context;
// Start with specialist greeting if it's a new conversation
let response = session.messageCount <= 1 ? `${specialist.persona.greeting} ` : '';
// Apply personality-driven response patterns
response += await this.generatePersonalityContent(specialist, personality, userMessage, relevantTopics, context);
// Add recommendations based on knowledge
const recommendations = this.generateRecommendations(relevantTopics, userMessage);
// Determine response type and confidence
const responseType = this.determineResponseType(userMessage, relevantTopics);
const confidenceLevel = this.calculateConfidenceLevel(specialist, userMessage, relevantTopics);
return {
content: response,
personality_elements: {
greeting_used: session.messageCount <= 1,
characteristic_phrases: personality.characteristic_phrases.slice(0, 2),
expertise_demonstrated: personality.expertise_focus.slice(0, 3),
communication_style_applied: personality.communication_style
},
recommendations,
response_type: responseType,
confidence_level: confidenceLevel
};
}
/**
* Generate personality-driven content
*/
async generatePersonalityContent(specialist, personality, userMessage, relevantTopics, context) {
// This is where we'd integrate with the actual BC knowledge to generate responses
// For now, let's create template-based responses that demonstrate personality
const templates = this.getResponseTemplates(specialist.specialist_id);
const selectedTemplate = this.selectBestTemplate(templates, userMessage, relevantTopics);
if (selectedTemplate) {
return this.fillTemplate(selectedTemplate, specialist, userMessage, relevantTopics, context);
}
// Fallback to basic personality-driven response
return this.generateBasicPersonalityResponse(specialist, userMessage, relevantTopics, context);
}
/**
* Generate agent roleplay instructions (NOT direct user response)
*/
generateBasicPersonalityResponse(specialist, userMessage, relevantTopics, context) {
// Generate AGENT INSTRUCTIONS using the FULL specialist instruction content
let instructions = `You are ${specialist.title} (${specialist.specialist_id}). Below are your complete instructions:\n\n`;
// **CRITICAL FIX**: Include the full specialist instruction content
instructions += `# SPECIALIST INSTRUCTIONS:\n${specialist.content}\n\n`;
instructions += `---\n\n# CURRENT REQUEST CONTEXT:\n`;
instructions += `**User Message**: "${userMessage}"\n\n`;
// **NO PRE-SELECTED KNOWLEDGE** - provide YAML-based hints instead
instructions += `**Knowledge Search Hints** (based on your YAML configuration):\n`;
instructions += `- Your Primary Expertise: ${specialist.expertise.primary.join(', ')}\n`;
instructions += `- Your Domains: ${specialist.domains.join(', ')}\n`;
instructions += `- Suggested find_bc_knowledge searches: "${specialist.expertise.primary.join('", "')}", "${specialist.domains.join('", "')}"\n\n`;
// Add introduction instructions if this is a new session or handoff
if (context.requiresIntroduction) {
instructions += `**Session Info**: This is a new session - introduce yourself using your greeting and explain your expertise.\n\n`;
}
instructions += `**CRITICAL REMINDER**: NO knowledge has been pre-selected for you. Follow your "Implementation Requirements" section exactly - you MUST use find_bc_knowledge to search for relevant information yourself.\n\n`;
instructions += `Now respond as ${specialist.title} following your complete instruction set above.`;
return instructions;
}
/**
* Generate style-appropriate opening
*/
getStyleApproach(specialist) {
const communication = specialist.persona.communication_style.toLowerCase();
if (communication.includes('technical')) {
return "Let's dive into the technical details.";
}
else if (communication.includes('business')) {
return "Let's think about this from a business perspective.";
}
else if (communication.includes('practical')) {
return "Here's a practical approach to your question.";
}
else if (communication.includes('teaching')) {
return "Let me walk you through this step by step.";
}
return "I'd be happy to help with this!";
}
/**
* Generate knowledge-based guidance
*/
generateKnowledgeBasedGuidance(specialist, topic, userMessage) {
// Create specialist-specific framing of the knowledge
const specialistLens = this.getSpecialistPerspective(specialist, topic);
// Extract key points from the topic content
const keyPoints = this.extractKeyGuidancePoints(topic);
return `${specialistLens} Looking at **${topic.title}**, ${keyPoints}. This ${topic.frontmatter.difficulty === 'advanced' ? 'advanced' : topic.frontmatter.difficulty} pattern applies directly to your situation.`;
}
/**
* Get specialist-specific perspective on a topic
*/
getSpecialistPerspective(specialist, topic) {
const specialistId = specialist.specialist_id;
if (specialistId.includes('performance') || specialistId.includes('debug')) {
return `From a performance optimization standpoint,`;
}
else if (specialistId.includes('security')) {
return `From a security perspective,`;
}
else if (specialistId.includes('architect')) {
return `From an architectural design perspective,`;
}
else if (specialistId.includes('test')) {
return `From a testing and quality standpoint,`;
}
else if (specialistId.includes('mentor') || specialistId.includes('docs')) {
return `Let me explain this step by step:`;
}
return `Based on my expertise in ${specialist.expertise.primary[0]},`;
}
/**
* Extract key guidance points from topic content
*/
extractKeyGuidancePoints(topic) {
const content = topic.content;
// Look for key implementation patterns
if (content.includes('## Implementation') || content.includes('## How to')) {
return `the key implementation approach focuses on ${this.extractImplementationFocus(content)}`;
}
// Look for best practices
if (content.includes('## Best Practices') || content.includes('### Best Practices')) {
return `the best practices emphasize ${this.extractBestPractices(content)}`;
}
// Look for common pitfalls
if (content.includes('## Common Pitfalls') || content.includes('### Pitfalls')) {
return `it's important to avoid ${this.extractPitfalls(content)}`;
}
// Default to title-based guidance
return `this pattern provides essential guidance for ${topic.title.toLowerCase()}`;
}
/**
* Extract implementation focus from content
*/
extractImplementationFocus(content) {
// Find implementation section and extract first few points
const lines = content.split('\n');
const implIndex = lines.findIndex(line => line.includes('## Implementation') || line.includes('## How to'));
if (implIndex >= 0 && implIndex < lines.length - 1) {
const nextFewLines = lines.slice(implIndex + 1, implIndex + 4)
.filter(line => line.trim() && !line.startsWith('#'))
.join(' ');
return this.summarizeIntoPhrase(nextFewLines);
}
return 'proper implementation patterns';
}
/**
* Extract best practices from content
*/
extractBestPractices(content) {
const lines = content.split('\n');
const practicesIndex = lines.findIndex(line => line.includes('Best Practices'));
if (practicesIndex >= 0 && practicesIndex < lines.length - 1) {
const nextFewLines = lines.slice(practicesIndex + 1, practicesIndex + 3)
.filter(line => line.trim() && !line.startsWith('#'))
.join(' ');
return this.summarizeIntoPhrase(nextFewLines);
}
return 'following established patterns and maintaining code quality';
}
/**
* Extract pitfalls from content
*/
extractPitfalls(content) {
const lines = content.split('\n');
const pitfallsIndex = lines.findIndex(line => line.includes('Pitfalls') || line.includes('Common Issues'));
if (pitfallsIndex >= 0 && pitfallsIndex < lines.length - 1) {
const nextFewLines = lines.slice(pitfallsIndex + 1, pitfallsIndex + 3)
.filter(line => line.trim() && !line.startsWith('#'))
.join(' ');
return this.summarizeIntoPhrase(nextFewLines);
}
return 'common implementation mistakes';
}
/**
* Summarize content into a concise phrase
*/
summarizeIntoPhrase(text) {
// Clean up the text and create a concise summary
const cleaned = text
.replace(/[*#-]/g, '')
.replace(/\s+/g, ' ')
.trim();
// Take first sentence or first 60 characters
const firstSentence = cleaned.split('.')[0];
const truncated = firstSentence.length > 60
? firstSentence.substring(0, 60) + '...'
: firstSentence;
return truncated.toLowerCase();
}
/**
* Generate general guidance when no specific topics match
*/
generateGeneralGuidance(specialist, userMessage) {
return `I'd suggest we start by understanding the specific requirements and then I can point you toward the right specialist or resources that would be most helpful.`;
}
// Additional helper methods...
initializeResponseTemplates() {
// Initialize with basic templates - these could be loaded from configuration
this.responseTemplates.set('performance', [
{
trigger_keywords: ['slow', 'performance', 'optimize', 'speed'],
specialist_types: ['dean-debug'],
template_pattern: "🔧 Dean here! Performance issues are my specialty. {problem_analysis} {solution_approach} {next_steps}",
personality_emphasis: ['technical', 'systematic', 'thorough'],
knowledge_domains: ['performance', 'optimization']
}
]);
}
getResponseTemplates(specialistId) {
// Return templates relevant to this specialist
return Array.from(this.responseTemplates.values()).flat()
.filter(template => template.specialist_types.includes(specialistId));
}
selectBestTemplate(templates, userMessage, relevantTopics) {
// Simple keyword matching for now
const messageLower = userMessage.toLowerCase();
for (const template of templates) {
if (template.trigger_keywords.some(keyword => messageLower.includes(keyword))) {
return template;
}
}
return null;
}
fillTemplate(template, specialist, userMessage, relevantTopics, context) {
// Basic template filling - would be more sophisticated in practice
let content = template.template_pattern;
content = content.replace('{problem_analysis}', 'Let me analyze this issue systematically.');
content = content.replace('{solution_approach}', 'Here\'s how I\'d approach solving this:');
content = content.replace('{next_steps}', 'Next steps would be to examine the specific implementation details.');
return content;
}
extractProblemApproach(specialist) {
// Extract problem-solving approach from specialist definition
return specialist.persona.communication_style;
}
extractCollaborationStyle(specialist) {
// Extract collaboration preferences
return specialist.collaboration?.natural_handoffs?.length > 0 ? 'collaborative' : 'independent';
}
extractCharacteristicPhrases(specialist) {
// Extract characteristic phrases from the content
return []; // Would parse from the specialist's content
}
buildGreeting(specialist, personality, context) {
let greeting = specialist.persona.greeting;
if (context?.problem) {
greeting += ` I understand you're working on ${context.problem}. `;
}
greeting += ` I'm here to help with ${specialist.expertise.primary.join(', ')}. What specific challenge are you facing?`;
return greeting;
}
buildHandoffFarewell(fromSpecialist, toSpecialist, context) {
return `I think ${toSpecialist.persona.greeting.replace('!', '')} would be perfect for this! They're our expert in ${toSpecialist.expertise.primary.join(' and ')}. Let me hand you over to them.`;
}
buildHandoffIntroduction(toSpecialist, fromSpecialist, context) {
return `${toSpecialist.persona.greeting} ${fromSpecialist.specialist_id.split('-')[0]} filled me in on what you're working on. I'm excited to help with ${toSpecialist.expertise.primary[0]}! Let's dive in.`;
}
async calculateSpecialistConfidence(userMessage, specialist, context) {
const messageLower = userMessage.toLowerCase();
// Tokenize the user message into individual keywords (filter out short words)
const messageTokens = messageLower
.split(/[\s,]+/)
.filter(token => token.length > 3)
.map(token => token.replace(/[^a-z0-9]/g, ''));
let confidence = 0;
const matchedTokens = new Set(); // Track matched tokens to avoid double-counting
// Check against primary expertise with token-based matching
for (const expertise of specialist.expertise.primary) {
const expertiseTokens = expertise
.toLowerCase()
.replace(/[-_]/g, ' ')
.split(/\s+/)
.filter(t => t.length > 3);
// Award points for any token match (bidirectional partial matching)
for (const msgToken of messageTokens) {
if (matchedTokens.has(msgToken))
continue; // Skip already matched tokens
for (const expToken of expertiseTokens) {
if (expToken.includes(msgToken) || msgToken.includes(expToken)) {
confidence += 0.15; // Lower increment for more granular scoring
matchedTokens.add(msgToken);
break; // Found a match for this message token
}
}
}
}
// Check against secondary expertise with token-based matching
for (const expertise of specialist.expertise.secondary) {
const expertiseTokens = expertise
.toLowerCase()
.replace(/[-_]/g, ' ')
.split(/\s+/)
.filter(t => t.length > 3);
for (const msgToken of messageTokens) {
if (matchedTokens.has(msgToken))
continue;
for (const expToken of expertiseTokens) {
if (expToken.includes(msgToken) || msgToken.includes(expToken)) {
confidence += 0.1;
matchedTokens.add(msgToken);
break;
}
}
}
}
// Check against domains with token-based matching
for (const domain of specialist.domains) {
const domainTokens = domain
.toLowerCase()
.replace(/[-_]/g, ' ')
.split(/\s+/)
.filter(t => t.length > 3);
for (const msgToken of messageTokens) {
if (matchedTokens.has(msgToken))
continue;
for (const domToken of domainTokens) {
if (domToken.includes(msgToken) || msgToken.includes(domToken)) {
confidence += 0.05;
matchedTokens.add(msgToken);
break;
}
}
}
}
return Math.min(confidence, 1.0);
}
generateSuggestionReasoning(userMessage, specialist, confidence) {
return `${specialist.specialist_id} specializes in ${specialist.expertise.primary.join(', ')} which aligns with your question about ${userMessage.substring(0, 50)}...`;
}
generateContextUpdates(context, relevantTopics) {
return {
// Add discovered topics to context
// Update problem understanding
// Track user preferences
};
}
async suggestCollaborations(context, relevantTopics) {
const suggestions = [];
// Check if other specialists might be helpful
const { specialist } = context;
if (specialist.collaboration?.natural_handoffs) {
for (const handoffId of specialist.collaboration.natural_handoffs) {
suggestions.push({
specialist_id: handoffId,
reason: `Natural collaboration partner for ${specialist.expertise.primary[0]}`
});
}
}
return suggestions.slice(0, 2); // Limit suggestions
}
getApplicationContext(topic, userMessage) {
const messageWords = userMessage.toLowerCase().split(' ');
const topicTags = topic.frontmatter.tags || [];
// Identify context based on user message and topic
let context = `Applied ${topic.title} to address`;
if (messageWords.some(word => ['performance', 'slow', 'optimize', 'speed'].includes(word))) {
context += ` performance concerns in ${this.extractEntityFromMessage(userMessage)}`;
}
else if (messageWords.some(word => ['security', 'permission', 'access'].includes(word))) {
context += ` security requirements for ${this.extractEntityFromMessage(userMessage)}`;
}
else if (messageWords.some(word => ['integration', 'api', 'connect'].includes(word))) {
context += ` integration challenges with ${this.extractEntityFromMessage(userMessage)}`;
}
else if (messageWords.some(word => ['test', 'testing', 'validation'].includes(word))) {
context += ` testing strategy for ${this.extractEntityFromMessage(userMessage)}`;
}
else {
context += ` the development challenge in ${userMessage.substring(0, 50)}...`;
}
// Add BC version context if available
if (topic.frontmatter.bc_versions) {
context += ` (BC ${topic.frontmatter.bc_versions} compatible)`;
}
return context;
}
/**
* Extract business entity or object from user message
*/
extractEntityFromMessage(message) {
const commonEntities = [
'table', 'page', 'report', 'codeunit', 'api', 'service',
'customer', 'vendor', 'item', 'purchase', 'sales', 'inventory'
];
const messageLower = message.toLowerCase();
for (const entity of commonEntities) {
if (messageLower.includes(entity)) {
return entity;
}
}
return 'your BC implementation';
}
generateRecommendations(topics, userMessage) {
const recommendations = [];
for (const topic of topics.slice(0, 3)) {
// Create actionable recommendations based on the topic
if (topic.frontmatter.bc_versions) {
recommendations.push(`Apply **${topic.title}** patterns (compatible with ${topic.frontmatter.bc_versions})`);
}
else {
recommendations.push(`Consider implementing **${topic.title}** best practices`);
}
// Add specific action based on topic type
if (topic.frontmatter.tags?.includes('performance')) {
recommendations.push(`Measure performance impact of ${topic.title.toLowerCase()} implementation`);
}
else if (topic.frontmatter.tags?.includes('security')) {
recommendations.push(`Review security implications when applying ${topic.title.toLowerCase()}`);
}
else if (topic.frontmatter.tags?.includes('testing')) {
recommendations.push(`Create test cases to validate ${topic.title.toLowerCase()} implementation`);
}
}
return recommendations.slice(0, 4); // Limit to 4 recommendations
}
determineResponseType(userMessage, topics) {
const messageLower = userMessage.toLowerCase();
if (messageLower.includes('how') || messageLower.includes('help')) {
return 'guidance';
}
else if (messageLower.includes('fix') || messageLower.includes('solve')) {
return 'solution';
}
else if (messageLower.includes('?')) {
return 'question';
}
return 'guidance';
}
calculateConfidenceLevel(specialist, userMessage, topics) {
if (topics.length >= 2)
return 'high';
if (topics.length === 1)
return 'medium';
return 'low';
}
}
/**
* Knowledge retriever implementation for BC topics
*/
class BCKnowledgeRetriever {
layerService;
knowledgeService;
constructor(layerService, knowledgeService) {
this.layerService = layerService;
this.knowledgeService = knowledgeService;
}
async findRelevantTopics(userMessage, specialistExpertise, limit = 5) {
try {
// Convert specialist expertise to broader search terms that match topic content
const broadSearchTerms = specialistExpertise.map(exp => {
// Map specific expertise terms to broader, more searchable terms
const mappings = {
'performance-analysis': 'performance',
'error-diagnosis': 'error',
'system-monitoring': 'monitoring',
'optimization-implementation': 'optimization',
'query-optimization': 'query performance',
'memory-management': 'memory',
'integration-performance': 'integration',
'user-experience-optimization': 'user experience'
};
return mappings[exp] || exp.replace('-', ' ');
});
// Search for topics using the user's message in code_context field
const searchParams = {
code_context: `${userMessage} ${broadSearchTerms.join(' ')}`, // Combine user message with expertise terms
limit,
bc_version: 'BC22' // Default - could be made configurable
};
// Use the existing knowledge service to find relevant topics
const searchResults = await this.knowledgeService.searchTopics(searchParams);
// Get full topic details for each search result
if (searchResults && Array.isArray(searchResults)) {
const topics = [];
for (const result of searchResults.slice(0, limit)) {
const topic = await this.knowledgeService.getTopic(result.id);
if (topic) {
topics.push(topic);
}
}
return topics;
}
return [];
}
catch (error) {
console.error('Error finding relevant topics:', error);
return [];
}
}
/**
* Find relevant topics within methodology context
*/
async findRelevantTopicsInMethodology(userMessage, methodologyContext, specialistExpertise, limit = 5) {
try {
// Focus search on methodology-specific topics and current phase context
const methodologyTerms = [
methodologyContext.methodology_id,
methodologyContext.current_phase,
...specialistExpertise
];
const searchParams = {
code_context: `${userMessage} ${methodologyTerms.join(' ')}`,
limit,
bc_version: 'BC22'
};
const searchResults = await this.knowledgeService.searchTopics(searchParams);
if (searchResults && Array.isArray(searchResults)) {
const topics = [];
for (const result of searchResults.slice(0, limit)) {
const topic = await this.knowledgeService.getTopic(result.id);
if (topic) {
topics.push(topic);
}
}
return topics;
}
return [];
}
catch (error) {
console.error('Error finding methodology-relevant topics:', error);
return [];
}
}
async getRelatedTopics(topicId, limit = 3) {
try {
// Get the main topic first
const mainTopic = await this.knowledgeService.getTopic(topicId);
if (!mainTopic) {
return [];
}
// Use domain-based search to find related topics
const searchParams = {
query: mainTopic.frontmatter.domain,
search_type: 'fuzzy',
limit: limit + 1, // Get one extra to exclude the main topic
bc_version: 'BC22',
domains: [mainTopic.frontmatter.domain]
};
const searchResults = await this.knowledgeService.searchTopics(searchParams);
const relatedTopics = [];
for (const result of searchResults) {
if (result.id !== topicId && relatedTopics.length < limit) {
const topic = await this.knowledgeService.getTopic(result.id);
if (topic) {
relatedTopics.push(topic);
}
}
}
return relatedTopics;
}
catch (error) {
console.error('Error getting related topics:', error);
return [];
}
}
async searchSolutions(problemDescription, domains, limit = 5) {
try {
// Search for solution-oriented topics in the specified domains
const searchParams = {
query: `${problemDescription} solution implementation fix pattern`,
search_type: 'hybrid',
limit: limit * 2, // Get more results to filter
bc_version: 'BC22',
domains
};
const searchResults = await this.knowledgeService.searchTopics(searchParams);
const solutionTopics = [];
for (const result of searchResults) {
if (solutionTopics.length >= limit)
break;
const topic = await this.knowledgeService.getTopic(result.id);
if (topic) {
// Filter for topics that are more solution-oriented
const content = topic.content.toLowerCase();
const title = topic.title.toLowerCase();
if (content.includes('solution') ||
content.includes('implementation') ||
content.includes('fix') ||
content.includes('pattern') ||
title.includes('pattern') ||
title.includes('optimization') ||
title.includes('best practice')) {
solutionTopics.push(topic);
}
}
}
return solutionTopics;
}
catch (error) {
console.error('Error searching solutions:', error);
return [];
}
}
}
//# sourceMappingURL=roleplay-engine.js.map