veriqa
Version:
๐ฏ Smart Manual QA Test Advisor with AI-Powered Regression Suggestions and Advanced Git Integration
909 lines (787 loc) โข 34 kB
JavaScript
#!/usr/bin/env node
/**
* VeriQA v3.0 - Smart Manual QA Test Advisor
* Fresh, Modern, AI-Powered Regression Testing Tool
* ๐ฎ๐ณ Proudly Made in India ๐ฎ๐ณ
*/
const { Command } = require('commander');
const chalk = require('chalk');
const path = require('path');
const fs = require('fs');
const ora = require('ora');
const gradient = require('gradient-string');
const figlet = require('figlet');
const boxen = require('boxen');
// Import modules
const ProjectScanner = require('./core/ProjectScanner');
const GitAnalyzer = require('./core/GitAnalyzer');
const AIService = require('./core/AIService');
const ReportGenerator = require('./core/ReportGenerator');
const ConfigManager = require('./utils/ConfigManager');
const UpdateChecker = require('./utils/UpdateChecker');
const SmartEnvManager = require('./utils/SimplifiedEnvManager');
class VeriQA {
constructor() {
this.configPath = path.join(process.cwd(), '.veriqa.json');
this.configManager = new ConfigManager(this.configPath);
this.updateChecker = new UpdateChecker();
}
// ๐ฏ Phase 1: Project Setup & Scan
async init() {
// Check for updates (non-blocking)
setTimeout(() => this.updateChecker.checkForUpdates(), 1000);
// Check for old version migration
this.updateChecker.detectOldVersion();
this.updateChecker.migrateOldConfig();
// Clear console for fresh start
console.clear();
// Beautiful Animated ASCII Header
const veriQATitle = figlet.textSync('VeriQA', { font: 'Speed' });
console.log(gradient(['#00f5ff', '#fc00ff'])(veriQATitle));
// Animated subtitle
const subtitle = 'โจ Smart Manual QA Test Advisor โจ';
console.log(gradient(['#ff6b6b', '#4ecdc4'])(subtitle.padStart((veriQATitle.split('\n')[0].length + subtitle.length) / 2)));
// Made in India Header with enhanced styling
const madeInIndiaBox = boxen(
chalk.white.bold('๐ฎ๐ณ PROUDLY MADE IN INDIA ๐ฎ๐ณ\n') +
chalk.yellow('Supporting Indian Innovation in AI & Testing') +
chalk.white('\n๐ Advanced AI-Powered Testing ๐'),
{
padding: 1,
margin: 1,
borderStyle: 'double',
borderColor: 'cyan',
backgroundColor: 'blue',
textAlignment: 'center'
}
);
console.log(madeInIndiaBox);
// Project Initialization Box with gradient
const initBox = boxen(
gradient(['#ffeaa7', '#fab1a0'])('๐ง INITIALIZING PROJECT SCAN') +
chalk.white('\nโก Scanning for testing frameworks...') +
chalk.gray('\n๐ Analyzing project structure...'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'yellow',
textAlignment: 'center'
}
);
console.log(initBox);
const spinner = ora({
text: chalk.cyan('๐ Scanning project structure...'),
spinner: 'dots12',
color: 'cyan'
}).start();
try {
// Setup directories
spinner.text = chalk.cyan('๐ Creating directories...');
await this.setupDirectories();
// Scan project
spinner.text = chalk.cyan('๐งช Detecting testing frameworks...');
const scanner = new ProjectScanner();
const projectData = await scanner.scan();
// Setup AI if not configured
spinner.text = chalk.cyan('๐ค Setting up AI configuration...');
await this.setupAI();
// Save configuration
spinner.text = chalk.cyan('๐พ Saving configuration...');
await this.configManager.save({
...projectData,
setupDate: new Date().toISOString(),
version: '3.0.2'
});
spinner.succeed(chalk.green.bold('โ
Project initialized successfully!'));
// Enhanced Project Summary with animations
const summaryBox = boxen(
chalk.white.bold('๐ PROJECT SUMMARY\n\n') +
chalk.cyan('๐ Project: ') + chalk.white.bold(projectData.name) + '\n' +
chalk.cyan('๐งช Frameworks: ') + chalk.yellow.bold(projectData.frameworks.join(', ')) + '\n' +
chalk.cyan('๐ Test Files: ') + chalk.green.bold(projectData.testFiles.length) + '\n' +
chalk.cyan('๐ Source Files: ') + chalk.blue.bold(projectData.sourceFiles.length) + '\n' +
chalk.cyan('โฐ Setup Date: ') + chalk.magenta.bold(new Date().toLocaleDateString()),
{
padding: 1,
margin: 1,
borderStyle: 'classic',
borderColor: 'green',
backgroundColor: 'black',
textAlignment: 'left'
}
);
console.log(summaryBox);
// Next Steps with enhanced styling
const nextStepsBox = boxen(
gradient(['#a29bfe', '#fd79a8'])('๐ NEXT STEPS') + '\n\n' +
chalk.white('Run: ') + chalk.green.bold('veriqa analyze <old-commit> <new-commit>') + '\n' +
chalk.gray(' โโ Compare commits and get AI suggestions') + '\n\n' +
chalk.white('Then: ') + chalk.blue.bold('veriqa report') + '\n' +
chalk.gray(' โโ Generate beautiful HTML/PDF reports'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'magenta',
textAlignment: 'left'
}
);
console.log(nextStepsBox);
} catch (error) {
spinner.fail(chalk.red('โ Initialization failed'));
console.error(chalk.red.bold('Error:'), error.message);
process.exit(1);
}
}
// ๐ Phase 2: Git Analysis & AI Suggestions
async analyze(oldCommit, newCommit) {
console.clear();
// Beautiful header for analysis
const analysisTitle = figlet.textSync('ANALYSIS', { font: 'Small' });
console.log(gradient(['#74b9ff', '#0984e3'])(analysisTitle));
const analysisBox = boxen(
gradient(['#fdcb6e', '#e17055'])('๐ ANALYZING COMMITS FOR REGRESSION') + '\n' +
chalk.white('๐ Git Diff Analysis + AI Powered Suggestions') + '\n' +
chalk.gray('๐ค Using Google Gemini AI for smart recommendations'),
{
padding: 1,
margin: 1,
borderStyle: 'double',
borderColor: 'blue',
textAlignment: 'center'
}
);
console.log(analysisBox);
if (!oldCommit || !newCommit) {
const errorBox = boxen(
chalk.red.bold('โ MISSING COMMIT IDs') + '\n\n' +
chalk.white('Usage: ') + chalk.green.bold('veriqa analyze <old-commit> <new-commit>') + '\n\n' +
chalk.yellow('Example:') + '\n' +
chalk.gray('veriqa analyze abc123 def456'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'red',
textAlignment: 'left'
}
);
console.log(errorBox);
process.exit(1);
}
const analysisSpinner = ora({
text: chalk.cyan('๐ Initializing git analysis...'),
spinner: 'aesthetic',
color: 'cyan'
}).start();
try {
const config = await this.configManager.load();
// Git analysis with enhanced progress
analysisSpinner.text = chalk.blue('๐ Analyzing git differences...');
const gitAnalyzer = new GitAnalyzer();
const changes = await gitAnalyzer.analyzeCommits(oldCommit, newCommit);
// Get code metrics (like git diff --stat)
analysisSpinner.text = chalk.cyan('๐ Calculating code metrics...');
const codeMetrics = await gitAnalyzer.getCodeMetrics(oldCommit, newCommit);
analysisSpinner.text = chalk.magenta('๐ค Generating AI suggestions...');
// AI-powered suggestions
const aiService = new AIService();
const suggestions = await aiService.generateSuggestions(changes, config);
analysisSpinner.text = chalk.green('๐พ Saving analysis results...');
// Save analysis
const analysis = {
oldCommit,
newCommit,
changes,
codeMetrics,
suggestions,
analysisDate: new Date().toISOString()
};
await this.configManager.saveAnalysis(analysis);
analysisSpinner.succeed(chalk.green.bold('โ
Analysis completed successfully!'));
// Enhanced Results Display
const resultsBox = boxen(
chalk.white.bold('๐ ANALYSIS RESULTS\n\n') +
chalk.blue('๐ Commits Compared: ') + chalk.yellow.bold(`${oldCommit.substring(0,7)} โ ${newCommit.substring(0,7)}`) + '\n' +
chalk.blue('๐ Files Changed: ') + chalk.green.bold(codeMetrics.filesChanged) + '\n' +
chalk.blue('๐ Insertions: ') + chalk.green.bold(`+${codeMetrics.totalInsertions}`) + '\n' +
chalk.blue('๐ Deletions: ') + chalk.red.bold(`-${codeMetrics.totalDeletions}`) + '\n' +
chalk.blue('๐ค AI Suggestions: ') + chalk.magenta.bold(suggestions.length) + '\n' +
chalk.blue('โฐ Analysis Date: ') + chalk.cyan.bold(new Date().toLocaleString()) + '\n\n' +
chalk.gray('๐ฏ Regression test suggestions ready!'),
{
padding: 1,
margin: 1,
borderStyle: 'classic',
borderColor: 'green',
backgroundColor: 'black',
textAlignment: 'left'
}
);
console.log(resultsBox);
// Display code metrics like git diff --stat
gitAnalyzer.displayCodeMetrics(codeMetrics);
// Top Suggestions Preview
if (suggestions.length > 0) {
const topSuggestions = suggestions.slice(0, 3);
const previewBox = boxen(
chalk.white.bold('๐ฏ TOP REGRESSION TEST SUGGESTIONS\n\n') +
topSuggestions.map((s, i) =>
chalk.yellow(`${i + 1}. `) + chalk.white(s.title) + '\n' +
chalk.gray(` ${s.description.substring(0, 60)}...`)
).join('\n\n'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'yellow',
textAlignment: 'left'
}
);
console.log(previewBox);
}
// Next Steps
const nextStepsBox = boxen(
gradient(['#a29bfe', '#fd79a8'])('๐ NEXT STEPS') + '\n\n' +
chalk.white('Generate Reports: ') + chalk.green.bold('veriqa report') + '\n' +
chalk.gray(' โโ Create HTML, PDF, and JSON reports') + '\n\n' +
chalk.white('View Results: ') + chalk.blue.bold('./veriqa-reports/') + '\n' +
chalk.gray(' โโ Access detailed analysis and suggestions'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'magenta',
textAlignment: 'left'
}
);
console.log(nextStepsBox);
} catch (error) {
analysisSpinner.fail(chalk.red('โ Analysis failed'));
console.error(chalk.red.bold('Error:'), error.message);
process.exit(1);
}
}
// ๐ Phase 3: Generate Beautiful Report
async report() {
console.clear();
// Beautiful header for reports
const reportTitle = figlet.textSync('REPORTS', { font: 'Small' });
console.log(gradient(['#00cec9', '#55a3ff'])(reportTitle));
const reportBox = boxen(
gradient(['#fd79a8', '#fdcb6e'])('๐ GENERATING COMPREHENSIVE REPORTS') + '\n' +
chalk.white('๐จ Beautiful HTML Reports with Charts') + '\n' +
chalk.white('๐ Professional PDF Documents') + '\n' +
chalk.white('๐ Structured JSON Data Export'),
{
padding: 1,
margin: 1,
borderStyle: 'double',
borderColor: 'cyan',
textAlignment: 'center'
}
);
console.log(reportBox);
const reportSpinner = ora({
text: chalk.cyan('๐ Loading analysis data...'),
spinner: 'bouncingBar',
color: 'cyan'
}).start();
try {
const analysis = await this.configManager.loadLatestAnalysis();
if (!analysis) {
reportSpinner.fail(chalk.red('โ No analysis found'));
const noAnalysisBox = boxen(
chalk.red.bold('โ NO ANALYSIS DATA FOUND') + '\n\n' +
chalk.white('Please run analysis first:') + '\n' +
chalk.green.bold('veriqa analyze <old-commit> <new-commit>') + '\n\n' +
chalk.gray('Then generate reports with:') + '\n' +
chalk.blue.bold('veriqa report'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'red',
textAlignment: 'left'
}
);
console.log(noAnalysisBox);
process.exit(1);
}
reportSpinner.text = chalk.magenta('๐จ Generating HTML report...');
const reportGenerator = new ReportGenerator();
// Simulate report generation steps with detailed progress
reportSpinner.text = chalk.blue('๐ Creating PDF document...');
await new Promise(resolve => setTimeout(resolve, 1000)); // Simulate work
reportSpinner.text = chalk.yellow('๐ Exporting JSON data...');
await new Promise(resolve => setTimeout(resolve, 800)); // Simulate work
reportSpinner.text = chalk.green('๐ฏ Finalizing reports...');
const reports = await reportGenerator.generate(analysis);
reportSpinner.succeed(chalk.green.bold('๐ All reports generated successfully!'));
// Enhanced Report Summary
const reportSummaryBox = boxen(
chalk.white.bold('๐ GENERATED REPORTS\n\n') +
chalk.green('โ
HTML Report: ') + chalk.cyan.bold('Ready') + '\n' +
chalk.gray(' โโ Interactive charts and detailed analysis') + '\n\n' +
chalk.green('โ
PDF Report: ') + chalk.cyan.bold(reports.pdf ? 'Ready' : 'Failed') + '\n' +
chalk.gray(' โโ Professional document for sharing') + '\n\n' +
chalk.green('โ
JSON Data: ') + chalk.cyan.bold('Ready') + '\n' +
chalk.gray(' โโ Structured data for integration') + '\n\n' +
chalk.yellow('๐ Analysis Summary:') + '\n' +
chalk.gray(` โโ ${(analysis.suggestions || analysis.comparison?.suggestions || []).length} AI suggestions generated`) + '\n' +
chalk.gray(` โโ ${(analysis.changes?.files || analysis.comparison?.files || []).length} files analyzed`),
{
padding: 1,
margin: 1,
borderStyle: 'classic',
borderColor: 'green',
backgroundColor: 'black',
textAlignment: 'left'
}
);
console.log(reportSummaryBox);
// File Locations Box
const locationsBox = boxen(
chalk.white.bold('๐ REPORT LOCATIONS\n\n') +
chalk.blue('HTML: ') + chalk.gray(reports.html) + '\n' +
(reports.pdf ? chalk.blue('PDF: ') + chalk.gray(reports.pdf) + '\n' : '') +
chalk.blue('JSON: ') + chalk.gray(reports.json) + '\n\n' +
chalk.yellow('๐ก Tip: ') + chalk.white('Open HTML report in browser for best experience'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'blue',
textAlignment: 'left'
}
);
console.log(locationsBox);
// Success message with celebration
console.log('\n' + gradient(['#00b894', '#00cec9'])('๐ Reports are ready! Share your analysis with the team! ๐'));
} catch (error) {
reportSpinner.fail(chalk.red('โ Report generation failed'));
console.error(chalk.red.bold('Error:'), error.message);
process.exit(1);
}
}
// ๐ Update VeriQA to latest version
async update() {
console.log('\n' + chalk.yellow.bold('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
console.log(chalk.yellow.bold('โ') + chalk.cyan.bold(' ๐ CHECKING FOR VERIQA UPDATES ') + chalk.yellow.bold('โ'));
console.log(chalk.yellow.bold('โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ'));
console.log('');
try {
const hasUpdate = await this.updateChecker.checkForUpdates();
if (hasUpdate) {
console.log(chalk.cyan('๐ Proceeding with auto-upgrade...'));
await this.updateChecker.autoUpgrade();
} else {
console.log(chalk.green('โ
You are already on the latest version!'));
console.log(chalk.blue(`Current version: v${require('../package.json').version}`));
}
} catch (error) {
console.error(chalk.red('โ Update check failed:'), error.message);
process.exit(1);
}
}
// ๐ฟ NEW: Git Status with Enhanced Information
async status() {
console.clear();
const statusTitle = figlet.textSync('STATUS', { font: 'Small' });
console.log(gradient(['#00b894', '#00cec9'])(statusTitle));
const statusSpinner = ora({
text: chalk.cyan('๐ Getting project status...'),
spinner: 'dots',
color: 'cyan'
}).start();
try {
const gitAnalyzer = new GitAnalyzer();
const projectStatus = await gitAnalyzer.getProjectStatus();
statusSpinner.succeed(chalk.green.bold('โ
Project status retrieved!'));
// Git Status Box
const gitStatusBox = boxen(
chalk.white.bold('๐ฟ GIT STATUS\n\n') +
chalk.cyan('Current Branch: ') + chalk.yellow.bold(projectStatus.currentBranch) + '\n' +
chalk.cyan('Repository: ') + chalk.white.bold(projectStatus.isClean ? 'โ
Clean' : 'โ ๏ธ Has Changes') + '\n' +
chalk.cyan('Ahead: ') + chalk.green.bold(projectStatus.ahead || 0) + ' ' +
chalk.cyan('Behind: ') + chalk.red.bold(projectStatus.behind || 0) + '\n' +
chalk.cyan('Staged: ') + chalk.blue.bold(projectStatus.staged || 0) + ' ' +
chalk.cyan('Modified: ') + chalk.yellow.bold(projectStatus.modified || 0) + ' ' +
chalk.cyan('Untracked: ') + chalk.gray.bold(projectStatus.untracked || 0) + '\n' +
(projectStatus.tracking ? chalk.cyan('Tracking: ') + chalk.white(projectStatus.tracking) : ''),
{
padding: 1,
margin: 1,
borderStyle: 'classic',
borderColor: 'green',
textAlignment: 'left'
}
);
console.log(gitStatusBox);
// Stash Information
if (projectStatus.stash && projectStatus.stash.hasStash) {
const stashBox = boxen(
chalk.white.bold('๐ฆ STASH INFORMATION\n\n') +
chalk.yellow('โ ๏ธ You have ') + chalk.red.bold(projectStatus.stash.stashCount) + chalk.yellow(' stashed changes') + '\n\n' +
chalk.gray('Recent stashes:') + '\n' +
projectStatus.stash.stashes.slice(0, 3).map(stash =>
chalk.gray(` โข ${stash.message}`)
).join('\n') + '\n\n' +
chalk.yellow('๐ก Consider applying stash before analysis'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'yellow',
textAlignment: 'left'
}
);
console.log(stashBox);
}
} catch (error) {
statusSpinner.fail(chalk.red('โ Failed to get status'));
console.error(chalk.red.bold('Error:'), error.message);
}
}
// ๐ฟ NEW: Compare Branches (HOTFIX: Enhanced error handling)
async compareBranches(branch1, branch2) {
console.clear();
const compareTitle = figlet.textSync('COMPARE', { font: 'Small' });
console.log(gradient(['#fd79a8', '#fdcb6e'])(compareTitle));
const compareBox = boxen(
gradient(['#a29bfe', '#6c5ce7'])('๐ BRANCH COMPARISON') + '\n' +
chalk.white(`Comparing ${branch1} vs ${branch2}`) + '\n' +
chalk.gray('๐ฏ Generate regression testing suggestions'),
{
padding: 1,
margin: 1,
borderStyle: 'double',
borderColor: 'magenta',
textAlignment: 'center'
}
);
console.log(compareBox);
try {
const gitAnalyzer = new GitAnalyzer();
const comparison = await gitAnalyzer.compareBranches(branch1, branch2);
// Branch Information
const branchInfoBox = boxen(
chalk.white.bold('๐ฟ BRANCH INFORMATION\n\n') +
chalk.cyan('Branch 1: ') + chalk.yellow.bold(comparison.branch1.name) + '\n' +
chalk.gray(` โโ ${comparison.branch1.lastCommit?.message || 'No commits'}`) + '\n' +
chalk.gray(` โโ Author: ${comparison.branch1.lastCommit?.author || 'Unknown'}`) + '\n\n' +
chalk.cyan('Branch 2: ') + chalk.yellow.bold(comparison.branch2.name) + '\n' +
chalk.gray(` โโ ${comparison.branch2.lastCommit?.message || 'No commits'}`) + '\n' +
chalk.gray(` โโ Author: ${comparison.branch2.lastCommit?.author || 'Unknown'}`) + '\n\n' +
chalk.cyan('Merge Base: ') + chalk.white(comparison.mergeBase?.substring(0, 8) || 'Not found'),
{
padding: 1,
margin: 1,
borderStyle: 'classic',
borderColor: 'blue',
textAlignment: 'left'
}
);
console.log(branchInfoBox);
// Comparison Results
const resultsBox = boxen(
chalk.white.bold('๐ COMPARISON RESULTS\n\n') +
chalk.blue('Files Changed: ') + chalk.green.bold(comparison.comparison.summary.filesChanged) + '\n' +
chalk.blue('Insertions: ') + chalk.green.bold(comparison.comparison.summary.insertions) + '\n' +
chalk.blue('Deletions: ') + chalk.red.bold(comparison.comparison.summary.deletions) + '\n\n' +
chalk.gray('Use these results for targeted regression testing'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green',
textAlignment: 'left'
}
);
console.log(resultsBox);
// Save comparison for report generation
const analysis = {
type: 'branch-comparison',
branch1: comparison.branch1,
branch2: comparison.branch2,
comparison: comparison.comparison,
analysisDate: new Date().toISOString()
};
await this.configManager.saveAnalysis(analysis);
console.log('\n' + gradient(['#00b894', '#00cec9'])('๐ฏ Branch comparison saved! Use "veriqa report" to generate detailed reports.'));
} catch (error) {
// HOTFIX: Enhanced error handling with troubleshooting tips
const errorBox = boxen(
chalk.red.bold('โ BRANCH COMPARISON FAILED\n\n') +
chalk.white('Error: ') + chalk.yellow(error.message) + '\n\n' +
chalk.cyan.bold('๐ง TROUBLESHOOTING TIPS:\n') +
chalk.white('1. Check available branches: ') + chalk.gray('git branch -a') + '\n' +
chalk.white('2. Verify branch names: ') + chalk.gray(`git show ${branch1} && git show ${branch2}`) + '\n' +
chalk.white('3. Try with full refs: ') + chalk.gray(`origin/${branch1} origin/${branch2}`) + '\n' +
chalk.white('4. Ensure branches exist: ') + chalk.gray('git fetch origin') + '\n\n' +
chalk.yellow('๐ก Example: ') + chalk.green.bold('veriqa compare main origin/feature-branch'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'red',
textAlignment: 'left'
}
);
console.log(errorBox);
// Don't exit, just show error
console.log(chalk.gray('\n๐ก Tip: Use "git branch -a" to see all available branches'));
}
}
// ๐ง NEW: Install Pre-commit Hooks
async installHooks() {
console.clear();
const hooksTitle = figlet.textSync('HOOKS', { font: 'Small' });
console.log(gradient(['#e17055', '#fdcb6e'])(hooksTitle));
const hooksSpinner = ora({
text: chalk.cyan('๐ง Installing pre-commit hooks...'),
spinner: 'arrow3',
color: 'yellow'
}).start();
try {
const fs = require('fs').promises;
const path = require('path');
// Check if .git directory exists
const gitDir = path.join(process.cwd(), '.git');
try {
await fs.access(gitDir);
} catch (error) {
hooksSpinner.fail(chalk.red('โ Not a git repository'));
return;
}
// Create hooks directory if it doesn't exist
const hooksDir = path.join(gitDir, 'hooks');
await fs.mkdir(hooksDir, { recursive: true });
// Create pre-commit hook content
const hookContent = `#!/bin/bash
# VeriQA Pre-commit Hook
# Automatically runs VeriQA analysis before commits
echo "๐ฏ VeriQA: Running regression analysis..."
# Get current branch and last commit
CURRENT_BRANCH=$(git branch --show-current)
LAST_COMMIT=$(git rev-parse HEAD~1 2>/dev/null || git rev-parse HEAD)
CURRENT_COMMIT=$(git rev-parse HEAD)
# Check if we have commits to compare
if [ "$LAST_COMMIT" = "$CURRENT_COMMIT" ]; then
echo "๐ VeriQA: First commit detected, skipping analysis"
exit 0
fi
# Run VeriQA analysis quietly
echo "๐ Analyzing changes between $LAST_COMMIT and staged changes..."
veriqa analyze $LAST_COMMIT HEAD --quiet > /dev/null 2>&1
if [ $? -eq 0 ]; then
echo "โ
VeriQA: Analysis completed successfully"
echo "๐ก Run 'veriqa report' to view detailed suggestions"
else
echo "โ ๏ธ VeriQA: Analysis failed, but allowing commit to proceed"
echo "๐ก Run 'veriqa analyze $LAST_COMMIT HEAD' manually to debug"
fi
echo "๐ VeriQA: Pre-commit hook completed"
exit 0
`;
// Write hook file
const hookPath = path.join(hooksDir, 'pre-commit');
await fs.writeFile(hookPath, hookContent);
// Make hook executable
const { execSync } = require('child_process');
execSync(`chmod +x "${hookPath}"`);
hooksSpinner.succeed(chalk.green.bold('โ
Pre-commit hooks installed successfully!'));
// Success Information
const successBox = boxen(
chalk.white.bold('๐ง HOOKS INSTALLED\n\n') +
chalk.green('โ
Pre-commit hook: ') + chalk.cyan('Installed') + '\n' +
chalk.gray(' โโ Runs VeriQA analysis before each commit') + '\n' +
chalk.gray(' โโ Provides regression testing suggestions') + '\n\n' +
chalk.yellow('๐ Hook Location: ') + chalk.white('.git/hooks/pre-commit') + '\n\n' +
chalk.cyan('๐ก Next commit will trigger automatic analysis!'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green',
textAlignment: 'left'
}
);
console.log(successBox);
} catch (error) {
hooksSpinner.fail(chalk.red('โ Hook installation failed'));
console.error(chalk.red.bold('Error:'), error.message);
}
}
// ๐ NEW: Blame Analysis
async blameAnalysis(filePath, lineNumber) {
console.clear();
const blameTitle = figlet.textSync('BLAME', { font: 'Small' });
console.log(gradient(['#ff7675', '#fd79a8'])(blameTitle));
if (!filePath) {
const errorBox = boxen(
chalk.red.bold('โ MISSING FILE PATH') + '\n\n' +
chalk.white('Usage: ') + chalk.green.bold('veriqa blame <file-path> [line-number]') + '\n\n' +
chalk.yellow('Examples:') + '\n' +
chalk.gray('veriqa blame src/app.js') + '\n' +
chalk.gray('veriqa blame src/app.js 45'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'red',
textAlignment: 'left'
}
);
console.log(errorBox);
return;
}
const blameSpinner = ora({
text: chalk.cyan(`๐ Analyzing blame info for ${filePath}...`),
spinner: 'dots',
color: 'cyan'
}).start();
try {
const gitAnalyzer = new GitAnalyzer();
const blameInfo = await gitAnalyzer.getBlameInfo(filePath, lineNumber);
if (blameInfo) {
blameSpinner.succeed(chalk.green.bold('โ
Blame analysis completed!'));
const blameBox = boxen(
chalk.white.bold('๐ BLAME INFORMATION\n\n') +
chalk.cyan('File: ') + chalk.white.bold(filePath) + '\n' +
(lineNumber ? chalk.cyan('Line: ') + chalk.yellow.bold(lineNumber) + '\n' : '') +
chalk.cyan('Last Modified By: ') + chalk.green.bold(blameInfo.author) + '\n' +
chalk.cyan('Commit: ') + chalk.yellow.bold(blameInfo.commit.substring(0, 8)) + '\n\n' +
chalk.gray('๐ก Use this info for targeted test reviews'),
{
padding: 1,
margin: 1,
borderStyle: 'classic',
borderColor: 'blue',
textAlignment: 'left'
}
);
console.log(blameBox);
} else {
blameSpinner.warn(chalk.yellow('โ ๏ธ No blame information found'));
}
} catch (error) {
blameSpinner.fail(chalk.red('โ Blame analysis failed'));
console.error(chalk.red.bold('Error:'), error.message);
}
}
async setupDirectories() {
const reportsDir = path.join(process.cwd(), 'veriqa-reports');
if (!fs.existsSync(reportsDir)) {
fs.mkdirSync(reportsDir, { recursive: true });
}
}
async setupAI() {
const envManager = new SmartEnvManager();
// Simple environment setup - no prompts, just add placeholder
const result = await envManager.setupGeminiAPIKey();
if (result.status === 'created') {
const createdBox = boxen(
chalk.green.bold('โ
.env File Created!') + '\n\n' +
chalk.white('๐ GEMINI_API_KEY placeholder added') + '\n' +
chalk.cyan('๐ Edit .env file and add your API key') + '\n' +
chalk.blue('๐ Get free key: https://ai.google.dev/') + '\n\n' +
chalk.yellow('๐ก VeriQA works with fallback suggestions until configured'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'green',
textAlignment: 'center'
}
);
console.log(createdBox);
} else if (result.status === 'appended') {
console.log(chalk.blue('๐ Added GEMINI_API_KEY to your existing .env file'));
} else if (result.status === 'existing') {
console.log(chalk.green('โ
Environment already configured'));
}
}
}
// CLI Setup with Enhanced Styling
const program = new Command();
const veriqa = new VeriQA();
// Custom help display
program.configureHelp({
formatHelp: (cmd, helper) => {
const header = figlet.textSync('VeriQA', { font: 'Speed' });
const coloredHeader = gradient(['#00f5ff', '#fc00ff'])(header);
const helpBox = boxen(
chalk.white.bold('๐ฏ VERIQA - Smart Manual QA Test Advisor') + '\n' +
chalk.yellow('๐ฎ๐ณ Proudly Made in India with AI Power ๐ฎ๐ณ') + '\n\n' +
chalk.cyan.bold('COMMANDS:') + '\n' +
chalk.white(' ๐ง init Initialize project scan') + '\n' +
chalk.white(' ๐ analyze <old> <new> Analyze git commits') + '\n' +
chalk.white(' ๐ report Generate reports') + '\n' +
chalk.white(' ๐ update Update to latest version') + '\n' +
chalk.white(' ๐ฟ status Get git status') + '\n' +
chalk.white(' ๐ฟ compare <branch1> <branch2> Compare branches') + '\n' +
chalk.white(' ๐ง hooks Install pre-commit hooks') + '\n' +
chalk.white(' ๐ blame <file> [line] Perform blame analysis') + '\n\n' +
chalk.cyan.bold('EXAMPLES:') + '\n' +
chalk.gray(' veriqa init') + '\n' +
chalk.gray(' veriqa analyze abc123 def456') + '\n' +
chalk.gray(' veriqa report') + '\n' +
chalk.gray(' veriqa status') + '\n' +
chalk.gray(' veriqa compare main feature-branch') + '\n' +
chalk.gray(' veriqa hooks') + '\n' +
chalk.gray(' veriqa blame src/app.js 45') + '\n\n' +
chalk.yellow('โจ AI-powered regression testing made simple! โจ'),
{
padding: 1,
margin: 1,
borderStyle: 'round',
borderColor: 'cyan',
textAlignment: 'left'
}
);
return coloredHeader + '\n' + helpBox + '\n';
}
});
program
.name('veriqa')
.description('๐ฏ VeriQA - Smart Manual QA Test Advisor with Advanced Git Integration')
.version(require('../package.json').version);
program
.command('init')
.description('๐ง Initialize project and scan for testing frameworks')
.action(async () => await veriqa.init());
program
.command('analyze <old-commit> <new-commit>')
.description('๐ Analyze commits and generate AI-powered test suggestions')
.action(async (oldCommit, newCommit) => await veriqa.analyze(oldCommit, newCommit));
program
.command('report')
.description('๐ Generate comprehensive HTML/PDF report')
.action(async () => await veriqa.report());
program
.command('update')
.description('๐ Update VeriQA to the latest version')
.action(async () => await veriqa.update());
program
.command('status')
.description('๐ฟ Get git status with enhanced information')
.action(async () => await veriqa.status());
program
.command('compare <branch1> <branch2>')
.description('๐ฟ Compare branches and generate regression suggestions')
.action(async (branch1, branch2) => await veriqa.compareBranches(branch1, branch2));
program
.command('hooks')
.description('๐ง Install pre-commit hooks for VeriQA')
.action(async () => await veriqa.installHooks());
program
.command('blame <file> [line]')
.description('๐ Perform blame analysis on a file or specific line')
.action(async (file, line) => await veriqa.blameAnalysis(file, line));
// Only run if called directly
if (require.main === module) {
program.parse();
}
module.exports = VeriQA;