UNPKG

@codervisor/devlog-ai

Version:

AI Chat History Extractor & Docker-based Automation - TypeScript implementation for GitHub Copilot and other AI coding assistants with automated testing capabilities

280 lines (279 loc) 10.4 kB
/** * Docker-based GitHub Copilot Automation * * Main orchestrator for automated Copilot testing using containerized VS Code */ import { VSCodeContainer } from './vscode-container.js'; import { RealTimeCaptureParser } from '../capture/real-time-parser.js'; export class DockerCopilotAutomation { container; captureParser; config; sessionId; constructor(config) { this.config = config; this.container = new VSCodeContainer(config); this.captureParser = new RealTimeCaptureParser(); this.sessionId = `automation-${Date.now()}`; } /** * Run a complete automation session with multiple test scenarios */ async runSession(scenarios) { const startTime = new Date(); let containerInfo; const results = []; try { // Start the container if (this.config.debug) { console.log('Starting automation session...'); } containerInfo = await this.container.start(); // Wait for container to be fully ready await this.waitForContainerReady(); // Run each test scenario for (const scenario of scenarios) { if (this.config.debug) { console.log(`Running scenario: ${scenario.name}`); } try { const result = await this.runScenario(scenario); results.push(result); } catch (error) { // Create failed result results.push({ scenarioId: scenario.id, startTime: new Date(), endTime: new Date(), success: false, interactions: [], generatedCode: '', metrics: { totalSuggestions: 0, acceptedSuggestions: 0, rejectedSuggestions: 0, averageResponseTime: 0, }, error: error instanceof Error ? error.message : String(error), }); } } } finally { // Always clean up the container try { await this.container.stop(); containerInfo = this.container.getStatus(); } catch (error) { console.error('Error stopping container:', error); containerInfo = { id: '', status: 'error', error: String(error) }; } } const endTime = new Date(); const successful = results.filter((r) => r.success).length; const totalInteractions = results.reduce((sum, r) => sum + r.interactions.length, 0); return { sessionId: this.sessionId, startTime, endTime, scenarios: results, containerInfo, summary: { totalScenarios: scenarios.length, successfulScenarios: successful, failedScenarios: scenarios.length - successful, totalInteractions, overallSuccessRate: scenarios.length > 0 ? successful / scenarios.length : 0, }, }; } /** * Run a single test scenario */ async runScenario(scenario) { const startTime = new Date(); const interactions = []; try { // Create test file in container await this.createTestFile(scenario); // Start capture parser this.captureParser.startCapture(); // Execute the test scenario await this.executeScenarioSteps(scenario, interactions); // Stop capture and get interactions const capturedInteractions = await this.captureParser.stopCapture(); interactions.push(...capturedInteractions); // Get the generated code const generatedCode = await this.getGeneratedCode(scenario); // Calculate metrics const metrics = this.calculateMetrics(interactions); return { scenarioId: scenario.id, startTime, endTime: new Date(), success: true, interactions, generatedCode, metrics, }; } catch (error) { return { scenarioId: scenario.id, startTime, endTime: new Date(), success: false, interactions, generatedCode: '', metrics: { totalSuggestions: 0, acceptedSuggestions: 0, rejectedSuggestions: 0, averageResponseTime: 0, }, error: error instanceof Error ? error.message : String(error), }; } } /** * Wait for the container to be fully ready for automation */ async waitForContainerReady() { // Wait for VS Code extensions to be fully loaded await new Promise((resolve) => setTimeout(resolve, 10000)); // Verify Copilot extension is active try { const checkCommand = ['code-insiders', '--list-extensions', '--show-versions']; const output = await this.container.executeInContainer(checkCommand); if (!output.includes('GitHub.copilot')) { throw new Error('GitHub Copilot extension not found'); } if (this.config.debug) { console.log('Container is ready for automation'); } } catch (error) { throw new Error(`Container readiness check failed: ${error}`); } } /** * Create test file for scenario in container */ async createTestFile(scenario) { const fileName = `test-${scenario.id}.${this.getFileExtension(scenario.language)}`; const filePath = `/workspace/automation-test/src/${fileName}`; // Create the file with initial code const createFileCommand = [ 'sh', '-c', `echo '${scenario.initialCode.replace(/'/g, "'\\''")}' > ${filePath}`, ]; await this.container.executeInContainer(createFileCommand); if (this.config.debug) { console.log(`Created test file: ${filePath}`); } } /** * Execute the steps for a test scenario */ async executeScenarioSteps(scenario, interactions) { const fileName = `test-${scenario.id}.${this.getFileExtension(scenario.language)}`; const filePath = `/workspace/automation-test/src/${fileName}`; // Open file in VS Code const openCommand = ['code-insiders', filePath, '--wait', '--new-window']; // This would need to use VS Code API or automation tools // For now, we'll simulate the process for (const prompt of scenario.expectedPrompts) { // Simulate typing the prompt await this.simulateTyping(filePath, prompt); // Wait for Copilot suggestion await new Promise((resolve) => setTimeout(resolve, 2000)); // Capture interaction (this would be done by real-time parser) interactions.push({ timestamp: new Date(), trigger: 'keystroke', context: { fileName, fileContent: scenario.initialCode + prompt, cursorPosition: { line: 0, character: prompt.length }, precedingText: scenario.initialCode, followingText: '', }, suggestion: { text: `// Generated suggestion for: ${prompt}`, confidence: 0.8, accepted: true, }, }); } } /** * Simulate typing in VS Code (placeholder implementation) */ async simulateTyping(filePath, text) { // This would need actual VS Code automation // For now, append to file as simulation const appendCommand = ['sh', '-c', `echo '${text.replace(/'/g, "'\\''")}' >> ${filePath}`]; await this.container.executeInContainer(appendCommand); } /** * Get the final generated code from the test file */ async getGeneratedCode(scenario) { const fileName = `test-${scenario.id}.${this.getFileExtension(scenario.language)}`; const filePath = `/workspace/automation-test/src/${fileName}`; const readCommand = ['cat', filePath]; return await this.container.executeInContainer(readCommand); } /** * Calculate metrics from interactions */ calculateMetrics(interactions) { const suggestions = interactions.filter((i) => i.suggestion); const accepted = suggestions.filter((i) => i.suggestion?.accepted); const responseTimes = interactions .map((i) => i.metadata?.responseTime) .filter((t) => typeof t === 'number'); const averageResponseTime = responseTimes.length > 0 ? responseTimes.reduce((sum, time) => sum + time, 0) / responseTimes.length : 0; return { totalSuggestions: suggestions.length, acceptedSuggestions: accepted.length, rejectedSuggestions: suggestions.length - accepted.length, averageResponseTime, }; } /** * Get file extension for language */ getFileExtension(language) { const extensions = { javascript: 'js', typescript: 'ts', python: 'py', java: 'java', csharp: 'cs', cpp: 'cpp', c: 'c', go: 'go', rust: 'rs', php: 'php', ruby: 'rb', }; return extensions[language.toLowerCase()] || 'txt'; } /** * Clean up resources */ async cleanup() { try { await this.container.stop(); } catch (error) { console.error('Cleanup error:', error); } } }