UNPKG

codecrucible-synth

Version:

Production-Ready AI Development Platform with Multi-Voice Synthesis, Smithery MCP Integration, Enterprise Security, and Zero-Timeout Reliability

673 lines (600 loc) 21.5 kB
import express from 'express'; import { createServer } from 'http'; import { Server as SocketIOServer } from 'socket.io'; import { CLIContext } from '../core/cli.js'; import { logger } from '../core/logger.js'; import chalk from 'chalk'; import { readFile, writeFile, stat } from 'fs/promises'; import { join, extname } from 'path'; export interface ServerOptions { port: number; host: string; cors?: boolean; auth?: { enabled: boolean; token?: string; }; } export interface ServerModeInterface { startServerMode(context: CLIContext, options: ServerOptions): Promise<void>; } /** * Server Mode for IDE Integration * * Provides HTTP and WebSocket APIs for IDE extensions and external tools * Compatible with VS Code, JetBrains IDEs, and other development environments */ export class ServerMode implements ServerModeInterface { async startServerMode(context: CLIContext, options: ServerOptions): Promise<void> { // Validate context initialization if (!context) { throw new Error('CLI context is required for server mode'); } if (!context.modelClient) { throw new Error('Model client not initialized'); } if (!context.voiceSystem) { throw new Error('Voice system not initialized'); } if (!context.config) { throw new Error('Configuration not loaded'); } console.log(chalk.blue('🚀 Starting CodeCrucible Server Mode...')); // Initialize context components if needed try { await context.modelClient.initialize(); logger.info('Model client initialized for server mode'); } catch (error) { logger.warn('Model client initialization warning:', error); } const app = express(); const server = createServer(app); const io = new SocketIOServer(server, { cors: options.cors ? { origin: '*' } : undefined, }); // Middleware app.use(express.json({ limit: '50mb' })); app.use(express.urlencoded({ extended: true })); if (options.cors) { app.use((req, res, next) => { res.header('Access-Control-Allow-Origin', '*'); res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,OPTIONS'); res.header( 'Access-Control-Allow-Headers', 'Content-Type, Authorization, Content-Length, X-Requested-With' ); if (req.method === 'OPTIONS') { res.sendStatus(200); } else { next(); } }); } // Authentication middleware if (options.auth?.enabled) { app.use((req, res, next) => { const token = req.headers.authorization?.replace('Bearer ', ''); if (token !== options.auth!.token) { return res.status(401).json({ error: 'Unauthorized' }); } return next(); }); } // Health check endpoint app.get('/health', (req, res) => { res.json({ status: 'healthy', version: '2.0.0', timestamp: Date.now(), model: { endpoint: context.config.model?.endpoint || 'http://localhost:11434', name: context.config.model?.name || 'llama2', }, }); }); // Model status endpoint app.get('/api/model/status', async (req, res) => { try { const healthCheck = await context.modelClient.healthCheck(); const isAvailable = Object.values(healthCheck).some(status => status); res.json({ available: isAvailable, endpoint: context.config.model?.endpoint || 'http://localhost:11434', model: context.config.model?.name || 'llama2', }); } catch (error) { res.status(500).json({ error: 'Model status check failed', available: false, }); } }); // Voice information endpoint app.get('/api/voices', (req, res) => { res.json({ available: context.config.voices?.available || [ 'explorer', 'maintainer', 'analyzer', 'developer', 'implementor', 'security', 'architect', 'designer', 'optimizer', ], default: context.config.voices?.default || ['explorer', 'maintainer'], descriptions: { explorer: 'Innovation and creative solutions', maintainer: 'Stability and long-term maintenance', analyzer: 'Performance and architectural insights', developer: 'Developer experience and usability', implementor: 'Practical implementation and delivery', security: 'Secure coding practices', architect: 'Scalable architecture and design', designer: 'UI/UX and interface design', optimizer: 'Performance optimization', }, }); }); // Code generation endpoint app.post('/api/generate', async (req, res): Promise<any> => { try { const { prompt, voices = context.config.voices?.default || ['explorer', 'maintainer'], mode = 'competitive', context: userContext = [], language: _language, file_path: _file_path, } = req.body; if (!prompt) { return res.status(400).json({ error: 'Prompt is required' }); } logger.info('Code generation request', { prompt: prompt.substring(0, 100), voices, mode, contextFiles: userContext.length, }); // Generate responses from selected voices const synthesis = await context.voiceSystem.synthesize( prompt, voices, mode as 'competitive' | 'collaborative' | 'consensus', context.modelClient ); res.json({ success: true, result: { code: (synthesis as Record<string, unknown>).combinedCode || synthesis.content, reasoning: (synthesis as Record<string, unknown>).reasoning || 'No reasoning provided', confidence: (synthesis as Record<string, unknown>).confidence || 0.8, quality_score: synthesis.qualityScore, voices_used: synthesis.voicesUsed, }, individual_responses: (synthesis.responses || []).map((r: any) => ({ voice: r.voice, content: r.content, confidence: r.confidence, tokens_used: r.tokens_used || 0, })), metadata: { timestamp: Date.now(), model: context.config.model?.name || 'llama2', mode, voices, }, }); } catch (error) { logger.error('Code generation failed:', error); res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Generation failed', }); } }); // Code analysis endpoint app.post('/api/analyze', async (req, res): Promise<any> => { try { const { code, language = 'text', file_path } = req.body; if (!code) { return res.status(400).json({ error: 'Code is required' }); } const analysis = await context.modelClient.processRequest({ prompt: `Analyze this ${language} code for quality, issues, and improvements:\n\n${code}`, temperature: 0.7, }); // Calculate quality score based on analysis content const qualityScore = calculateQualityScore(analysis.content); const recommendations = extractRecommendations(analysis.content); res.json({ success: true, analysis: { content: analysis.content, quality_score: qualityScore, recommendations: recommendations, timestamp: new Date().toISOString(), }, metadata: { file_path, language, code_length: code.length, model: context.config.model?.name || 'llama2', }, }); } catch (error) { logger.error('Code analysis failed:', error); res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Analysis failed', }); } }); // File operations endpoint app.post('/api/file/:operation', async (req, res): Promise<any> => { try { const { operation } = req.params; const { file_path, content, prompt } = req.body; switch (operation) { case 'read': if (!file_path) { return res.status(400).json({ error: 'file_path is required' }); } try { const fileContent = await readFile(file_path, 'utf8'); const stats = await stat(file_path); res.json({ success: true, content: fileContent, metadata: { size: stats.size, modified: stats.mtime, language: detectLanguage(extname(file_path)), }, }); } catch (error) { res.status(404).json({ success: false, error: 'File not found', }); } break; case 'write': if (!file_path || content === undefined) { return res.status(400).json({ error: 'file_path and content are required' }); } await writeFile(file_path, content, 'utf8'); res.json({ success: true, message: `File written to ${file_path}`, }); break; case 'refactor': { if (!file_path || !prompt) { return res.status(400).json({ error: 'file_path and prompt are required' }); } const originalContent = await readFile(file_path, 'utf8'); const language = detectLanguage(extname(file_path)); // Define the voice archetype for refactoring const voice = { id: 'refactoring-specialist', name: 'Refactoring Specialist', systemPrompt: 'You are a senior software engineer specializing in code refactoring. You excel at improving code structure, readability, and maintainability while preserving functionality. You provide clean, well-documented refactored code with clear explanations of changes made.', temperature: 0.4, style: 'methodical', }; const refactorPrompt = `You are an expert ${language} developer specializing in code refactoring. Your task is to refactor the provided code based on the specific request: "${prompt}" **Refactoring Context:** - Language: ${language} - File: ${file_path} - Specific Request: ${prompt} **Original Code:** \`\`\`${language} ${originalContent} \`\`\` **Refactoring Guidelines:** 1. **Preserve Functionality** - Ensure all existing behavior is maintained 2. **Address the Request** - Specifically implement the requested changes 3. **Improve Code Quality** - Apply best practices and modern ${language} patterns 4. **Maintain API Compatibility** - Keep public interfaces unchanged unless specifically requested 5. **Add Documentation** - Include comments explaining significant changes **Required Output:** 1. **Refactored Code** - Complete, working code in proper ${language} syntax 2. **Change Summary** - Brief explanation of what was modified and why 3. **Testing Notes** - Any testing considerations for the changes Focus on delivering production-ready code that addresses the specific refactoring request while improving overall code quality.`; const response = await context.modelClient.processRequest( { prompt: `${voice.systemPrompt} ${refactorPrompt}`, temperature: voice.temperature, }, { files: [ { path: file_path, content: originalContent, type: language, language, }, ], workingDirectory: ((context.config as unknown as Record<string, unknown>) .workingDirectory as string) || process.cwd(), config: {}, structure: { directories: [], fileTypes: {} }, } ); // Extract code from response const codeMatch = response.content.match(/```[\w]*\n([\s\S]*?)\n```/); const refactoredCode = codeMatch ? codeMatch[1] : response.content; res.json({ success: true, original_code: originalContent, refactored_code: refactoredCode, explanation: response.content.replace(/```[\s\S]*?```/g, '').trim(), confidence: ((response as unknown as Record<string, unknown>).confidence as number) || 0.8, }); break; } default: res.status(400).json({ error: `Unknown operation: ${operation}` }); } } catch (error) { logger.error(`File operation ${req.params.operation} failed:`, error); res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Operation failed', }); } }); // Project scanning endpoint app.post('/api/project/scan', async (req, res) => { try { const { directory = process.cwd(), pattern = '**/*.{js,ts,jsx,tsx,py}' } = req.body; const { glob } = await import('glob'); const files = await glob(pattern, { cwd: directory, ignore: ['node_modules/**', '.git/**', 'dist/**', 'build/**'], }); const projectStructure = await Promise.all( files.slice(0, 50).map(async file => { try { const fullPath = join(directory, file); const stats = await stat(fullPath); return { path: file, full_path: fullPath, size: stats.size, modified: stats.mtime, language: detectLanguage(extname(file)), }; } catch (error) { return null; } }) ); res.json({ success: true, directory, files: projectStructure.filter(Boolean), total_files: files.length, scanned_files: projectStructure.filter(Boolean).length, }); } catch (error) { logger.error('Project scan failed:', error); res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Scan failed', }); } }); // Configuration endpoints app.get('/api/config', (req, res) => { res.json(context.config); }); app.post('/api/config', async (req, res): Promise<any> => { try { const { key, value } = req.body; if (!key) { return res.status(400).json({ error: 'Key is required' }); } const { ConfigManager } = await import('../config/config-manager.js'); const configManager = await ConfigManager.getInstance(); await configManager.set(key, value); res.json({ success: true, message: `Configuration updated: ${key}`, }); } catch (error) { res.status(500).json({ success: false, error: error instanceof Error ? error.message : 'Config update failed', }); } }); // WebSocket handling for real-time communication io.on('connection', socket => { console.log(chalk.gray(`🔌 IDE client connected: ${socket.id}`)); // Send initial status socket.emit('status', { connected: true, model_available: true, // Will be updated by actual check voices: context.config.voices?.available || [ 'explorer', 'maintainer', 'analyzer', 'developer', 'implementor', 'security', 'architect', 'designer', 'optimizer', ], }); // Handle real-time code generation socket.on('generate_realtime', async data => { try { const { prompt, voices, mode, context: _userContext } = data; socket.emit('generation_started', { id: data.id }); const synthesis = await context.voiceSystem.synthesize( prompt, voices || context.config.voices?.default || ['explorer', 'maintainer'], (mode || 'collaborative') as 'competitive' | 'collaborative' | 'consensus', context.modelClient ); socket.emit('generation_complete', { id: data.id, success: true, result: synthesis, responses: synthesis.responses || [], }); } catch (error) { socket.emit('generation_complete', { id: data.id, success: false, error: error instanceof Error ? error.message : 'Generation failed', }); } }); // Handle file watching requests socket.on('watch_file', data => { const { file_path } = data; // Implement file watching logic here console.log(chalk.gray(`👀 Watching file: ${file_path}`)); }); socket.on('disconnect', () => { console.log(chalk.gray(`🔌 IDE client disconnected: ${socket.id}`)); }); }); // Start server return new Promise(resolve => { server.listen(options.port, options.host, () => { console.log(chalk.green(`✅ Server running on http://${options.host}:${options.port}`)); console.log(chalk.gray(' Available endpoints:')); console.log(chalk.gray(' • GET /health')); console.log(chalk.gray(' • GET /api/model/status')); console.log(chalk.gray(' • GET /api/voices')); console.log(chalk.gray(' • POST /api/generate')); console.log(chalk.gray(' • POST /api/analyze')); console.log(chalk.gray(' • POST /api/file/:operation')); console.log(chalk.gray(' • POST /api/project/scan')); console.log(chalk.gray(' • WebSocket support for real-time communication')); logger.info('Server mode started', { host: options.host, port: options.port, cors: options.cors, auth: options.auth?.enabled, }); resolve(); }); }); } } /** * Detect programming language from file extension */ function detectLanguage(ext: string): string { const langMap: Record<string, string> = { '.js': 'javascript', '.ts': 'typescript', '.jsx': 'jsx', '.tsx': 'tsx', '.py': 'python', '.java': 'java', '.cpp': 'cpp', '.c': 'c', '.h': 'c', '.cs': 'csharp', '.php': 'php', '.rb': 'ruby', '.go': 'go', '.rs': 'rust', '.html': 'html', '.css': 'css', '.scss': 'scss', '.vue': 'vue', '.svelte': 'svelte', }; return langMap[ext.toLowerCase()] || 'text'; } /** * Calculate quality score based on analysis content */ function calculateQualityScore(analysisContent: string): number { let score = 0.5; // Base score // Positive indicators if ( analysisContent.includes('good') || analysisContent.includes('well-written') || analysisContent.includes('excellent') ) { score += 0.2; } if ( analysisContent.includes('clean') || analysisContent.includes('readable') || analysisContent.includes('maintainable') ) { score += 0.15; } if (analysisContent.includes('optimized') || analysisContent.includes('efficient')) { score += 0.1; } // Negative indicators if ( analysisContent.includes('issues') || analysisContent.includes('problems') || analysisContent.includes('bugs') ) { score -= 0.2; } if ( analysisContent.includes('improve') || analysisContent.includes('fix') || analysisContent.includes('refactor') ) { score -= 0.1; } if (analysisContent.includes('complex') || analysisContent.includes('difficult')) { score -= 0.05; } // Ensure score is between 0 and 1 return Math.max(0, Math.min(1, score)); } /** * Extract recommendations from analysis content */ function extractRecommendations(analysisContent: string): string[] { const recommendations: string[] = []; // Split into sentences and look for recommendation patterns const sentences = analysisContent .split(/[.!?]+/) .map(s => s.trim()) .filter(s => s.length > 0); for (const sentence of sentences) { const lowerSentence = sentence.toLowerCase(); // Look for recommendation patterns if ( lowerSentence.includes('should') || lowerSentence.includes('could') || lowerSentence.includes('consider') || lowerSentence.includes('recommend') || lowerSentence.includes('suggest') || lowerSentence.includes('improve') || lowerSentence.includes('add') || lowerSentence.includes('use') || lowerSentence.includes('implement') ) { // Clean up the sentence const recommendation = sentence.trim(); if (recommendation.length > 10 && recommendation.length < 200) { recommendations.push(recommendation); } } } return recommendations.slice(0, 5); // Return top 5 recommendations }