UNPKG

ai-debug-local-mcp

Version:

🎯 ENHANCED AI GUIDANCE v4.1.2: Dramatically improved tool descriptions help AI users choose the right tools instead of 'close enough' options. Ultra-fast keyboard automation (10x speed), universal recording, multi-ecosystem debugging support, and compreh

429 lines (365 loc) • 11.1 kB
/** * CI/CD Integration for AI Debug Local MCP * Generates automated CI/CD templates for competitive parity * GitHub Issue #1: CI/CD Integration - Competitive Parity Feature */ import fs from 'fs/promises'; import path from 'path'; interface CICDOptions { provider?: string; appUrl?: string; framework?: string; testCommand?: string; setupCommand?: string; environment?: Record<string, any>; hasTidewave?: boolean; tools?: string[]; } interface CICDConfig extends CICDOptions { appUrl: string; framework: string; provider: string; } class CICDIntegration { private supportedFrameworks: string[]; private supportedProviders: string[]; constructor() { this.supportedFrameworks = ['react', 'vue', 'angular', 'nextjs', 'phoenix', 'rails', 'node']; this.supportedProviders = ['github', 'gitlab', 'circleci', 'jenkins']; } /** * Initialize CI/CD setup for a project */ async initCI(projectDir: string, options: CICDOptions = {}) { const { provider = 'github', appUrl = 'http://localhost:3000', framework } = options; try { // Validate inputs this.validateConfig({ appUrl, provider, framework }); // Detect framework if not provided const detectedFramework = framework || await this.detectFramework(projectDir); const config: CICDConfig = { appUrl, framework: detectedFramework, provider, ...options }; const generatedFiles: string[] = []; // Generate CI/CD templates based on provider switch (provider) { case 'github': const githubFile = await this.generateGitHubActions(projectDir, config); generatedFiles.push(githubFile); break; case 'gitlab': const gitlabFile = await this.generateGitLabCI(projectDir, config); generatedFiles.push(gitlabFile); break; default: throw new Error(`Unsupported CI/CD provider: ${provider}`); } // Add NPM scripts await this.addNPMScripts(projectDir, config); generatedFiles.push('package.json'); return { success: true, files: generatedFiles.map((f: string) => path.relative(projectDir, f)), framework: detectedFramework, config }; } catch (error) { throw new Error(`CI/CD initialization failed: ${error instanceof Error ? error.message : String(error)}`); } } /** * Generate GitHub Actions workflow */ async generateGitHubActions(projectDir: string, config: CICDConfig) { await this.ensureDirectoryExists(projectDir); const workflowDir = path.join(projectDir, '.github', 'workflows'); await fs.mkdir(workflowDir, { recursive: true }); const workflowPath = path.join(workflowDir, 'ai-debug.yml'); const template = this.generateGitHubActionsTemplate(config); await fs.writeFile(workflowPath, template, 'utf8'); return workflowPath; } /** * Generate GitLab CI configuration */ async generateGitLabCI(projectDir: string, config: CICDConfig) { await this.ensureDirectoryExists(projectDir); const configPath = path.join(projectDir, '.gitlab-ci.yml'); const template = this.generateGitLabCITemplate(config); await fs.writeFile(configPath, template, 'utf8'); return configPath; } /** * Add NPM scripts for AI debugging */ async addNPMScripts(projectDir: string, config: CICDConfig) { const packageJsonPath = path.join(projectDir, 'package.json'); let packageJson: any; try { const content = await fs.readFile(packageJsonPath, 'utf8'); packageJson = JSON.parse(content); } catch (error) { // Create basic package.json if it doesn't exist packageJson = { name: path.basename(projectDir), version: '1.0.0', scripts: {} }; } // Add AI debug scripts packageJson.scripts = { ...packageJson.scripts, 'ai-debug': 'ai-debug-mcp debug_page --url=' + config.appUrl, 'ai-debug:ci': `ai-debug-mcp debug_page --url=${config.appUrl} --audit --ci`, 'ai-debug:performance': `ai-debug-mcp performance_audit --url=${config.appUrl}`, 'ai-debug:accessibility': `ai-debug-mcp run_accessibility_check --url=${config.appUrl}` }; // Add Tidewave scripts for Phoenix/Rails if (config.framework === 'phoenix' || config.framework === 'rails') { packageJson.scripts['ai-debug:tidewave'] = `ai-debug-mcp tidewave_query --url=${config.appUrl} --tool=logs`; } await fs.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf8'); return packageJsonPath; } /** * Detect project framework */ async detectFramework(projectDir: string): Promise<string> { try { // Check for Phoenix (mix.exs) const mixPath = path.join(projectDir, 'mix.exs'); try { await fs.access(mixPath); return 'phoenix'; } catch {} // Check for Rails (Gemfile) const gemfilePath = path.join(projectDir, 'Gemfile'); try { const gemfileContent = await fs.readFile(gemfilePath, 'utf8'); if (gemfileContent.includes('rails')) { return 'rails'; } } catch {} // Check for Node.js projects (package.json) const packageJsonPath = path.join(projectDir, 'package.json'); try { const packageContent = await fs.readFile(packageJsonPath, 'utf8'); const packageJson = JSON.parse(packageContent); const deps = { ...packageJson.dependencies, ...packageJson.devDependencies }; if (deps.react) return 'react'; if (deps.vue) return 'vue'; if (deps['@angular/core']) return 'angular'; if (deps.next) return 'nextjs'; return 'node'; } catch {} return 'unknown'; } catch (error) { return 'unknown'; } } /** * Generate GitHub Actions template */ generateGitHubActionsTemplate(config: CICDConfig) { const { appUrl = 'http://localhost:3000', framework = 'node', testCommand = 'npm test', setupCommand = 'npm install', environment = {}, hasTidewave = false, tools = ['debug_page', 'performance_audit'] } = config; const envVars = Object.entries(environment) .map(([key, value]: [string, any]) => ` ${key}: ${value}`) .join('\n'); const frameworkSetup = this.getFrameworkSetup(framework); const tidewaveConfig = hasTidewave ? this.getTidewaveConfig(framework) : ''; return `name: AI Debug Tests on: push: branches: [ main, develop ] pull_request: branches: [ main ] jobs: ai-debug-tests: runs-on: ubuntu-latest steps: - name: Checkout code uses: actions/checkout@v4 - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' cache: 'npm' ${frameworkSetup} - name: Install dependencies run: ${setupCommand} - name: Install AI Debug MCP run: npm install -g @living-software/ai-debug-local-mcp@latest - name: Start application run: | ${this.getStartCommand(framework)} & sleep 10 # Wait for app to start env: ${envVars || ' NODE_ENV: test'} - name: Run AI Debug Tests run: | ${tools.map((tool: string) => `ai-debug-mcp ${tool} --url=${appUrl}`).join('\n ')} env: APP_URL: ${appUrl} ${tidewaveConfig} - name: Upload Debug Reports uses: actions/upload-artifact@v4 if: always() with: name: ai-debug-reports path: debug-reports/ retention-days: 30 `; } /** * Generate GitLab CI template */ generateGitLabCITemplate(config: CICDConfig) { const { appUrl = 'http://localhost:3000', framework = 'node', environment = {} } = config; const envVars = Object.entries(environment) .map(([key, value]: [string, any]) => ` ${key}: "${value}"`) .join('\n'); return `stages: - test variables: APP_URL: ${appUrl} ${envVars} ai-debug-tests: stage: test image: node:18 before_script: - npm install -g @living-software/ai-debug-local-mcp@latest - npm install script: - ${this.getStartCommand(framework)} & - sleep 10 - ai-debug-mcp debug_page --url=$APP_URL - ai-debug-mcp performance_audit --url=$APP_URL - ai-debug-mcp run_accessibility_check --url=$APP_URL artifacts: reports: junit: debug-reports/junit.xml paths: - debug-reports/ expire_in: 1 week only: - main - develop - merge_requests `; } /** * Generate workflow template (for testing) */ generateWorkflowTemplate(config: CICDConfig) { return this.generateGitHubActionsTemplate(config); } /** * Get framework-specific setup steps */ getFrameworkSetup(framework: string) { switch (framework) { case 'phoenix': return ` - name: Setup Elixir uses: erlef/setup-beam@v1 with: elixir-version: '1.15' otp-version: '26' - name: Install Elixir dependencies run: mix deps.get`; case 'rails': return ` - name: Setup Ruby uses: ruby/setup-ruby@v1 with: ruby-version: '3.1' bundler-cache: true`; default: return ''; } } /** * Get framework-specific start command */ getStartCommand(framework: string) { switch (framework) { case 'phoenix': return 'mix phx.server'; case 'rails': return 'rails server'; case 'nextjs': return 'npm run dev'; case 'react': return 'npm start'; default: return 'npm start'; } } /** * Get Tidewave configuration for Phoenix/Rails */ getTidewaveConfig(framework: string) { if (framework === 'phoenix' || framework === 'rails') { return ` TIDEWAVE_ENABLED: true TIDEWAVE_TOOLS: "logs,sql,processes"`; } return ''; } /** * Validate configuration */ validateConfig(config: Partial<CICDConfig>) { const { appUrl, provider, framework } = config; // Validate URL if (appUrl && !this.isValidUrl(appUrl)) { throw new Error('Invalid configuration: appUrl must be a valid URL'); } // Validate provider if (provider && !this.supportedProviders.includes(provider)) { throw new Error(`Invalid configuration: unsupported provider ${provider}`); } // Validate framework if (framework && !this.supportedFrameworks.includes(framework)) { throw new Error(`Invalid configuration: unsupported framework ${framework}`); } } /** * Check if URL is valid */ isValidUrl(string: string) { try { new URL(string); return true; } catch { return false; } } /** * Ensure directory exists */ async ensureDirectoryExists(dir: string) { try { await fs.access(dir); } catch { throw new Error('Directory does not exist: ' + dir); } } } export { CICDIntegration };