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

513 lines (488 loc) 19 kB
/** * PRD Generator for VineGuard * Automatically generates Product Requirements Documents from codebase analysis */ import fs from 'fs/promises'; import path from 'path'; export class PRDGenerator { projectRoot; projectType = 'unknown'; constructor(projectRoot) { this.projectRoot = projectRoot; } /** * Generate complete PRD from codebase analysis */ async generatePRD(options) { const { includeUserStories = true, analyzeBusinessLogic = true, identifyTestingImplications = true, projectType } = options || {}; this.projectType = projectType || await this.detectProjectType(); const prd = { projectName: await this.getProjectName(), version: '1.0.0', generatedAt: new Date().toISOString(), overview: await this.generateOverview(), userStories: includeUserStories ? await this.extractUserStories() : [], functionalRequirements: await this.analyzeFunctionalRequirements(), nonFunctionalRequirements: await this.identifyNonFunctionalRequirements(), businessLogicMap: analyzeBusinessLogic ? await this.mapBusinessLogic() : [], testingImplications: identifyTestingImplications ? await this.analyzeTestingImplications() : { criticalPaths: [], riskAreas: [], coveragePriority: [] } }; return prd; } /** * Generate project overview */ async generateOverview() { const packageJsonPath = path.join(this.projectRoot, 'package.json'); let description = 'A software application'; let name = 'Application'; try { const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')); description = packageJson.description || description; name = packageJson.name || name; } catch (error) { // Use defaults } const readmePath = path.join(this.projectRoot, 'README.md'); let readmeContent = ''; try { readmeContent = await fs.readFile(readmePath, 'utf8'); } catch (error) { // No README found } return { purpose: this.extractPurpose(description, readmeContent), scope: this.defineScope(), objectives: await this.identifyObjectives(readmeContent) }; } /** * Extract user stories from code analysis */ async extractUserStories() { const stories = []; const components = await this.analyzeComponents(); for (const component of components) { const componentStories = await this.generateStoriesForComponent(component); stories.push(...componentStories); } // Add generic user stories based on project type stories.push(...this.getGenericUserStories()); return stories; } /** * Analyze functional requirements from codebase */ async analyzeFunctionalRequirements() { const requirements = []; // Analyze API routes/endpoints const apiRequirements = await this.analyzeApiRequirements(); requirements.push(...apiRequirements); // Analyze UI components const uiRequirements = await this.analyzeUIRequirements(); requirements.push(...uiRequirements); // Analyze business logic const businessRequirements = await this.analyzeBusinessRequirements(); requirements.push(...businessRequirements); return requirements; } /** * Identify non-functional requirements */ async identifyNonFunctionalRequirements() { const requirements = []; // Performance requirements requirements.push({ id: 'NFR-001', type: 'performance', description: 'Application should load within acceptable time limits', metrics: ['Page load time < 3 seconds', 'API response time < 500ms'], testStrategy: 'Automated performance testing with Playwright' }); // Security requirements requirements.push({ id: 'NFR-002', type: 'security', description: 'Application should handle user data securely', metrics: ['Input validation', 'Authentication/Authorization', 'Data encryption'], testStrategy: 'Security testing with automated vulnerability scanning' }); // Usability requirements requirements.push({ id: 'NFR-003', type: 'usability', description: 'Application should be accessible to all users', metrics: ['WCAG 2.1 AA compliance', 'Keyboard navigation support'], testStrategy: 'Automated accessibility testing with axe-core' }); return requirements; } /** * Map business logic across components */ async mapBusinessLogic() { const businessLogicMap = []; const components = await this.analyzeComponents(); for (const component of components) { businessLogicMap.push({ component: component.name, responsibilities: await this.identifyResponsibilities(component), interactions: await this.identifyInteractions(component) }); } return businessLogicMap; } /** * Analyze testing implications */ async analyzeTestingImplications() { return { criticalPaths: await this.identifyCriticalPaths(), riskAreas: await this.identifyRiskAreas(), coveragePriority: await this.prioritizeTestCoverage() }; } // Helper methods async detectProjectType() { try { const packageJsonPath = path.join(this.projectRoot, 'package.json'); const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')); 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 'next'; if (deps.express || deps.fastify) return 'api'; return 'web'; } catch (error) { return 'unknown'; } } async getProjectName() { try { const packageJsonPath = path.join(this.projectRoot, 'package.json'); const packageJson = JSON.parse(await fs.readFile(packageJsonPath, 'utf8')); return packageJson.name || 'Unnamed Project'; } catch (error) { return 'Unnamed Project'; } } extractPurpose(description, readmeContent) { if (readmeContent.includes('## Purpose') || readmeContent.includes('# Purpose')) { const match = readmeContent.match(/#{1,2}\s*Purpose\s*\n(.*?)(?=\n#{1,2}|$)/s); if (match) return match[1].trim(); } if (description && description.length > 20) { return description; } return `This ${this.projectType} application provides functionality to users through a web interface.`; } defineScope() { switch (this.projectType) { case 'react': return 'Frontend React application with component-based architecture'; case 'vue': return 'Frontend Vue.js application with reactive components'; case 'angular': return 'Frontend Angular application with TypeScript and services'; case 'next': return 'Full-stack Next.js application with SSR capabilities'; case 'api': return 'Backend API service with RESTful endpoints'; default: return 'Web application providing user functionality'; } } async identifyObjectives(readmeContent) { const objectives = []; // Extract from README if available if (readmeContent.includes('## Objectives') || readmeContent.includes('## Goals')) { const match = readmeContent.match(/#{1,2}\s*(?:Objectives|Goals)\s*\n(.*?)(?=\n#{1,2}|$)/s); if (match) { const section = match[1]; const lines = section.split('\n') .filter(line => line.trim().startsWith('-') || line.trim().startsWith('*')) .map(line => line.replace(/^[\s\-\*]+/, '').trim()) .filter(line => line.length > 0); objectives.push(...lines); } } // Default objectives based on project type if (objectives.length === 0) { switch (this.projectType) { case 'react': objectives.push('Provide responsive user interface', 'Ensure component reusability', 'Maintain high performance'); break; case 'api': objectives.push('Provide reliable API endpoints', 'Ensure data consistency', 'Maintain security standards'); break; default: objectives.push('Deliver high-quality user experience', 'Ensure application reliability', 'Maintain code quality'); } } return objectives; } async analyzeComponents() { const components = []; try { const srcPath = path.join(this.projectRoot, 'src'); await this.findComponents(srcPath, components); } catch (error) { // src directory might not exist } return components; } async findComponents(dir, components) { try { const entries = await fs.readdir(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { await this.findComponents(fullPath, components); } else if (entry.isFile()) { const ext = path.extname(entry.name); const name = path.basename(entry.name, ext); if (['.jsx', '.tsx', '.vue', '.ts', '.js'].includes(ext)) { components.push({ name, path: fullPath, type: this.determineComponentType(fullPath, ext) }); } } } } catch (error) { // Directory access error } } determineComponentType(filePath, ext) { if (filePath.includes('/components/') || filePath.includes('\\components\\')) { return 'component'; } if (filePath.includes('/pages/') || filePath.includes('\\pages\\')) { return 'page'; } if (filePath.includes('/api/') || filePath.includes('\\api\\')) { return 'api'; } if (filePath.includes('/utils/') || filePath.includes('\\utils\\')) { return 'utility'; } if (filePath.includes('/services/') || filePath.includes('\\services\\')) { return 'service'; } return 'module'; } async generateStoriesForComponent(component) { const stories = []; switch (component.type) { case 'component': stories.push({ id: `US-${component.name.toUpperCase()}-001`, title: `User interacts with ${component.name}`, description: `As a user, I want to interact with the ${component.name} component so that I can achieve my goals`, acceptanceCriteria: [ `${component.name} renders correctly`, `${component.name} handles user input appropriately`, `${component.name} displays proper feedback` ], priority: 'medium', component: component.name, businessValue: 'Enables user interaction and improves user experience' }); break; case 'api': stories.push({ id: `US-${component.name.toUpperCase()}-001`, title: `System processes ${component.name} requests`, description: `As a client, I want to make requests to ${component.name} so that I can retrieve/modify data`, acceptanceCriteria: [ `${component.name} responds to valid requests`, `${component.name} validates input data`, `${component.name} returns appropriate status codes` ], priority: 'high', component: component.name, businessValue: 'Provides essential data operations and business logic' }); break; } return stories; } getGenericUserStories() { const genericStories = []; switch (this.projectType) { case 'react': case 'vue': case 'angular': genericStories.push({ id: 'US-GEN-001', title: 'User navigates the application', description: 'As a user, I want to navigate through different sections of the application', acceptanceCriteria: [ 'Navigation is intuitive and accessible', 'Page transitions work smoothly', 'Current location is clearly indicated' ], priority: 'high', businessValue: 'Essential for basic application usability' }, { id: 'US-GEN-002', title: 'User receives feedback on actions', description: 'As a user, I want to receive clear feedback when I perform actions', acceptanceCriteria: [ 'Success actions show confirmation', 'Errors display helpful messages', 'Loading states are indicated' ], priority: 'high', businessValue: 'Improves user confidence and reduces confusion' }); break; } return genericStories; } async analyzeApiRequirements() { // Placeholder for API analysis return []; } async analyzeUIRequirements() { // Placeholder for UI analysis return []; } async analyzeBusinessRequirements() { // Placeholder for business logic analysis return []; } async identifyResponsibilities(component) { const responsibilities = []; switch (component.type) { case 'component': responsibilities.push('Render UI elements', 'Handle user interactions', 'Manage local state'); break; case 'service': responsibilities.push('Provide business logic', 'Handle data operations', 'Interface with external services'); break; case 'api': responsibilities.push('Process HTTP requests', 'Validate input data', 'Return appropriate responses'); break; } return responsibilities; } async identifyInteractions(component) { return [ 'Communicates with parent components', 'Triggers events and callbacks', 'Consumes external data sources' ]; } async identifyCriticalPaths() { return [ 'User authentication flow', 'Core business functionality', 'Data persistence operations', 'External API integrations' ]; } async identifyRiskAreas() { return [ 'User input validation', 'Authentication and authorization', 'Data handling and storage', 'Third-party integrations', 'Error handling and recovery' ]; } async prioritizeTestCoverage() { return [ 'Critical business logic (90%+ coverage)', 'User authentication (100% coverage)', 'Data operations (85%+ coverage)', 'UI components (75%+ coverage)', 'Utility functions (80%+ coverage)' ]; } /** * Export PRD to different formats */ async exportPRD(prd, format = 'json') { switch (format) { case 'json': return JSON.stringify(prd, null, 2); case 'markdown': return this.generateMarkdownPRD(prd); case 'html': return this.generateHTMLPRD(prd); default: return JSON.stringify(prd, null, 2); } } generateMarkdownPRD(prd) { return `# Product Requirements Document: ${prd.projectName} **Version:** ${prd.version} **Generated:** ${new Date(prd.generatedAt).toLocaleDateString()} ## Overview ### Purpose ${prd.overview.purpose} ### Scope ${prd.overview.scope} ### Objectives ${prd.overview.objectives.map(obj => `- ${obj}`).join('\n')} ## User Stories ${prd.userStories.map(story => ` ### ${story.title} (${story.id}) **Priority:** ${story.priority} ${story.description} **Acceptance Criteria:** ${story.acceptanceCriteria.map(criteria => `- ${criteria}`).join('\n')} **Business Value:** ${story.businessValue} `).join('\n')} ## Functional Requirements ${prd.functionalRequirements.map(req => ` ### ${req.id}: ${req.category} ${req.description} **Business Logic:** ${req.businessLogic.map(logic => `- ${logic}`).join('\n')} **Dependencies:** ${req.dependencies.join(', ')} **Testable:** ${req.testable ? 'Yes' : 'No'} `).join('\n')} ## Non-Functional Requirements ${prd.nonFunctionalRequirements.map(req => ` ### ${req.id}: ${req.type} ${req.description} **Metrics:** ${req.metrics.map(metric => `- ${metric}`).join('\n')} **Test Strategy:** ${req.testStrategy} `).join('\n')} ## Testing Implications ### Critical Paths ${prd.testingImplications.criticalPaths.map(path => `- ${path}`).join('\n')} ### Risk Areas ${prd.testingImplications.riskAreas.map(risk => `- ${risk}`).join('\n')} ### Coverage Priority ${prd.testingImplications.coveragePriority.map(priority => `- ${priority}`).join('\n')} `; } generateHTMLPRD(prd) { // Placeholder for HTML generation return `<html><body><h1>PRD: ${prd.projectName}</h1><p>Generated: ${prd.generatedAt}</p></body></html>`; } } //# sourceMappingURL=prd-generator.js.map