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
JavaScript
/**
* 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