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
JavaScript
#!/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