UNPKG

mushcode-mcp-server

Version:

A specialized Model Context Protocol server for MUSHCODE development assistance. Provides AI-powered code generation, validation, optimization, and examples for MUD development.

365 lines 13.8 kB
/** * Help file processor for local MUSHCODE help files * Processes help files from the helps/ directory and adds them to the knowledge base */ import { readFile, readdir } from 'fs/promises'; import { join } from 'path'; import { logger } from '../utils/logger.js'; export class HelpFileProcessor { knowledgeBase; helpsDirectory; constructor(knowledgeBase, helpsDirectory = 'helps') { this.knowledgeBase = knowledgeBase; this.helpsDirectory = helpsDirectory; } /** * Process all help files in the helps directory */ async processHelpFiles() { logger.info('Starting help file processing', { operation: 'help_process_start', directory: this.helpsDirectory }); try { const files = await readdir(this.helpsDirectory); const helpFiles = files.filter(file => file.endsWith('.txt')); logger.info(`Found ${helpFiles.length} help files`, { operation: 'help_files_found', count: helpFiles.length, files: helpFiles }); for (const file of helpFiles) { try { await this.processHelpFile(file); } catch (error) { logger.error(`Failed to process help file ${file}`, error, { operation: 'help_file_error', file }); } } logger.info('Help file processing completed', { operation: 'help_process_complete' }); } catch (error) { logger.error('Failed to process help files', error, { operation: 'help_process_error' }); throw error; } } /** * Process a single help file */ async processHelpFile(filename) { const filePath = join(this.helpsDirectory, filename); const content = await readFile(filePath, 'utf-8'); logger.info(`Processing help file: ${filename}`, { operation: 'help_file_process', file: filename, size: content.length }); // Parse help entries from the file const entries = this.parseHelpFile(content, filename); logger.info(`Extracted ${entries.length} help entries from ${filename}`, { operation: 'help_entries_extracted', file: filename, count: entries.length }); // Convert help entries to knowledge base items for (const entry of entries) { this.addHelpEntryToKnowledgeBase(entry, filename); } } /** * Parse help file content into individual help entries */ parseHelpFile(content, _filename) { const entries = []; const lines = content.split('\n'); let currentEntry = null; let contentLines = []; for (const line of lines) { // Check if this is a new help entry (starts with &) if (line.startsWith('& ')) { // Save previous entry if it exists if (currentEntry) { currentEntry.content = contentLines.join('\n').trim(); entries.push(currentEntry); } // Start new entry const topicLine = line.substring(2).trim(); const topics = topicLine.split(/\s+/); const mainTopic = topics[0]; const aliases = topics.slice(1); if (mainTopic) { currentEntry = { topic: mainTopic, content: '', aliases: aliases }; } contentLines = []; } else if (currentEntry) { // Add content line to current entry contentLines.push(line); } } // Don't forget the last entry if (currentEntry) { currentEntry.content = contentLines.join('\n').trim(); entries.push(currentEntry); } return entries.filter(entry => entry.topic && entry.content); } /** * Add a help entry to the knowledge base */ addHelpEntryToKnowledgeBase(entry, filename) { const serverType = this.inferServerType(filename); // Create a code example from the help entry const example = { id: `help-${serverType}-${entry.topic.toLowerCase().replace(/[^a-z0-9]/g, '-')}`, title: `${entry.topic} - ${serverType} Help`, description: `Help documentation for ${entry.topic} command/function in ${serverType}`, code: this.extractCodeFromHelp(entry.content), explanation: entry.content, difficulty: this.inferDifficulty(entry.content), category: this.inferCategory(entry.topic, entry.content), tags: this.extractTags(entry.topic, entry.content, entry.aliases), serverCompatibility: [serverType], relatedConcepts: this.extractConcepts(entry.content), learningObjectives: [ `Understand the ${entry.topic} command/function`, `Learn ${serverType}-specific syntax and usage`, 'Apply help documentation in practical scenarios' ], source: { url: `file://${filename}`, author: `${serverType} Documentation` } }; this.knowledgeBase.addExample(example); // If the help entry contains command syntax, create a pattern too const pattern = this.createPatternFromHelp(entry, serverType); if (pattern) { this.knowledgeBase.addPattern(pattern); } } /** * Infer server type from filename */ inferServerType(filename) { const lower = filename.toLowerCase(); if (lower.includes('rhost')) return 'RhostMUSH'; if (lower.includes('penn')) return 'PennMUSH'; if (lower.includes('tiny')) return 'TinyMUSH'; if (lower.includes('mux')) return 'TinyMUX'; return 'Generic'; } /** * Extract code examples from help content */ extractCodeFromHelp(content) { // Look for lines that appear to be code examples const lines = content.split('\n'); const codeLines = []; for (const line of lines) { const trimmed = line.trim(); // Look for lines that start with common MUSHCODE patterns if (trimmed.match(/^[@&$+]/)) { codeLines.push(trimmed); } // Look for syntax examples else if (trimmed.includes('Syntax:') || trimmed.includes('Usage:')) { const nextLines = lines.slice(lines.indexOf(line) + 1, lines.indexOf(line) + 5); for (const nextLine of nextLines) { const nextTrimmed = nextLine.trim(); if (nextTrimmed && !nextTrimmed.startsWith('See also:')) { codeLines.push(nextTrimmed); } } } } return codeLines.length > 0 ? codeLines.join('\n') : content.substring(0, 200) + '...'; } /** * Create a pattern from help entry if it contains command syntax */ createPatternFromHelp(entry, serverType) { const content = entry.content.toLowerCase(); // Skip if this doesn't look like a command/function if (!content.includes('syntax') && !entry.topic.startsWith('@') && !entry.topic.startsWith('&')) { return null; } return { id: `help-pattern-${serverType.toLowerCase()}-${entry.topic.toLowerCase().replace(/[^a-z0-9]/g, '-')}`, name: `${entry.topic} Pattern`, description: `${entry.topic} command/function pattern from ${serverType} help`, category: this.inferPatternCategory(entry.topic, entry.content), difficulty: this.inferDifficulty(entry.content), serverCompatibility: [serverType], codeTemplate: this.extractTemplate(entry.content), parameters: this.extractParameters(entry.content), tags: this.extractTags(entry.topic, entry.content, entry.aliases), relatedPatterns: [], examples: [this.extractCodeFromHelp(entry.content)], securityLevel: this.inferSecurityLevel(entry.topic, entry.content), createdAt: new Date(), updatedAt: new Date() }; } /** * Infer difficulty from help content */ inferDifficulty(content) { const lower = content.toLowerCase(); if (lower.includes('wizard') || lower.includes('god') || lower.includes('admin')) { return 'advanced'; } if (lower.includes('builder') || lower.includes('complex') || lower.includes('advanced')) { return 'intermediate'; } return 'beginner'; } /** * Infer category from topic and content */ inferCategory(topic, content) { const lower = topic.toLowerCase() + ' ' + content.toLowerCase(); if (lower.includes('mail') || lower.includes('message')) return 'communication'; if (lower.includes('channel') || lower.includes('chat')) return 'communication'; if (lower.includes('create') || lower.includes('dig')) return 'creation'; if (lower.includes('lock') || lower.includes('permission')) return 'security'; if (lower.includes('function') || lower.includes('()')) return 'function'; if (topic.startsWith('@')) return 'command'; if (topic.startsWith('&')) return 'function'; if (topic.startsWith('+')) return 'softcode'; return 'utility'; } /** * Infer pattern category */ inferPatternCategory(topic, content) { if (topic.startsWith('@')) return 'command'; if (topic.startsWith('&')) return 'function'; if (content.toLowerCase().includes('trigger')) return 'trigger'; if (content.toLowerCase().includes('attribute')) return 'attribute'; return 'utility'; } /** * Extract tags from topic, content, and aliases */ extractTags(topic, content, aliases) { const tags = new Set(); // Add topic as tag tags.add(topic.toLowerCase().replace(/[^a-z0-9]/g, '')); // Add aliases as tags for (const alias of aliases) { tags.add(alias.toLowerCase().replace(/[^a-z0-9]/g, '')); } // Extract tags from content const lower = content.toLowerCase(); if (lower.includes('mail')) tags.add('mail'); if (lower.includes('channel')) tags.add('channel'); if (lower.includes('lock')) tags.add('lock'); if (lower.includes('permission')) tags.add('permission'); if (lower.includes('wizard')) tags.add('wizard'); if (lower.includes('builder')) tags.add('builder'); return Array.from(tags).slice(0, 10); } /** * Extract related concepts */ extractConcepts(content) { const concepts = new Set(); const lower = content.toLowerCase(); if (lower.includes('lock')) concepts.add('permissions'); if (lower.includes('mail')) concepts.add('communication'); if (lower.includes('channel')) concepts.add('communication'); if (lower.includes('create')) concepts.add('object-creation'); if (lower.includes('function')) concepts.add('functions'); if (lower.includes('command')) concepts.add('commands'); return Array.from(concepts); } /** * Extract template from help content */ extractTemplate(content) { const lines = content.split('\n'); for (const line of lines) { if (line.toLowerCase().includes('syntax:') || line.toLowerCase().includes('usage:')) { const nextLine = lines[lines.indexOf(line) + 1]; if (nextLine && nextLine.trim()) { return nextLine.trim(); } } } return content.split('\n')[0] || ''; } /** * Extract parameters from help content */ extractParameters(content) { const params = []; // Look for parameter descriptions in angle brackets const paramMatches = content.match(/<[^>]+>/g); if (paramMatches) { for (const match of paramMatches) { const paramName = match.slice(1, -1); params.push({ name: paramName, type: 'string', description: `Parameter: ${paramName}`, required: true }); } } return params; } /** * Infer security level */ inferSecurityLevel(_topic, content) { const lower = content.toLowerCase(); if (lower.includes('god') || lower.includes('shutdown')) return 'god'; if (lower.includes('wizard') || lower.includes('admin')) return 'wizard'; if (lower.includes('builder') || lower.includes('create') || lower.includes('dig')) return 'builder'; return 'public'; } } //# sourceMappingURL=help-processor.js.map