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
text/typescript
/**
* 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 };