UNPKG

vineguard-mcp-server-standalone

Version:

VineGuard MCP Server v2.1 - Intelligent QA Workflow System with advanced test generation for Jest/RTL, Cypress, and Playwright. Features smart project analysis, progressive testing strategies, and comprehensive quality patterns for React/Vue/Angular proje

1,230 lines (1,205 loc) 80.7 kB
#!/usr/bin/env node /** * VineGuard MCP Server - Your AI Testing Partner * Complete workflow: PRD Analysis → Code Understanding → Test Planning → Test Generation → Execution → Results → Fixes * Default Mode: Orchestration (with standard mode fallback) */ import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListResourcesRequestSchema, ListToolsRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import * as fs from 'fs/promises'; import * as path from 'path'; import { spawn } from 'child_process'; // Import new security and testing modules import { InputValidator } from './security/input-validator.js'; import { defaultRateLimiter, RateLimiter } from './security/rate-limiter.js'; import { SecurityAuditor } from './tools/security-audit.js'; import { ComponentTester } from './tools/component-testing.js'; import { HttpTransport } from './transport/http.js'; // Import enhanced test generators and workflow detection import { JestGenerator } from './test-generators/jest-generator.js'; import { CypressGenerator } from './test-generators/cypress-generator.js'; import { PlaywrightGenerator } from './test-generators/playwright-generator.js'; import { TestOrganizer } from './test-organization.js'; const PROJECT_ROOT = process.env.VINEGUARD_VINEYARD_ROOT || process.cwd(); const VINEGUARD_MODE = process.env.VINEGUARD_MODE || 'orchestrator'; const VINEGUARD_DEFAULT = process.env.VINEGUARD_DEFAULT || 'intelligent'; const ENABLE_MCP_ORCHESTRATION = process.env.VINEGUARD_ENABLE_MCP_ORCHESTRATION !== 'false'; const TRANSPORT_MODE = process.env.VINEGUARD_TRANSPORT || 'stdio'; // 'stdio' or 'http' const HTTP_PORT = parseInt(process.env.VINEGUARD_HTTP_PORT || '3001'); console.error(`[VineGuard MCP] Starting server for project: ${PROJECT_ROOT}`); console.error(`[VineGuard MCP] Mode: ${VINEGUARD_MODE}, Default: ${VINEGUARD_DEFAULT}`); console.error(`[VineGuard MCP] Orchestration: ${ENABLE_MCP_ORCHESTRATION ? 'Enabled' : 'Disabled'}`); console.error(`[VineGuard MCP] Transport: ${TRANSPORT_MODE}${TRANSPORT_MODE === 'http' ? ` (port ${HTTP_PORT})` : ''}`); const server = new Server({ name: 'vineguard', version: '2.0.0', }, { capabilities: { tools: {}, resources: {} } }); // Tool Handlers server.setRequestHandler(ListToolsRequestSchema, async () => { const workflowTools = [ { name: 'analyze_prd', description: 'Analyze Product Requirements Document and extract user stories, test scenarios, and acceptance criteria', inputSchema: { type: 'object', properties: { prdPath: { type: 'string', description: 'Path to PRD file (markdown, text, or README)', default: './README.md' }, extractUserStories: { type: 'boolean', description: 'Extract user stories from PRD', default: true }, generateTestScenarios: { type: 'boolean', description: 'Generate test scenarios based on requirements', default: true }, riskAssessment: { type: 'boolean', description: 'Perform risk assessment for testing priorities', default: true } } } }, { name: 'create_test_plan', description: 'Generate comprehensive test plan based on PRD analysis and code structure', inputSchema: { type: 'object', properties: { prdAnalysis: { type: 'object', description: 'PRD analysis results from analyze_prd' }, projectScan: { type: 'object', description: 'Project scan results from scan_project' }, testTypes: { type: 'array', items: { type: 'string', enum: ['unit', 'integration', 'e2e', 'visual', 'performance', 'accessibility'] }, description: 'Types of tests to include in plan', default: ['unit', 'integration', 'e2e'] }, coverageTarget: { type: 'number', description: 'Target test coverage percentage', default: 80 } } } }, { name: 'generate_vineguard_prd', description: 'Create normalized VineGuard PRD from user requirements and code analysis', inputSchema: { type: 'object', properties: { userPrdPath: { type: 'string', description: 'Path to user PRD file' }, codeAnalysis: { type: 'object', description: 'Code analysis results' }, includeTestStrategy: { type: 'boolean', description: 'Include testing strategy in generated PRD', default: true }, outputPath: { type: 'string', description: 'Where to save the generated VineGuard PRD', default: './VINEGUARD_PRD.md' } } } }, { name: 'detect_bugs', description: 'Proactive bug detection using static analysis and pattern recognition', inputSchema: { type: 'object', properties: { scanPath: { type: 'string', description: 'Path to scan for bugs', default: './src' }, severity: { type: 'string', enum: ['all', 'critical', 'high', 'medium'], description: 'Minimum bug severity to report', default: 'medium' }, includeFixSuggestions: { type: 'boolean', description: 'Include automated fix suggestions', default: true }, analyzePatterns: { type: 'array', items: { type: 'string', enum: ['security', 'performance', 'accessibility', 'async-errors', 'memory-leaks'] }, description: 'Specific bug patterns to analyze', default: ['security', 'performance', 'async-errors'] } } } }, { name: 'generate_fixes', description: 'Generate automated fixes for detected issues and bugs', inputSchema: { type: 'object', properties: { bugReport: { type: 'object', description: 'Bug detection results from detect_bugs' }, autoApply: { type: 'boolean', description: 'Automatically apply safe fixes', default: false }, createTests: { type: 'boolean', description: 'Create regression tests for fixes', default: true }, backupOriginal: { type: 'boolean', description: 'Backup original files before applying fixes', default: true } } } } ]; const coreTools = [ { name: 'scan_project', description: 'Analyze project structure, detect frameworks, and assess testing setup', inputSchema: { type: 'object', properties: { path: { type: 'string', description: 'Project root path to scan', default: PROJECT_ROOT }, deep: { type: 'boolean', description: 'Perform deep analysis including dependencies', default: false } } } }, { name: 'run_tests', description: 'Execute tests using detected or specified test framework', inputSchema: { type: 'object', properties: { framework: { type: 'string', enum: ['npm', 'jest', 'vitest', 'playwright', 'cypress'], description: 'Test framework to use', default: 'npm' }, coverage: { type: 'boolean', description: 'Generate coverage report', default: false }, watch: { type: 'boolean', description: 'Run in watch mode', default: false }, pattern: { type: 'string', description: 'Test file pattern to run' } } } }, { name: 'generate_test', description: 'Generate comprehensive test file for a component or function', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to the source file to generate tests for' }, testType: { type: 'string', enum: ['unit', 'integration', 'e2e'], description: 'Type of test to generate', default: 'unit' }, framework: { type: 'string', enum: ['jest', 'vitest', 'playwright', 'cypress'], description: 'Testing framework to use', default: 'jest' }, template: { type: 'string', enum: ['basic', 'comprehensive', 'component'], description: 'Test template style', default: 'comprehensive' } }, required: ['filePath'] } }, { name: 'analyze_code', description: 'Analyze code for common issues, anti-patterns, and improvement suggestions', inputSchema: { type: 'object', properties: { filePath: { type: 'string', description: 'Path to file to analyze' }, patterns: { type: 'array', items: { type: 'string', enum: ['all', 'performance', 'security', 'accessibility', 'async-errors', 'react-patterns'] }, description: 'Specific patterns to analyze', default: ['all'] }, includeFixSuggestions: { type: 'boolean', description: 'Include automated fix suggestions', default: true } }, required: ['filePath'] } }, { name: 'setup_testing', description: 'Initialize testing setup for the project with recommended configurations', inputSchema: { type: 'object', properties: { frameworks: { type: 'array', items: { type: 'string', enum: ['jest', 'vitest', 'playwright', 'cypress'] }, description: 'Testing frameworks to set up', default: ['jest'] }, projectType: { type: 'string', enum: ['react', 'vue', 'angular', 'svelte', 'astro', 'next', 'node'], description: 'Project type for framework-specific setup' } } } }, { name: 'security_audit', description: 'Comprehensive security audit and vulnerability scanning', inputSchema: { type: 'object', properties: { projectPath: { type: 'string', description: 'Path to project for security audit', default: PROJECT_ROOT }, includeNodeModules: { type: 'boolean', description: 'Include node_modules in security scan', default: false }, includeDependencyAudit: { type: 'boolean', description: 'Audit npm dependencies for vulnerabilities', default: true }, maxFileSize: { type: 'number', description: 'Maximum file size to scan (bytes)', default: 1048576 } } } }, { name: 'test_component', description: 'Generate comprehensive tests for React/Vue/Angular components', inputSchema: { type: 'object', properties: { componentPath: { type: 'string', description: 'Path to component file to test' }, testType: { type: 'string', enum: ['unit', 'integration', 'visual', 'accessibility'], description: 'Type of component test to generate', default: 'unit' }, framework: { type: 'string', enum: ['auto-detect', 'react', 'vue', 'angular', 'svelte'], description: 'Component framework (auto-detect if not specified)', default: 'auto-detect' }, includeVisualTests: { type: 'boolean', description: 'Include visual regression tests', default: false }, includeAccessibilityTests: { type: 'boolean', description: 'Include accessibility tests', default: true }, mockDependencies: { type: 'boolean', description: 'Mock external dependencies', default: true } }, required: ['componentPath'] } } ]; return { tools: [...workflowTools, ...coreTools] }; }); server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { // Security: Rate limiting const clientId = RateLimiter.createIdentifier(); const rateLimit = defaultRateLimiter.checkLimit(clientId, name); if (!rateLimit.allowed) { return { content: [{ type: 'text', text: `Rate limit exceeded: ${rateLimit.error}` }], isError: true }; } // Security: Input validation and sanitization const validation = InputValidator.validateToolArgs(name, args); if (!validation.isValid) { return { content: [{ type: 'text', text: `Input validation failed: ${validation.error}` }], isError: true }; } // Use sanitized arguments const sanitizedArgs = validation.sanitizedValue; // Workflow tools (PRD → Code → Test → Fix) switch (name) { case 'analyze_prd': return await analyzePRD(args?.prdPath || './README.md', args?.extractUserStories !== false, args?.generateTestScenarios !== false, args?.riskAssessment !== false); case 'create_test_plan': return await createTestPlan(args?.prdAnalysis, args?.projectScan, args?.testTypes || ['unit', 'integration', 'e2e'], args?.coverageTarget || 80); case 'generate_vineguard_prd': return await generateVineGuardPRD(args?.userPrdPath, args?.codeAnalysis, args?.includeTestStrategy !== false, args?.outputPath || './VINEGUARD_PRD.md'); case 'detect_bugs': return await detectBugs(args?.scanPath || './src', args?.severity || 'medium', args?.includeFixSuggestions !== false, args?.analyzePatterns || ['security', 'performance', 'async-errors']); case 'generate_fixes': return await generateFixes(args?.bugReport, args?.autoApply || false, args?.createTests !== false, args?.backupOriginal !== false); // Core tools case 'scan_project': return await scanProject(args?.path || PROJECT_ROOT, args?.deep || false); case 'run_tests': return await runTests(args?.framework || 'npm', args?.coverage || false, args?.watch || false, args?.pattern); case 'generate_test': return await generateTest(args?.filePath, args?.testType || 'unit', args?.framework || 'jest', args?.template || 'comprehensive'); case 'analyze_code': return await analyzeCode(args?.filePath, args?.patterns || ['all'], args?.includeFixSuggestions !== false); case 'setup_testing': return await setupTesting(sanitizedArgs?.frameworks || ['jest'], sanitizedArgs?.projectType); case 'security_audit': const auditResult = await SecurityAuditor.auditProject(sanitizedArgs?.projectPath || PROJECT_ROOT, { includeNodeModules: sanitizedArgs?.includeNodeModules || false, includeDependencyAudit: sanitizedArgs?.includeDependencyAudit !== false, maxFileSize: sanitizedArgs?.maxFileSize || 1048576 }); return { content: [{ type: 'text', text: JSON.stringify(auditResult, null, 2) }] }; case 'test_component': const componentResult = await ComponentTester.testComponent(sanitizedArgs?.componentPath, { testType: sanitizedArgs?.testType || 'unit', framework: sanitizedArgs?.framework || 'auto-detect', includeVisualTests: sanitizedArgs?.includeVisualTests || false, includeAccessibilityTests: sanitizedArgs?.includeAccessibilityTests !== false, mockDependencies: sanitizedArgs?.mockDependencies !== false }); return { content: [{ type: 'text', text: `Generated test file:\n\n\`\`\`typescript\n${componentResult.testCode}\n\`\`\`\n\n**Analysis:**\n${JSON.stringify(componentResult.analysis, null, 2)}\n\n**Recommendations:**\n${componentResult.recommendations.map(r => `- ${r}`).join('\n')}` }] }; default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [{ type: 'text', text: `Error: ${error instanceof Error ? error.message : 'Unknown error occurred'}` }], isError: true }; } }); // Resource Handlers server.setRequestHandler(ListResourcesRequestSchema, async () => { return { resources: [ { uri: 'vineguard://templates/jest-component', name: 'Jest Component Test Template', description: 'Comprehensive template for testing React/Vue components with Jest', mimeType: 'text/javascript' }, { uri: 'vineguard://templates/jest-function', name: 'Jest Function Test Template', description: 'Template for testing utility functions and services with Jest', mimeType: 'text/javascript' }, { uri: 'vineguard://templates/playwright-e2e', name: 'Playwright E2E Test Template', description: 'Template for end-to-end testing with Playwright', mimeType: 'text/javascript' }, { uri: 'vineguard://config/jest', name: 'Jest Configuration Template', description: 'Optimized Jest configuration for modern projects', mimeType: 'application/json' } ] }; }); server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const { uri } = request.params; const templates = { 'vineguard://templates/jest-component': `import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { ComponentName } from './ComponentName'; describe('ComponentName', () => { beforeEach(() => { // Setup before each test }); afterEach(() => { // Cleanup after each test }); it('renders without crashing', () => { render(<ComponentName />); expect(screen.getByRole('main')).toBeInTheDocument(); }); it('displays correct initial content', () => { render(<ComponentName />); expect(screen.getByText(/expected text/i)).toBeInTheDocument(); }); it('handles user interactions correctly', async () => { const user = userEvent.setup(); render(<ComponentName />); const button = screen.getByRole('button', { name: /click me/i }); await user.click(button); await waitFor(() => { expect(screen.getByText(/result/i)).toBeInTheDocument(); }); }); it('handles error states gracefully', () => { const mockError = jest.fn(); console.error = mockError; render(<ComponentName errorProp={true} />); expect(mockError).toHaveBeenCalled(); }); it('handles loading states', () => { render(<ComponentName loading={true} />); expect(screen.getByRole('progressbar')).toBeInTheDocument(); }); it('handles edge cases', () => { render(<ComponentName data={null} />); expect(screen.getByText(/no data/i)).toBeInTheDocument(); }); });`, 'vineguard://templates/jest-function': `import { functionName } from './functionName'; describe('functionName', () => { it('returns expected result for valid input', () => { const result = functionName('valid input'); expect(result).toEqual('expected output'); }); it('handles edge cases correctly', () => { expect(() => functionName(null)).toThrow('Expected error message'); expect(() => functionName(undefined)).toThrow('Expected error message'); expect(functionName('')).toEqual('default value'); }); it('handles async operations', async () => { const result = await functionName(); expect(result).toBeDefined(); }); it('maintains immutability', () => { const input = { key: 'value' }; const result = functionName(input); expect(input).toEqual({ key: 'value' }); // Original unchanged expect(result).not.toBe(input); // New object returned }); });`, 'vineguard://templates/playwright-e2e': `import { test, expect } from '@playwright/test'; test.describe('Feature Name', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); // Setup common to all tests }); test('should complete main user flow', async ({ page }) => { await page.click('text=Feature Link'); await expect(page).toHaveURL(/.*feature/); await page.fill('[data-testid="input-field"]', 'test data'); await page.click('button[type="submit"]'); await expect(page.locator('.success-message')).toBeVisible(); await expect(page.locator('.success-message')).toContainText('Success'); }); test('should handle form validation', async ({ page }) => { await page.click('text=Feature Link'); await page.click('button[type="submit"]'); await expect(page.locator('.error-message')).toBeVisible(); await expect(page.locator('.error-message')).toContainText('Required field'); }); test('should work on mobile viewport', async ({ page }) => { await page.setViewportSize({ width: 375, height: 667 }); await page.click('text=Feature Link'); await expect(page.locator('.mobile-menu')).toBeVisible(); }); });`, 'vineguard://config/jest': `{ "preset": "ts-jest", "testEnvironment": "jsdom", "setupFilesAfterEnv": ["<rootDir>/src/setupTests.ts"], "testMatch": [ "**/__tests__/**/*.{js,jsx,ts,tsx}", "**/*.{test,spec}.{js,jsx,ts,tsx}" ], "moduleNameMapper": { "^@/(.*)$": "<rootDir>/src/$1", "\\\\.(css|less|scss|sass)$": "identity-obj-proxy" }, "collectCoverageFrom": [ "src/**/*.{js,jsx,ts,tsx}", "!src/**/*.d.ts", "!src/**/index.{js,jsx,ts,tsx}" ], "coverageThreshold": { "global": { "branches": 75, "functions": 80, "lines": 80, "statements": 80 } } }` }; const content = templates[uri]; if (!content) { throw new Error(`Unknown resource: ${uri}`); } return { contents: [{ uri, mimeType: uri.includes('/config/') ? 'application/json' : 'text/javascript', text: content }] }; }); // Workflow Implementation Functions async function analyzePRD(prdPath, extractUserStories, generateTestScenarios, riskAssessment) { try { console.error(`[VineGuard] Analyzing PRD: ${prdPath}`); const fullPath = path.resolve(PROJECT_ROOT, prdPath); let prdContent = ''; try { prdContent = await fs.readFile(fullPath, 'utf-8'); } catch (error) { throw new Error(`Could not read PRD file: ${prdPath}`); } const analysis = { document: { title: extractTitle(prdContent), wordCount: prdContent.split(/\s+/).length, sections: extractSections(prdContent), lastModified: new Date().toISOString() }, userStories: extractUserStories ? extractUserStoriesFromPRD(prdContent) : [], testScenarios: generateTestScenarios ? generateTestScenariosFromPRD(prdContent) : [], acceptanceCriteria: extractAcceptanceCriteria(prdContent), features: extractFeatures(prdContent), businessRules: extractBusinessRules(prdContent), riskAssessment: riskAssessment ? performRiskAssessment(prdContent) : null, testingRecommendations: generateTestingRecommendations(prdContent), analyzedAt: new Date().toISOString() }; return { content: [{ type: 'text', text: JSON.stringify(analysis, null, 2) }] }; } catch (error) { throw new Error(`Failed to analyze PRD: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async function createTestPlan(prdAnalysis, projectScan, testTypes, coverageTarget) { try { console.error('[VineGuard] Creating comprehensive test plan'); const testPlan = { id: `test-plan-${Date.now()}`, name: 'VineGuard Comprehensive Test Plan', description: 'AI-generated test plan based on PRD analysis and code structure', createdAt: new Date().toISOString(), targetCoverage: coverageTarget, testTypes: testTypes, estimatedDuration: calculateEstimatedDuration(testTypes, prdAnalysis), unitTests: testTypes.includes('unit') ? generateUnitTestPlan(projectScan, prdAnalysis) : [], integrationTests: testTypes.includes('integration') ? generateIntegrationTestPlan(projectScan, prdAnalysis) : [], e2eTests: testTypes.includes('e2e') ? generateE2ETestPlan(prdAnalysis) : [], visualTests: testTypes.includes('visual') ? generateVisualTestPlan(prdAnalysis) : [], performanceTests: testTypes.includes('performance') ? generatePerformanceTestPlan(prdAnalysis) : [], accessibilityTests: testTypes.includes('accessibility') ? generateAccessibilityTestPlan(prdAnalysis) : [], priorities: { critical: extractCriticalTests(prdAnalysis), high: extractHighPriorityTests(prdAnalysis), medium: extractMediumPriorityTests(prdAnalysis) }, executionStrategy: { parallel: true, maxWorkers: 4, retryFailedTests: true, maxRetries: 2, testTimeout: 30000 }, recommendations: generateTestPlanRecommendations(prdAnalysis, projectScan, testTypes) }; return { content: [{ type: 'text', text: JSON.stringify(testPlan, null, 2) }] }; } catch (error) { throw new Error(`Failed to create test plan: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async function generateVineGuardPRD(userPrdPath, codeAnalysis, includeTestStrategy, outputPath) { try { console.error(`[VineGuard] Generating normalized PRD from ${userPrdPath}`); let userPrdContent = ''; if (userPrdPath) { const fullPath = path.resolve(PROJECT_ROOT, userPrdPath); userPrdContent = await fs.readFile(fullPath, 'utf-8'); } const vineGuardPRD = { title: 'VineGuard Normalized PRD', version: '1.0.0', generatedAt: new Date().toISOString(), overview: { purpose: 'Normalized requirements document for comprehensive testing', scope: extractScope(userPrdContent), objectives: extractObjectives(userPrdContent) }, userStories: extractUserStoriesFromPRD(userPrdContent), functionalRequirements: extractFunctionalRequirements(userPrdContent), nonFunctionalRequirements: extractNonFunctionalRequirements(userPrdContent), acceptanceCriteria: extractAcceptanceCriteria(userPrdContent), testStrategy: includeTestStrategy ? { approach: 'Risk-based testing with AI-powered test generation', frameworks: ['Jest', 'Playwright', 'Cypress'], coverageTargets: { unit: 85, integration: 75, e2e: 60 }, automationStrategy: 'Maximize automation for regression testing' } : null, qualityGates: { codeQuality: 'SonarQube quality gate', testCoverage: '80% minimum coverage', performanceThresholds: 'Sub-2s page load times', accessibilityCompliance: 'WCAG 2.1 AA compliance' }, riskMatrix: generateRiskMatrix(userPrdContent, codeAnalysis) }; // Save to file if output path provided const prdMarkdown = generatePRDMarkdown(vineGuardPRD); return { content: [{ type: 'text', text: `Generated VineGuard PRD:\n\n${prdMarkdown}\n\n**Next Steps:**\n1. Save this content to ${outputPath}\n2. Review and customize the generated requirements\n3. Use this PRD for comprehensive test planning` }] }; } catch (error) { throw new Error(`Failed to generate VineGuard PRD: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async function detectBugs(scanPath, severity, includeFixSuggestions, analyzePatterns) { try { console.error(`[VineGuard] Detecting bugs in ${scanPath}, severity: ${severity}`); const fullPath = path.resolve(PROJECT_ROOT, scanPath); const files = await findFiles(fullPath); const bugReport = { scanPath: fullPath, scannedFiles: files.length, analyzedPatterns: analyzePatterns, severity: severity, timestamp: new Date().toISOString(), issues: [], summary: { critical: 0, high: 0, medium: 0, low: 0, total: 0 }, fixSuggestions: [] }; for (const file of files.slice(0, 20)) { // Limit to first 20 files for demo try { const content = await fs.readFile(file, 'utf-8'); const fileIssues = await analyzeFileForBugs(file, content, analyzePatterns, severity); bugReport.issues.push(...fileIssues); // Update summary counts fileIssues.forEach(issue => { bugReport.summary[issue.severity]++; bugReport.summary.total++; }); if (includeFixSuggestions) { const fixes = await generateBugFixes(file, fileIssues); bugReport.fixSuggestions.push(...fixes); } } catch (error) { console.error(`Error analyzing file ${file}:`, error); } } bugReport.qualityScore = calculateQualityScore(bugReport.issues, []); bugReport.recommendations = generateBugFixRecommendations(bugReport); return { content: [{ type: 'text', text: JSON.stringify(bugReport, null, 2) }] }; } catch (error) { throw new Error(`Failed to detect bugs: ${error instanceof Error ? error.message : 'Unknown error'}`); } } async function generateFixes(bugReport, autoApply, createTests, backupOriginal) { try { console.error('[VineGuard] Generating automated fixes'); if (!bugReport || !bugReport.issues) { throw new Error('Invalid bug report provided'); } const fixResults = { timestamp: new Date().toISOString(), autoApply: autoApply, createTests: createTests, backupOriginal: backupOriginal, fixes: [], testsGenerated: [], summary: { totalIssues: bugReport.issues.length, fixableIssues: 0, appliedFixes: 0, testsCreated: 0 } }; for (const issue of bugReport.issues) { if (issue.autoFixable) { fixResults.summary.fixableIssues++; const fix = await createAutomatedFix(issue, backupOriginal); fixResults.fixes.push(fix); if (autoApply && fix.safe) { await applyFix(fix); fixResults.summary.appliedFixes++; } if (createTests) { const regressionTest = await createRegressionTest(issue, fix); fixResults.testsGenerated.push(regressionTest); fixResults.summary.testsCreated++; } } } return { content: [{ type: 'text', text: JSON.stringify(fixResults, null, 2) }] }; } catch (error) { throw new Error(`Failed to generate fixes: ${error instanceof Error ? error.message : 'Unknown error'}`); } } // Helper functions for workflow implementation function extractTitle(content) { const titleMatch = content.match(/^#\s+(.+)$/m); return titleMatch ? titleMatch[1].trim() : 'Untitled Document'; } function extractSections(content) { const sections = content.match(/^##\s+(.+)$/gm); return sections ? sections.map(s => s.replace(/^##\s+/, '')) : []; } function extractUserStoriesFromPRD(content) { const stories = []; const storyPattern = /(?:as\s+(?:a|an)\s+(.+?),?\s*)?(?:i\s+want\s+to\s+(.+?))(?:\s+so\s+that\s+(.+?))?/gi; let match; let id = 1; while ((match = storyPattern.exec(content)) !== null) { stories.push({ id: `US-${id++}`, userType: match[1] || 'user', functionality: match[2], benefit: match[3] || '', priority: 'medium', estimatedComplexity: 5, extractedFrom: 'PRD analysis' }); } return stories; } function generateTestScenariosFromPRD(content) { const scenarios = []; const features = extractFeatures(content); features.forEach((feature, index) => { scenarios.push({ id: `TS-${index + 1}`, title: `Test ${feature.name}`, description: `Comprehensive testing of ${feature.description}`, type: 'functional', priority: feature.priority, steps: [ { description: 'Navigate to feature', action: 'navigate', expected: 'Page loads successfully' }, { description: 'Interact with feature', action: 'interact', expected: 'Feature responds correctly' }, { description: 'Verify outcome', action: 'verify', expected: 'Expected result achieved' } ] }); }); return scenarios; } function extractAcceptanceCriteria(content) { const criteria = []; const givenWhenThenPattern = /given\s+(.+?),?\s*when\s+(.+?),?\s*then\s+(.+?)(?:\.|$)/gi; let match; let id = 1; while ((match = givenWhenThenPattern.exec(content)) !== null) { criteria.push({ id: `AC-${id++}`, given: match[1].trim(), when: match[2].trim(), then: match[3].trim(), priority: 'high', testable: true }); } return criteria; } function extractFeatures(content) { const features = []; const sections = extractSections(content); sections.forEach((section, index) => { if (!section.toLowerCase().includes('introduction') && !section.toLowerCase().includes('overview')) { features.push({ name: section, description: `Feature: ${section}`, priority: index < 3 ? 'high' : 'medium' }); } }); return features; } function extractBusinessRules(content) { const rules = []; const rulePattern = /(?:rule|constraint|requirement):\s*(.+?)(?:\.|$)/gi; let match; let id = 1; while ((match = rulePattern.exec(content)) !== null) { rules.push({ id: `BR-${id++}`, description: match[1].trim(), conditions: ['user interaction', 'system validation'] }); } return rules; } function performRiskAssessment(content) { const complexity = content.length / 1000; // Simple complexity metric const features = extractFeatures(content); return { level: complexity > 5 ? 'high' : complexity > 2 ? 'medium' : 'low', factors: [ { factor: 'Feature Complexity', impact: complexity > 3 ? 'high' : 'medium' }, { factor: 'Integration Points', impact: features.length > 5 ? 'high' : 'medium' }, { factor: 'User Experience', impact: 'medium' } ], mitigation: [ 'Comprehensive unit testing', 'Integration testing for all features', 'User acceptance testing' ] }; } function generateTestingRecommendations(content) { const features = extractFeatures(content); const recommendations = [ 'Start with unit tests for core functionality', 'Add integration tests for feature interactions', 'Include E2E tests for critical user journeys' ]; if (features.length > 10) { recommendations.push('Consider parallel test execution for faster feedback'); } if (content.toLowerCase().includes('api')) { recommendations.push('Add API contract testing'); } if (content.toLowerCase().includes('mobile')) { recommendations.push('Include mobile-specific testing scenarios'); } return recommendations; } // Additional helper functions for workflow implementation function calculateEstimatedDuration(testTypes, prdAnalysis) { const baseTime = 30; // minutes const typeMultipliers = { unit: 1, integration: 1.5, e2e: 3, visual: 2, performance: 2, accessibility: 1.5 }; return testTypes.reduce((total, type) => total + (baseTime * (typeMultipliers[type] || 1)), 0); } function generateUnitTestPlan(projectScan, prdAnalysis) { return [{ id: 'unit-1', name: 'Core functionality tests', priority: 'high', estimatedTime: 30 }]; } function generateIntegrationTestPlan(projectScan, prdAnalysis) { return [{ id: 'integration-1', name: 'API integration tests', priority: 'high', estimatedTime: 45 }]; } function generateE2ETestPlan(prdAnalysis) { return [{ id: 'e2e-1', name: 'User journey tests', priority: 'critical', estimatedTime: 90 }]; } function generateVisualTestPlan(prdAnalysis) { return [{ id: 'visual-1', name: 'Visual regression tests', priority: 'medium', estimatedTime: 60 }]; } function generatePerformanceTestPlan(prdAnalysis) { return [{ id: 'perf-1', name: 'Load testing', priority: 'high', estimatedTime: 120 }]; } function generateAccessibilityTestPlan(prdAnalysis) { return [{ id: 'a11y-1', name: 'WCAG compliance tests', priority: 'high', estimatedTime: 60 }]; } function extractCriticalTests(prdAnalysis) { return ['User authentication', 'Core business logic']; } function extractHighPriorityTests(prdAnalysis) { return ['Data validation', 'API endpoints']; } function extractMediumPriorityTests(prdAnalysis) { return ['UI interactions', 'Error handling']; } function generateTestPlanRecommendations(prdAnalysis, projectScan, testTypes) { return [ 'Start with unit tests for fastest feedback', 'Add integration tests for API coverage', 'Include accessibility tests for compliance' ]; } function extractScope(content) { return 'Application functionality and user experience'; } function extractObjectives(content) { return ['Ensure quality', 'Improve user experience', 'Reduce bugs']; } function extractFunctionalRequirements(content) { return extractFeatures(content).map(f => f.name); } function extractNonFunctionalRequirements(content) { return ['Performance', 'Security', 'Accessibility']; } function generateRiskMatrix(userPrdContent, codeAnalysis) { return { high: ['Data loss'], medium: ['Performance'], low: ['UI glitches'] }; } function generatePRDMarkdown(prd) { return `# ${prd.title}\n\n## Overview\n${prd.overview.purpose}\n\n## User Stories\n${prd.userStories.map((s) => `- ${s.functionality}`).join('\n')}`; } async function analyzeFileForBugs(file, content, patterns, severity) { const issues = []; // Simple bug detection patterns if (patterns.includes('security') && content.includes('eval(')) { issues.push({ file, type: 'security', severity: 'critical', message: 'Use of eval() detected', line: 1, autoFixable: true }); } if (patterns.includes('async-errors') && content.includes('fetch(') && !content.includes('.catch(')) { issues.push({ file, type: 'async-error', severity: 'medium', message: 'Unhandled fetch() call', line: 1, autoFixable: true }); } return issues; } async function generateBugFixes(file, issues) { return issues.map(issue => ({ issueId: issue.type, file, fix: `Fix for ${issue.message}`, safe: true })); } function generateBugFixRecommendations(bugReport) { return [ 'Fix critical issues first', 'Add error handling for async operations', 'Implement security best practices' ]; } async function createAutomatedFix(issue, backup) { return { id: `fix-${issue.type}`, issue: issue.type, description: `Automated fix for ${issue.message}`, safe: true, code: '// Fixed code would go here' }; } async function applyFix(fix) { console.error(`[VineGuard] Applied fix: ${fix.description}`); } async function createRegressionTest(issue, fix) { return { id: `test-${issue.type}`, name: `Regression test for ${issue.message}`, code: '// Test code would go here' }; } // Implementation functions async function scanProject(projectPath, deep) { try { console.error(`[VineGuard] Scanning project: ${projectPath}, deep: ${deep}`); const packageJsonPath = path.join(projectPath, 'package.json'); let packageJson = {}; try { const packageJsonContent = await fs.readFile(packageJsonPath, 'utf-8'); packageJson = JSON.parse(packageJsonContent); } catch (error) { console.error('[VineGuard] No package.json found or invalid JSON'); } const dependencies = { ...packageJson.dependencies, ...packageJson.devDependencies }; // Detect framework let framework = 'unknown'; if (dependencies['astro']) framework = 'astro'; else if (dependencies['@nuxt/kit'] || dependencies['nuxt']) framework = 'nuxt'; else if (dependencies['next']) framework = 'nextjs'; else if (dependencies['react']) framework = 'react'; else if (dependencies['vue']) framework = 'vue'; else if (dependencies['@angular/core']) framework = 'angular'; else if (dependencies['svelte']) framework = 'svelte'; // Detect language const language = dependencies['typescript'] ? 'typescript' : 'javascript'; // Detect test frameworks const testing = { hasJest: !!(dependencies['jest'] || dependencies['@jest/core']), hasVitest: !!dependencies['vitest'], hasPlaywright: !!dependencies['@playwright/test'], hasCypress: !!dependencies['cypress'], hasTestingLibrary: !!(dependencies['@testing-library/react'] || dependencies['@testing-library/vue']) }; // Count files const srcPath = path.join(projectPath, 'src'); let fileStats = { total: 0, tests: 0, components: 0, pages: 0 }; try { const files = await findFiles(srcPath); fileStats.total = files.length; fileStats.tests = files.filter(f => f.includes('.test.') || f.includes('.spec.') || f.includes('__tests__')).length; fileStats.components = files.filter(f => f.includes('/component') || /[A-Z]/.test(path.basename(f)[0])).length; fileStats.pages = files.filter(f => f.includes('/pages/') || f.includes('/routes/')).length; } catch (error) { console.error('[VineGuard] Could not analyze src directory'); } // Generate recommendations const recommendations = []; if (!testing.hasJest && !testing.hasVitest) { recommendations.push({ type: 'setup', message: 'Add a unit testing framework (Jest or Vitest recommended)', priority: 'high' }); } if (!testing.hasPlaywright && !testing.hasCypress) { recommendations.push({ type: 'setup', message: 'Consider adding E2E testing with Playwright or Cypress', priority: 'medium' }); } if (fileStats.tests