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

490 lines 19.2 kB
/** * Workflow detection for projects with/without tests * Provides smart recommendations and step-by-step guidance */ import * as path from 'path'; import * as fs from 'fs/promises'; import { TestOrganizer } from './test-organization.js'; export class WorkflowDetector { projectRoot; testOrganizer; constructor(projectRoot) { this.projectRoot = projectRoot; this.testOrganizer = new TestOrganizer(projectRoot); } /** * Analyze project and generate workflow recommendations */ async analyzeProject() { const structure = await this.testOrganizer.analyzeProjectStructure(); const projectType = await this.determineProjectType(structure); const testCoverage = await this.estimateTestCoverage(structure); const testQuality = await this.assessTestQuality(structure); const frameworks = await this.detectFrameworks(); const architecture = await this.detectArchitecture(); const complexity = await this.assessComplexity(); const recommendations = await this.generateRecommendations({ projectType, hasTests: structure.hasTests, testCoverage, testQuality, frameworks, architecture, complexity, structure }); const nextPhase = this.determineNextPhase(recommendations); return { projectType, hasTests: structure.hasTests, testCoverage, testQuality, frameworks, architecture, complexity, recommendations, nextPhase }; } /** * Generate step-by-step workflow for greenfield projects (no tests) */ async generateGreenfieldWorkflow() { return [ { phase: 'discovery', priority: 'critical', action: 'Analyze project structure and identify testable components', description: 'Scan codebase to understand architecture, component types, and business logic', estimatedTime: '30 minutes', tools: ['scan_project', 'analyze_code'], nextSteps: [ 'Identify React components, utility functions, and API endpoints', 'Categorize components by complexity and business importance', 'Map out user flows and critical paths' ] }, { phase: 'foundation', priority: 'critical', action: 'Set up testing infrastructure', description: 'Install and configure testing frameworks with proper project structure', estimatedTime: '45 minutes', tools: ['setup_testing'], nextSteps: [ 'Install Jest and React Testing Library for unit tests', 'Install Playwright for E2E tests', 'Create test directory structure', 'Set up test utilities and custom render functions', 'Configure test scripts in package.json' ] }, { phase: 'coverage', priority: 'high', action: 'Generate tests for critical components', description: 'Create comprehensive tests starting with most important business logic', estimatedTime: '2-4 hours', tools: ['generate_test', 'test_component'], nextSteps: [ 'Start with authentication and core business logic', 'Add tests for main user interface components', 'Create API endpoint tests', 'Generate E2E tests for critical user journeys' ] }, { phase: 'quality', priority: 'medium', action: 'Enhance test quality and coverage', description: 'Add edge cases, accessibility tests, and improve test patterns', estimatedTime: '1-2 hours', tools: ['analyze_code', 'security_audit', 'test_component'], nextSteps: [ 'Add accessibility tests for all interactive components', 'Create error boundary and edge case tests', 'Add visual regression tests for key components', 'Implement performance tests for critical paths' ] }, { phase: 'optimization', priority: 'low', action: 'Optimize test execution and maintenance', description: 'Improve test performance, reduce flakiness, and add advanced patterns', estimatedTime: '1 hour', tools: ['run_tests'], nextSteps: [ 'Set up parallel test execution', 'Add test data factories and fixtures', 'Configure CI/CD pipeline integration', 'Set up test result reporting and metrics' ] } ]; } /** * Generate step-by-step workflow for brownfield projects (has some tests) */ async generateBrownfieldWorkflow(structure) { const testQuality = await this.assessTestQuality(structure); const coverageGaps = structure.coverageGaps; const recommendations = []; // Phase 1: Audit existing tests recommendations.push({ phase: 'discovery', priority: 'high', action: 'Audit existing test quality and coverage', description: 'Analyze current tests for quality, coverage gaps, and improvement opportunities', estimatedTime: '30 minutes', tools: ['analyze_code', 'run_tests'], nextSteps: [ 'Run existing test suite and analyze results', 'Identify flaky or slow tests', 'Review test patterns and quality', 'Generate coverage report' ] }); // Phase 2: Improve existing tests (if quality is poor) if (testQuality === 'poor' || testQuality === 'fair') { recommendations.push({ phase: 'quality', priority: 'high', action: 'Improve existing test quality', description: 'Refactor existing tests to follow best practices and reduce maintenance burden', estimatedTime: '1-3 hours', tools: ['analyze_code', 'generate_test'], nextSteps: [ 'Remove duplicate or redundant tests', 'Add missing assertions and improve test clarity', 'Fix flaky tests and improve reliability', 'Update test data and mocking patterns' ] }); } // Phase 3: Fill coverage gaps if (coverageGaps.length > 0) { recommendations.push({ phase: 'coverage', priority: 'high', action: 'Fill critical coverage gaps', description: `Add tests for ${coverageGaps.length} uncovered files, prioritizing business-critical components`, estimatedTime: `${Math.ceil(coverageGaps.length / 10)} hours`, tools: ['generate_test', 'test_component'], nextSteps: [ 'Prioritize files by business importance', 'Generate tests for authentication and payment logic', 'Add tests for complex UI components', 'Create integration tests for API endpoints' ] }); } // Phase 4: Add missing test types const hasE2E = structure.frameworks.includes('cypress') || structure.frameworks.includes('playwright'); if (!hasE2E) { recommendations.push({ phase: 'foundation', priority: 'medium', action: 'Add E2E testing capability', description: 'Set up end-to-end testing to cover complete user workflows', estimatedTime: '1 hour', tools: ['setup_testing'], nextSteps: [ 'Install Playwright or Cypress', 'Create E2E test structure', 'Generate tests for critical user journeys', 'Set up CI/CD integration' ] }); } // Phase 5: Advanced testing recommendations.push({ phase: 'quality', priority: 'medium', action: 'Add advanced testing patterns', description: 'Implement accessibility, visual, and performance testing', estimatedTime: '2 hours', tools: ['test_component', 'security_audit'], nextSteps: [ 'Add accessibility tests with axe-core', 'Set up visual regression testing', 'Create performance benchmarks', 'Add security vulnerability tests' ] }); return recommendations; } /** * Determine project type based on test structure */ async determineProjectType(structure) { if (!structure.hasTests) { return 'greenfield'; } const testQuality = await this.assessTestQuality(structure); const coverageGaps = structure.coverageGaps.length; const totalFiles = await this.countSourceFiles(); // If test quality is poor and coverage is low, consider it legacy if (testQuality === 'poor' && coverageGaps > totalFiles * 0.7) { return 'legacy'; } return 'brownfield'; } /** * Estimate test coverage percentage */ async estimateTestCoverage(structure) { if (!structure.hasTests) { return 0; } const totalFiles = await this.countSourceFiles(); const uncoveredFiles = structure.coverageGaps.length; if (totalFiles === 0) return 0; const coveredFiles = totalFiles - uncoveredFiles; return Math.round((coveredFiles / totalFiles) * 100); } /** * Assess the quality of existing tests */ async assessTestQuality(structure) { if (!structure.hasTests) { return 'none'; } let qualityScore = 0; let totalChecks = 0; // Check for modern testing practices const hasJest = structure.frameworks.includes('jest'); const hasTestingLibrary = await this.hasTestingLibrary(); const hasE2E = structure.frameworks.includes('cypress') || structure.frameworks.includes('playwright'); if (hasJest) qualityScore += 20; if (hasTestingLibrary) qualityScore += 20; if (hasE2E) qualityScore += 20; totalChecks += 60; // Check test organization if (structure.testPattern === 'separate') qualityScore += 10; else if (structure.testPattern === 'mixed') qualityScore += 5; totalChecks += 10; // Check for test configuration const hasJestConfig = await this.hasFile('jest.config.js') || await this.hasFile('jest.config.ts'); const hasTestSetup = await this.hasFile('src/setupTests.js') || await this.hasFile('src/setupTests.ts'); if (hasJestConfig) qualityScore += 5; if (hasTestSetup) qualityScore += 5; totalChecks += 10; // Check coverage const coverage = await this.estimateTestCoverage(structure); if (coverage >= 80) qualityScore += 20; else if (coverage >= 60) qualityScore += 15; else if (coverage >= 40) qualityScore += 10; else if (coverage >= 20) qualityScore += 5; totalChecks += 20; const finalScore = (qualityScore / totalChecks) * 100; if (finalScore >= 90) return 'excellent'; if (finalScore >= 75) return 'good'; if (finalScore >= 50) return 'fair'; return 'poor'; } /** * Detect frontend frameworks and libraries */ async detectFrameworks() { const frameworks = []; try { const packageJsonPath = path.join(this.projectRoot, 'package.json'); const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8')); const allDeps = { ...packageJson.dependencies, ...packageJson.devDependencies }; if (allDeps.react) frameworks.push('react'); if (allDeps.vue) frameworks.push('vue'); if (allDeps['@angular/core']) frameworks.push('angular'); if (allDeps.svelte) frameworks.push('svelte'); if (allDeps.next) frameworks.push('nextjs'); if (allDeps.nuxt) frameworks.push('nuxt'); if (allDeps.astro) frameworks.push('astro'); if (allDeps.express) frameworks.push('express'); if (allDeps.fastify) frameworks.push('fastify'); } catch (error) { // package.json not found or invalid } return frameworks; } /** * Detect project architecture */ async detectArchitecture() { // Check for workspace configuration (indicates potential microservices/microfrontends) const hasWorkspaces = await this.hasFile('lerna.json') || await this.hasFile('rush.json') || await this.hasPackageWorkspaces(); if (hasWorkspaces) { const frameworks = await this.detectFrameworks(); if (frameworks.includes('react') || frameworks.includes('vue') || frameworks.includes('angular')) { return 'microfrontends'; } return 'microservices'; } // Check for multiple apps/packages const hasAppsDir = await this.hasDirectory('apps'); const hasPackagesDir = await this.hasDirectory('packages'); if (hasAppsDir || hasPackagesDir) { return 'microservices'; } return 'monolith'; } /** * Assess project complexity */ async assessComplexity() { const fileCount = await this.countSourceFiles(); const hasComplexFrameworks = await this.hasComplexFrameworks(); const hasDatabases = await this.hasDatabaseIntegration(); const hasAuth = await this.hasAuthentication(); let complexityScore = 0; // File count if (fileCount > 200) complexityScore += 3; else if (fileCount > 50) complexityScore += 2; else if (fileCount > 10) complexityScore += 1; // Framework complexity if (hasComplexFrameworks) complexityScore += 2; // Integration complexity if (hasDatabases) complexityScore += 2; if (hasAuth) complexityScore += 1; if (complexityScore >= 6) return 'high'; if (complexityScore >= 3) return 'medium'; return 'low'; } /** * Generate recommendations based on analysis */ async generateRecommendations(analysis) { if (analysis.projectType === 'greenfield') { return this.generateGreenfieldWorkflow(); } else { return this.generateBrownfieldWorkflow(analysis.structure); } } /** * Determine the next phase to focus on */ determineNextPhase(recommendations) { const criticalRecs = recommendations.filter(r => r.priority === 'critical'); if (criticalRecs.length > 0) { return criticalRecs[0].phase; } const highRecs = recommendations.filter(r => r.priority === 'high'); if (highRecs.length > 0) { return highRecs[0].phase; } return recommendations[0]?.phase || 'discovery'; } // Helper methods async countSourceFiles() { const files = await this.findFiles(this.projectRoot, /\.(js|jsx|ts|tsx|vue|svelte)$/, ['node_modules', 'dist', 'build', '__tests__', 'test', 'tests', 'cypress', 'playwright']); return files.length; } async hasTestingLibrary() { return this.hasFile('package.json', /@testing-library/); } async hasFile(fileName, contentPattern) { try { const filePath = path.join(this.projectRoot, fileName); const exists = await fs.access(filePath).then(() => true).catch(() => false); if (!exists || !contentPattern) return exists; const content = await fs.readFile(filePath, 'utf-8'); return contentPattern.test(content); } catch { return false; } } async hasDirectory(dirName) { try { const dirPath = path.join(this.projectRoot, dirName); const stat = await fs.stat(dirPath); return stat.isDirectory(); } catch { return false; } } async hasPackageWorkspaces() { try { const packageJsonPath = path.join(this.projectRoot, 'package.json'); const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf-8')); return !!packageJson.workspaces; } catch { return false; } } async hasComplexFrameworks() { const frameworks = await this.detectFrameworks(); const complexFrameworks = ['nextjs', 'nuxt', 'angular', 'astro']; return frameworks.some(f => complexFrameworks.includes(f)); } async hasDatabaseIntegration() { return this.hasFile('package.json', /(prisma|mongoose|sequelize|typeorm|drizzle)/); } async hasAuthentication() { return this.hasFile('package.json', /(next-auth|auth0|passport|firebase\/auth)/); } async findFiles(dir, pattern, excludeDirs = ['node_modules', '.git', 'dist', 'build']) { const files = []; try { const items = await fs.readdir(dir); for (const item of items) { if (excludeDirs.includes(item) || item.startsWith('.')) continue; const fullPath = path.join(dir, item); const stat = await fs.stat(fullPath); if (stat.isDirectory()) { files.push(...await this.findFiles(fullPath, pattern, excludeDirs)); } else if (pattern.test(item)) { files.push(fullPath); } } } catch (error) { // Directory not accessible } return files; } } //# sourceMappingURL=workflow-detector.js.map