vibe-code-build
Version:
Real-time code monitoring with teaching explanations, CLAUDE.md compliance checking, and interactive chat
2,328 lines ⢠88 kB
JavaScript
import { watch } from 'chokidar';
import chalk from 'chalk';
import { readFile } from 'fs/promises';
import path from 'path';
import { execSync } from 'child_process';
import readline from 'readline';
import { RulesChecker } from './rules-checker.js';
import { SeniorDevAdvisor } from './senior-dev-advisor.js';
import { insightEngine } from './insight-engine-unified.js';
import { TypeWriter } from './type-writer.js';
import { tokenAnalyzer } from './token-analyzer.js';
import { BuildChecker } from './build-checker.js';
import { DependencyChecker } from './dependency-checker.js';
import { ClaudeChecker } from './claude-checker.js';
import { SecurityChecker } from './security-checker.js';
import { PerformanceOptimizer } from './performance-optimizer.js';
import { CheckFormatter } from './check-formatter.js';
import { SEOChecker } from './seo-checker.js';
export class UnifiedMonitor {
constructor(projectPath, options = {}) {
this.projectPath = projectPath;
this.options = options;
this.modes = ['diff', 'rules', 'stats', 'checks', 'seo', 'security', 'speed', 'god'];
this.currentModeIndex = 0;
this.currentMode = this.modes[this.currentModeIndex];
// Shared state
this.updates = [];
this.updateCount = 0;
this.currentContext = null;
this.isProcessingInput = false;
this.watcher = null;
this.lastDisplayedUpdate = null;
this.updateHistoryPage = 0;
this.viewingHistory = false;
this.previousFileContents = {};
// Add mode switching protection
this.isSwitchingMode = false;
this.lastModeSwitchTime = 0;
this.isExiting = false;
// Stats tracking
this.stats = {
sessionStart: Date.now(),
totalFiles: 0,
linesAdded: 0,
linesRemoved: 0,
filesModified: new Set(),
languageStats: {},
streak: 0,
lastActive: Date.now()
};
// Initialize components
this.rulesChecker = new RulesChecker(projectPath);
this.seniorDev = new SeniorDevAdvisor();
this.typeWriter = new TypeWriter();
// Initialize checkers for checks mode with silent option to prevent spinner conflicts
this.checkers = {
build: new BuildChecker(projectPath, { silent: true }),
dependencies: new DependencyChecker(projectPath, { silent: true }),
claude: new ClaudeChecker(projectPath, { silent: true }),
security: new SecurityChecker(projectPath, { silent: true }),
performance: new PerformanceOptimizer(projectPath, { silent: true })
};
this.checkFormatter = new CheckFormatter();
this.checkResults = null;
this.lastCheckTime = null;
this.isRunningChecks = false;
// SEO mode specific state
this.seoResults = null;
this.seoHistory = [];
this.lastSEOCheck = null;
// Security mode specific state
this.securityThreats = [];
this.securityScore = 0;
this.isMonitoringSecurity = true;
// Speed mode specific state
this.performanceMetrics = {};
this.buildMetrics = [];
this.lastSpeedCheck = null;
// God mode specific state
this.godModeData = {
health: 0,
criticalIssues: [],
trends: {},
autoRefresh: false,
refreshInterval: null
};
// Set up readline with keypress events
this.setupReadline();
}
setupReadline() {
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
prompt: this.getPrompt(),
terminal: true
});
// Enable keypress events
if (process.stdin.isTTY) {
try {
process.stdin.setRawMode(true);
} catch (e) {
console.error('Warning: Could not set raw mode:', e.message);
}
}
readline.emitKeypressEvents(process.stdin, this.rl);
// Handle keypress events with better escape sequence filtering
// Remove any existing keypress listeners to prevent duplicates
process.stdin.removeAllListeners('keypress');
process.stdin.on('keypress', async (str, key) => {
// Ensure we're still active and not exiting
if (!this.rl || this.rl.closed || this.isExiting) {
return;
}
// Filter out raw escape sequences and prevent them from accumulating
if (str && (str.includes('\x1B[Z') || str === '\x1B[Z')) {
// This is a raw Shift+Tab escape sequence, process it properly
if (!this.isSwitchingMode && !key) {
// Only handle if we didn't get a proper key object
await this.handleModeSwitch();
}
return;
}
// Also check for sequences that might slip through
if (str && str.match(/^\x1B\[/)) {
// This is an escape sequence, ignore it
return;
}
// Prevent processing during mode switching
if (this.isSwitchingMode) {
return;
}
// No special handling needed during input - readline.question handles it
// Clear any stray input for non-interactive modes
if (this.currentMode !== 'diff' && this.currentMode !== 'rules' && str && !key.ctrl) {
process.stdout.write('\r\x1B[K');
}
// Normal keypress handling
if (key && key.name === 'tab' && key.shift) {
await this.handleModeSwitch();
} else if (key && key.ctrl && key.name === 'c') {
this.handleExit();
} else if (this.currentMode === 'checks') {
await this.handleChecksKeypress(key, str);
} else if (this.currentMode === 'seo') {
await this.handleSEOKeypress(key, str);
} else if (this.currentMode === 'security') {
await this.handleSecurityKeypress(key, str);
} else if (this.currentMode === 'speed') {
await this.handleSpeedKeypress(key, str);
} else if (this.currentMode === 'god') {
await this.handleGodModeKeypress(key, str);
} else if (this.currentMode === 'diff' && this.viewingHistory) {
await this.handleHistoryNavigation(key);
} else if (this.currentMode === 'diff' && key && key.name === 'h' && !this.viewingHistory) {
// Press 'h' to view history
this.viewingHistory = true;
this.updateHistoryPage = 0;
await this.displayUpdateHistory();
} else if (this.currentMode === 'diff' && key && key.name === 't') {
// Press 't' to cycle through insight modes
const modes = ['basic', 'detailed', 'optimized'];
const currentMode = insightEngine.getMode();
const currentIndex = modes.indexOf(currentMode);
const nextMode = modes[(currentIndex + 1) % modes.length];
insightEngine.setMode(nextMode);
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white
};
console.log(colors.info(`\nš Switched to ${nextMode} insights mode\n`));
await this.updateDisplay();
}
// Remove the manual character writing - readline handles this
});
}
async handleModeSwitch() {
// Prevent rapid mode switching
const now = Date.now();
if (this.isSwitchingMode || (now - this.lastModeSwitchTime) < 300) {
return; // Debounce: ignore if switching or less than 300ms since last switch
}
// Safety check: prevent infinite switching
if (!this.switchCount) this.switchCount = 0;
this.switchCount++;
// Reset counter after 5 seconds of no switching
if (this.switchResetTimer) clearTimeout(this.switchResetTimer);
this.switchResetTimer = setTimeout(() => {
this.switchCount = 0;
}, 5000);
// Prevent excessive switching that might crash the app
if (this.switchCount > 20) {
console.log('\nā ļø Too many rapid mode switches detected. Please wait a moment...');
this.switchCount = 0;
return;
}
this.isSwitchingMode = true;
this.lastModeSwitchTime = now;
try {
// Clear any pending input from readline with error handling
if (this.rl && !this.rl.closed) {
try {
this.rl.clearLine(process.stdout, 0);
this.rl.pause();
} catch (e) {
// Ignore errors if readline is in bad state
}
}
// Clear any lingering text
process.stdout.write('\r\x1B[K');
// Switch to next mode
this.currentModeIndex = (this.currentModeIndex + 1) % this.modes.length;
this.currentMode = this.modes[this.currentModeIndex];
// Clear screen before switching
this.clearScreen();
// Update display
await this.updateDisplay();
// Show mode switch notification
this.showModeNotification();
// Resume readline and update prompt with error handling
if (this.rl && !this.rl.closed) {
try {
this.rl.resume();
this.rl.setPrompt(this.getPrompt());
// Only show prompt for modes that need it
if (this.currentMode === 'diff' || this.currentMode === 'rules') {
this.rl.prompt();
}
} catch (e) {
// If readline is corrupted, recreate it
console.error('Readline error, recreating interface...');
this.setupReadline();
}
}
} catch (error) {
console.error('Error switching modes:', error);
// Try to recover by recreating readline if needed
if (this.rl && this.rl.closed) {
this.setupReadline();
}
} finally {
this.isSwitchingMode = false;
}
}
async handleHistoryNavigation(key) {
if (!key) return;
const pageSize = 5;
const totalPages = Math.ceil(this.updates.length / pageSize);
if (key.name === 'escape' || key.name === 'q') {
// Return to live mode
this.viewingHistory = false;
await this.updateDisplay();
} else if ((key.name === 'left' || key.name === 'up') && this.updateHistoryPage < totalPages - 1) {
// Older updates (page increases)
this.updateHistoryPage++;
await this.displayUpdateHistory();
} else if ((key.name === 'right' || key.name === 'down') && this.updateHistoryPage > 0) {
// Newer updates (page decreases)
this.updateHistoryPage--;
await this.displayUpdateHistory();
}
}
showModeNotification() {
const modeInfo = {
'diff': 'š Diff Mode - Real-time file monitoring with AI explanations',
'rules': 'š Rules Mode - CLAUDE.md compliance checking and best practices',
'stats': 'š Stats Mode - Coding session metrics and productivity insights',
'checks': 'š Checks Mode - Build, lint, test, and dependency monitoring',
'seo': 'š SEO Mode - Search engine optimization scoring and recommendations',
'security': 'š Security Mode - Vulnerability scanning and threat detection',
'speed': 'ā” Speed Mode - Performance analysis and optimization tips',
'god': 'šļø God Mode - All monitoring systems active simultaneously'
};
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
console.log('\n' + colors.success(`⨠Mode switched to: ${modeInfo[this.currentMode] || this.currentMode}`));
console.log(colors.muted('Loading interface...'));
console.log(colors.muted('Press Shift+Tab to switch modes ⢠Ctrl+C to exit\n'));
}
getPrompt() {
const prompts = {
'diff': '', // No prompt needed - just monitoring
'rules': '', // No prompt needed - just monitoring
'stats': '', // No prompt needed
'checks': '', // No prompt needed - keyboard controlled
'seo': '', // No prompt needed - keyboard controlled
'security': '', // No prompt needed - keyboard controlled
'speed': '', // No prompt needed - keyboard controlled
'god': '' // No prompt needed - keyboard controlled
};
return prompts[this.currentMode] || '';
}
async start() {
// Add global error handlers to prevent unexpected exits
process.on('uncaughtException', (error) => {
console.error('Uncaught Exception:', error);
// Try to recover instead of exiting
if (this.rl && this.rl.closed) {
this.setupReadline();
}
});
process.on('unhandledRejection', (reason, promise) => {
console.error('Unhandled Rejection at:', promise, 'reason:', reason);
// Don't exit on unhandled promise rejections
});
// Log unexpected exits for debugging
process.on('exit', (code) => {
if (!this.isExiting && code !== 0) {
console.error('Unexpected exit with code:', code);
}
});
// Theme loading removed
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
// Clear screen and show ASCII logo
console.clear();
const logo = colors.primary(`
⦠ā¦ā¦āā āāā āāāāāāāā¦āāāā āā ⦠ā¦ā¦ā¦ āā¦ā
āāāāāā ā©āāā£āāāā ā ā āāāā£āāāā ā©āā āāā āā
āā ā©āāāāāā āāāāāāāā©āāāā āāāāāāā©ā©āāāā©ā
`);
console.log(logo);
console.log(colors.muted('Real-time code monitoring with AI insights\n'));
// Small pause to let logo display
await new Promise(resolve => setTimeout(resolve, 500));
// Quick initialization
await this.rulesChecker.initialize();
// Quick checks
try {
execSync('git status', { cwd: this.projectPath, stdio: 'ignore' });
console.log(colors.success('ā Git repository detected'));
} catch {
console.log(colors.muted('ā¹ Not a git repository (limited diff support)'));
}
const fileCount = await this.countProjectFiles();
console.log(colors.success(`ā Monitoring ${fileCount} files`));
console.log(colors.success('ā Ready to analyze code changes\n'));
// Show initial mode (interface will be displayed after mode switch)
console.log(colors.secondary('š Starting in Diff Mode...'));
// Set up file watcher
this.setupFileWatcher();
// Set up input handling
this.setupInputHandling();
// Initial display
await this.updateDisplay();
}
async showWelcome() {
// No longer needed - startup is now simple and direct
}
setupFileWatcher() {
this.watcher = watch(this.projectPath, {
ignored: [
/node_modules/,
/\.git/,
/\.DS_Store/,
/\.next/,
/dist/,
/build/,
/coverage/,
/\.cache/,
/tmp/,
/temp/,
/\.log$/,
/\.lock$/,
/\.bin$/,
/\.exe$/,
/\.dll$/,
/\.so$/,
/\.dylib$/,
/\.zip$/,
/\.tar/,
/\.gz$/,
/\.jpg$/,
/\.png$/,
/\.gif$/,
/\.pdf$/,
/\.mp4$/,
/\.mov$/,
/\.avi$/,
/\.mp3$/,
/\.wav$/,
// Skip large data files
/\.csv$/,
/\.json$/ // Skip large JSON files for now
],
persistent: true,
ignoreInitial: true
});
this.watcher
.on('add', path => this.handleFileChange('added', path))
.on('change', path => this.handleFileChange('modified', path))
.on('unlink', path => this.handleFileChange('deleted', path));
}
setupInputHandling() {
// No input handling needed for monitoring modes
this.rl.on('line', async (input) => {
// Clear any input line and don't show prompt
process.stdout.write('\r\x1B[K');
});
}
async handleFileChange(type, filePath) {
this.updateCount++;
const filename = path.basename(filePath);
const ext = path.extname(filename).slice(1);
// Store context
this.currentContext = {
type,
filename,
filePath,
ext,
timestamp: new Date().toLocaleTimeString()
};
// Get file content and diff for modified files
if (type !== 'deleted') {
try {
// Check file size first - skip huge files
const stats = await import('fs/promises').then(fs => fs.stat(filePath));
if (stats.size > 1024 * 1024) { // Skip files > 1MB
console.log(`ā ļø Skipping large file: ${filename} (${Math.round(stats.size/1024)}KB)`);
return;
}
this.currentContext.content = await readFile(filePath, 'utf-8');
// Truncate very long content
if (this.currentContext.content.length > 50000) {
this.currentContext.content = this.currentContext.content.substring(0, 50000) + '\n... (truncated)';
}
if (type === 'modified') {
try {
// First try unstaged changes (what most developers are working on)
const diffUnstaged = execSync(`git diff -- "${filePath}"`, {
encoding: 'utf8',
maxBuffer: 512 * 1024, // Reduce buffer size
cwd: this.projectPath
});
if (diffUnstaged && diffUnstaged.trim()) {
// Truncate very long diffs
this.currentContext.diff = diffUnstaged.length > 10000
? diffUnstaged.substring(0, 10000) + '\n... (diff truncated)'
: diffUnstaged;
} else {
// If no unstaged changes, try staged changes
const diff = execSync(`git diff --cached -- "${filePath}"`, {
encoding: 'utf8',
maxBuffer: 512 * 1024,
cwd: this.projectPath
});
if (diff && diff.trim()) {
this.currentContext.diff = diff.length > 10000
? diff.substring(0, 10000) + '\n... (diff truncated)'
: diff;
}
}
} catch (e) {
// Not in git or no changes - create a manual diff
if (this.previousFileContents && this.previousFileContents[filePath]) {
const oldContent = this.previousFileContents[filePath];
const newContent = this.currentContext.content;
this.currentContext.diff = this.createSimpleDiff(oldContent, newContent, filename);
} else {
// First time seeing this file - show limited content
const lines = this.currentContext.content.split('\n');
let diff = `diff --git a/${filename} b/${filename}\n`;
diff += `--- /dev/null\n`;
diff += `+++ b/${filename}\n`;
diff += `@@ -0,0 +1,${Math.min(lines.length, 20)} @@\n`;
lines.slice(0, 20).forEach(line => {
diff += `+${line.substring(0, 200)}\n`; // Limit line length
});
if (lines.length > 20) {
diff += `... ${lines.length - 20} more lines\n`;
}
this.currentContext.diff = diff;
}
}
} else if (type === 'added') {
// For new files, create a diff showing limited lines
const lines = this.currentContext.content.split('\n');
let diff = `diff --git a/${filename} b/${filename}\n`;
diff += `new file mode 100644\n`;
diff += `--- /dev/null\n`;
diff += `+++ b/${filename}\n`;
diff += `@@ -0,0 +1,${Math.min(lines.length, 20)} @@\n`;
lines.slice(0, 20).forEach(line => {
diff += `+${line.substring(0, 200)}\n`; // Limit line length
});
if (lines.length > 20) {
diff += `... ${lines.length - 20} more lines (truncated)\n`;
}
this.currentContext.diff = diff;
}
} catch (e) {
console.log(`ā ļø Error reading file: ${filename}`);
return;
}
}
// Store content for future diffs (store current content for next comparison)
if (type !== 'deleted' && this.currentContext.content) {
// Limit memory usage - only store content for recent files
const maxStoredFiles = 50;
if (Object.keys(this.previousFileContents).length > maxStoredFiles) {
// Remove oldest entries
const keys = Object.keys(this.previousFileContents);
keys.slice(0, 10).forEach(key => delete this.previousFileContents[key]);
}
// For first time seeing a file, capture baseline (truncated)
if (!this.previousFileContents[filePath]) {
this.previousFileContents[filePath] = this.currentContext.content.substring(0, 10000);
} else if (type === 'modified') {
// Update stored content after processing diff (truncated)
setTimeout(() => {
this.previousFileContents[filePath] = this.currentContext.content.substring(0, 10000);
}, 100);
}
}
// Update stats tracking
this.updateStats(type, filename, this.currentContext);
// Create update object with proper timestamp for sorting
const update = {
number: this.updateCount,
timestamp: this.currentContext.timestamp,
sortTimestamp: Date.now(), // Add numeric timestamp for accurate sorting
type,
filename,
filePath,
context: { ...this.currentContext }
};
// Add to updates
this.updates.push(update);
const maxUpdates = 10;
if (this.updates.length > maxUpdates) {
this.updates.shift();
}
// Update display if not processing input
if (!this.isProcessingInput && this.currentMode === 'diff') {
// For diff mode, show the new update immediately at the top
await this.showNewUpdate(update);
} else if (!this.isProcessingInput) {
await this.updateDisplay();
}
}
async updateDisplay() {
// All modes now get proper display
if (this.currentMode === 'diff') {
this.clearScreen();
await this.displayDiffMode();
} else if (this.currentMode === 'rules') {
this.clearScreen();
await this.displayRulesMode();
} else if (this.currentMode === 'stats') {
this.clearScreen();
await this.displayStatsMode();
} else if (this.currentMode === 'checks') {
this.clearScreen();
await this.displayChecksMode();
} else if (this.currentMode === 'seo') {
this.clearScreen();
await this.displaySEOMode();
} else if (this.currentMode === 'security') {
this.clearScreen();
await this.displaySecurityMode();
} else if (this.currentMode === 'speed') {
this.clearScreen();
await this.displaySpeedMode();
} else if (this.currentMode === 'god') {
this.clearScreen();
await this.displayGodMode();
}
// diff mode doesn't need updateDisplay - it uses natural terminal flow
}
async displayDiffMode() {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
console.log(colors.header('š Vibe Code - Real-time Code Changes'));
console.log(colors.muted('Live monitoring with AI-powered explanations'));
console.log(colors.muted('ā'.repeat(60)));
// Show monitoring status
const fileCount = await this.countProjectFiles();
console.log(colors.success(`š Actively monitoring ${fileCount} files for changes`));
console.log(colors.muted('Changes appear instantly with teaching explanations\n'));
if (this.updates.length === 0) {
console.log(colors.info(' Ready to analyze your next code change...'));
console.log(colors.muted(' Make changes to any file to see AI explanations'));
} else {
// Show recent changes (most recent first)
console.log(colors.secondary('\nš Recent Changes:'));
const recentUpdates = this.updates.slice(-3).reverse();
for (const update of recentUpdates) {
const timeAgo = this.getTimeAgo(update.timestamp);
console.log(colors.muted(` š ${update.file} ${timeAgo}`));
}
}
this.showControlsFooter(colors, ['h - View change history', 't - Cycle AI insights']);
}
async displayRulesMode() {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
console.log(colors.header('š Vibe Code - CLAUDE.md Compliance'));
console.log(colors.muted('Real-time monitoring of best practices and rules'));
console.log(colors.muted('ā'.repeat(60)));
console.log(colors.muted('\nMost recent changes appear at the top\n'));
if (this.updates.length === 0) {
console.log(colors.info(' š Actively monitoring for rule violations...'));
console.log(colors.muted(' Code changes will be analyzed against CLAUDE.md guidelines'));
return;
}
// Show updates with focused AI analysis (most recent first)
const recentUpdates = this.updates.slice(-3);
recentUpdates.sort((a, b) => b.sortTimestamp - a.sortTimestamp);
for (const update of recentUpdates) {
console.log('\n' + colors.muted('ā'.repeat(50)));
console.log(`${this.getUpdateIcon(update.type)} ${colors.primary(update.filename)} ${colors.muted(update.timestamp)}`);
if (update.context.diff && update.type !== 'deleted') {
// Show the diff first
console.log('\n' + colors.secondary('š Code Changes:'));
console.log(colors.muted('ā'.repeat(50)));
console.log(this.formatDiff(update.context.diff));
console.log(colors.muted('ā'.repeat(50)));
// Then show AI analysis of the changes
const insights = insightEngine.analyzeChange(update.context);
if (insights) {
console.log(insights);
} else {
console.log(colors.muted(' No specific insights for this change'));
}
}
}
}
async displayStatsMode() {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
console.log(colors.header('š Vibe Code - Developer Stats'));
console.log(colors.muted('Real-time coding session metrics and progress'));
console.log(colors.muted('ā'.repeat(60)));
// Session duration
const sessionDuration = Date.now() - this.stats.sessionStart;
const minutes = Math.floor(sessionDuration / 60000);
const seconds = Math.floor((sessionDuration % 60000) / 1000);
console.log(colors.secondary('\nā±ļø Session Overview:'));
console.log(colors.muted(` Active for: ${minutes}m ${seconds}s`));
console.log(colors.muted(` Files monitored: ${this.stats.totalFiles}`));
console.log(colors.muted(` Files modified: ${this.stats.filesModified.size}`));
// Code changes
console.log(colors.secondary('\nš Code Changes:'));
const progressBar = this.createProgressBar(this.stats.linesAdded, this.stats.linesAdded + this.stats.linesRemoved);
console.log(colors.success(` Lines added: ${this.stats.linesAdded} ${progressBar.added}`));
console.log(colors.error(` Lines removed: ${this.stats.linesRemoved} ${progressBar.removed}`));
console.log(colors.primary(` Net change: ${this.stats.linesAdded - this.stats.linesRemoved}`));
// Language breakdown
if (Object.keys(this.stats.languageStats).length > 0) {
console.log(colors.secondary('\nš» Languages:'));
Object.entries(this.stats.languageStats)
.sort(([,a], [,b]) => b - a)
.slice(0, 5)
.forEach(([lang, count]) => {
const bar = this.createMiniProgressBar(count, Math.max(...Object.values(this.stats.languageStats)));
console.log(colors.muted(` ${lang}: ${count} files ${bar}`));
});
}
// Productivity metrics
const linesPerMinute = minutes > 0 ? Math.round((this.stats.linesAdded + this.stats.linesRemoved) / minutes) : 0;
const filesPerHour = minutes > 0 ? Math.round((this.stats.filesModified.size * 60) / minutes) : 0;
console.log(colors.secondary('\nā” Productivity:'));
console.log(colors.accent(` Lines/min: ${linesPerMinute}`));
console.log(colors.accent(` Files/hour: ${filesPerHour}`));
// Quality Check Summary (if available)
if (this.checkResults && this.lastCheckTime) {
console.log(colors.secondary('\nš Quality Checks:'));
let passedChecks = 0;
let failedChecks = 0;
let warningChecks = 0;
// Count check statuses
Object.values(this.checkResults).forEach(category => {
if (category && typeof category === 'object') {
Object.values(category).forEach(check => {
if (check && check.status) {
switch (check.status) {
case 'passed': passedChecks++; break;
case 'failed': failedChecks++; break;
case 'warning': warningChecks++; break;
}
}
});
}
});
const totalChecks = passedChecks + failedChecks + warningChecks;
const healthScore = totalChecks > 0 ? Math.round((passedChecks / totalChecks) * 100) : 0;
console.log(` Health Score: ${this.getHealthScoreDisplay(healthScore)}`);
console.log(` ${colors.success(`ā
Passed: ${passedChecks}`)} | ${colors.error(`ā Failed: ${failedChecks}`)} | ${colors.warning(`ā ļø Warnings: ${warningChecks}`)}`);
const checkAge = Math.floor((Date.now() - this.lastCheckTime) / 1000);
console.log(colors.muted(` Last checked: ${checkAge}s ago`));
}
// Recent activity
const lastActiveAgo = Math.floor((Date.now() - this.stats.lastActive) / 1000);
const status = lastActiveAgo < 10 ? 'š¢ Active' : lastActiveAgo < 60 ? 'š” Idle' : 'š“ Away';
console.log(colors.secondary('\nšÆ Status:'));
console.log(colors.muted(` ${status} (${lastActiveAgo}s ago)`));
console.log(colors.muted('\n' + 'ā'.repeat(60)));
console.log(colors.muted('Press Shift+Tab to switch modes ⢠Stats update in real-time'));
}
getHealthScoreDisplay(score) {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
const emoji = score >= 90 ? 'š' : score >= 70 ? 'š' : score >= 50 ? 'š' : 'ā ļø';
const color = score >= 90 ? colors.success : score >= 70 ? colors.warning : colors.error;
return `${emoji} ${color(score + '%')}`;
}
async handleChecksKeypress(key, str) {
if (!key && !str) return;
// Handle 'r' key to manually run checks
if (str === 'r' && !key.ctrl && !key.meta) {
console.log('\nš Running checks...');
await this.runChecks();
}
}
createProgressBar(value, total, length = 20) {
if (total === 0) return { added: '', removed: '' };
const addedRatio = value / total;
const removedRatio = (total - value) / total;
const addedBars = Math.round(addedRatio * length);
const removedBars = Math.round(removedRatio * length);
return {
added: 'ā'.repeat(addedBars),
removed: 'ā'.repeat(removedBars)
};
}
showControlsFooter(colors, customControls = []) {
console.log(colors.muted('\nš® Controls:'));
console.log(colors.muted(' Shift+Tab - Switch between modes'));
console.log(colors.muted(' Ctrl+C - Exit application'));
// Add mode-specific controls
customControls.forEach(control => {
console.log(colors.muted(` ${control}`));
});
}
getTimeAgo(timestamp) {
const now = Date.now();
const diff = now - timestamp;
const seconds = Math.floor(diff / 1000);
const minutes = Math.floor(seconds / 60);
const hours = Math.floor(minutes / 60);
if (seconds < 60) return 'just now';
if (minutes < 60) return `${minutes}m ago`;
if (hours < 24) return `${hours}h ago`;
return new Date(timestamp).toLocaleDateString();
}
createMiniProgressBar(value, max, length = 10) {
if (max === 0) return '';
const ratio = value / max;
const bars = Math.round(ratio * length);
return 'ā'.repeat(bars) + 'ā'.repeat(length - bars);
}
async displayChecksMode() {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
console.log(colors.header('š Vibe Code - Quality Checks'));
console.log(colors.muted('Real-time code quality monitoring'));
console.log(colors.muted('ā'.repeat(60)));
// Show when checks were last run
if (this.lastCheckTime) {
const ago = Math.floor((Date.now() - this.lastCheckTime) / 1000);
console.log(colors.muted(`Last check: ${ago}s ago`));
}
// Show what's currently being checked
if (this.currentCheckStatus) {
console.log(colors.info(`\n${this.currentCheckStatus}`));
}
// If no results yet, run initial checks
if (!this.checkResults) {
console.log(colors.info('\nā³ Running initial checks...'));
await this.runChecks();
return;
}
// Display results with specific issues
console.log(colors.secondary('\nš Check Results:'));
// Build checks
if (this.checkResults.build) {
const buildResult = this.checkResults.build.build || {};
const icon = buildResult.status === 'passed' ? 'ā
' :
buildResult.status === 'failed' ? 'ā' : 'āļø';
console.log(`\n${icon} ${colors.primary('Build')}: ${buildResult.message || 'Not checked'}`);
if (buildResult.status === 'failed' && buildResult.error) {
console.log(colors.error(` ā ļø ${buildResult.error.split('\n')[0]}`));
}
}
// Dependencies
if (this.checkResults.dependencies) {
const vulns = this.checkResults.dependencies.vulnerabilities || {};
const outdated = this.checkResults.dependencies.outdated || {};
const vulnIcon = vulns.status === 'passed' ? 'ā
' :
vulns.status === 'failed' ? 'ā' : 'ā ļø';
console.log(`\n${vulnIcon} ${colors.primary('Dependencies')}:`);
if (vulns.message) {
console.log(` ${vulns.message}`);
}
if (vulns.status === 'failed' && vulns.vulnerabilities) {
vulns.vulnerabilities.slice(0, 3).forEach(v => {
console.log(colors.error(` ā ļø ${v.name} - ${v.severity}`));
});
}
if (outdated.message) {
console.log(` ${outdated.message}`);
}
if (outdated.dependencies && outdated.dependencies.length > 0) {
outdated.dependencies.slice(0, 3).forEach(d => {
console.log(colors.warning(` š¦ ${d.name}: ${d.current} ā ${d.latest}`));
});
}
}
// Security
if (this.checkResults.security) {
const godMode = this.checkResults.security.godMode || {};
const secrets = this.checkResults.security.secrets || {};
const secIcon = (godMode.status === 'failed' || secrets.status === 'failed') ? 'ā' :
(godMode.status === 'warning' || secrets.status === 'warning') ? 'ā ļø' : 'ā
';
console.log(`\n${secIcon} ${colors.primary('Security')}:`);
if (godMode.message) {
console.log(` ${godMode.message}`);
}
if (godMode.findings && godMode.findings.length > 0) {
godMode.findings.slice(0, 3).forEach(f => {
console.log(colors.error(` šØ ${f.file}:${f.line} - ${f.description}`));
});
if (godMode.findings.length > 3) {
console.log(colors.error(` ... and ${godMode.findings.length - 3} more issues`));
}
}
if (secrets.message && secrets.message !== godMode.message) {
console.log(` ${secrets.message}`);
}
if (secrets.findings && secrets.findings.length > 0) {
secrets.findings.slice(0, 2).forEach(f => {
console.log(colors.error(` š ${f.file}:${f.line} - ${f.type}`));
});
}
}
// CLAUDE.md
if (this.checkResults.claude) {
const claudeMd = this.checkResults.claude.claudeMdExists || {};
const icon = claudeMd.status === 'passed' ? 'ā
' :
claudeMd.status === 'failed' ? 'ā' : 'ā ļø';
console.log(`\n${icon} ${colors.primary('CLAUDE.md')}: ${claudeMd.message || 'Not checked'}`);
}
// Performance & SEO
if (this.checkResults.performance) {
const bundle = this.checkResults.performance.bundleSize || {};
const seo = this.checkResults.performance.seo || {};
// Show bundle size
const bundleIcon = bundle.status === 'warning' ? 'ā ļø' :
bundle.status === 'failed' ? 'ā' : 'ā
';
console.log(`\n${bundleIcon} ${colors.primary('Performance')}:`);
if (bundle.message) {
console.log(` ${bundle.message}`);
}
// Enhanced SEO display
if (seo.score !== undefined) {
// Show SEO score prominently
const seoIcon = seo.status === 'passed' ? 'ā
' :
seo.status === 'warning' ? 'ā ļø' : 'ā';
const scoreColor = seo.score >= 80 ? colors.success :
seo.score >= 60 ? colors.warning : colors.error;
console.log(`\n${seoIcon} ${colors.primary('SEO Analysis')}:`);
console.log(scoreColor(` šÆ Score: ${seo.score}/100 (${seo.grade || 'N/A'})`));
// Show category breakdown if available
if (seo.categories) {
console.log(colors.secondary(' š Category Scores:'));
['technical', 'content', 'social', 'performance'].forEach(category => {
const cat = seo.categories[category];
if (cat && cat.score !== undefined) {
const catColor = cat.score >= 80 ? colors.success :
cat.score >= 60 ? colors.warning : colors.error;
console.log(catColor(` ⢠${category.charAt(0).toUpperCase() + category.slice(1)}: ${cat.score}/100`));
}
});
}
// Show top issues
if (seo.issues && seo.issues.length > 0) {
console.log(colors.secondary(' ā ļø Top Issues:'));
seo.issues.slice(0, 3).forEach(issue => {
const issueColor = issue.severity === 'critical' ? colors.error :
issue.severity === 'high' ? colors.error :
issue.severity === 'medium' ? colors.warning : colors.muted;
console.log(issueColor(` ⢠${issue.message}`));
if (issue.recommendation) {
console.log(colors.info(` š” ${issue.recommendation}`));
}
});
if (seo.issues.length > 3) {
console.log(colors.muted(` ... and ${seo.issues.length - 3} more issues`));
}
}
// Show top recommendations
if (seo.recommendations && seo.recommendations.length > 0) {
console.log(colors.secondary(' šÆ Priority Actions:'));
seo.recommendations.slice(0, 2).forEach((rec, idx) => {
if (rec.priority) {
const recColor = rec.priority === 'critical' || rec.priority === 'high' ? colors.error :
rec.priority === 'medium' ? colors.warning : colors.info;
console.log(recColor(` ${idx + 1}. ${rec.title}`));
console.log(colors.muted(` ${rec.description}`));
} else {
console.log(colors.info(` ⢠${rec}`));
}
});
}
} else if (seo.message) {
// Fallback to simple message
console.log(` ${seo.message}`);
}
}
// Show hint about manual checking
if (this.updates.length > 0 && !this.lastCheckTime) {
const lastUpdate = this.updates[this.updates.length - 1];
const timeSinceUpdate = Date.now() - lastUpdate.sortTimestamp;
if (timeSinceUpdate > 5000) {
console.log(colors.muted('\nš” Tip: Press "r" to run checks on recent changes'));
}
}
this.showControlsFooter(colors, ['r - Run quality checks']);
}
async runChecks() {
// Prevent recursive calls
if (this.isRunningChecks) return;
this.isRunningChecks = true;
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
try {
this.checkResults = {};
// Run checks sequentially to show progress
console.log(colors.info('\nš Running quality checks...\n'));
// Build check
this.currentCheckStatus = 'šØ Checking build configuration...';
await this.updateDisplay();
this.checkResults.build = await this.checkers.build.checkAll().catch(e => ({ error: e.message }));
// Dependencies check
this.currentCheckStatus = 'š¦ Checking dependencies...';
await this.updateDisplay();
this.checkResults.dependencies = await this.checkers.dependencies.checkAll().catch(e => ({ error: e.message }));
// Security check
this.currentCheckStatus = 'š Running security scans...';
await this.updateDisplay();
this.checkResults.security = await this.checkers.security.checkAll().catch(e => ({ error: e.message }));
// CLAUDE.md check
this.currentCheckStatus = 'š¤ Validating CLAUDE.md compliance...';
await this.updateDisplay();
this.checkResults.claude = await this.checkers.claude.checkAll().catch(e => ({ error: e.message }));
// Performance check
this.currentCheckStatus = 'ā” Analyzing performance...';
await this.updateDisplay();
this.checkResults.performance = await this.checkers.performance.checkAll().catch(e => ({ error: e.message }));
this.lastCheckTime = Date.now();
this.currentCheckStatus = null;
// Clear screen and display results
this.clearScreen();
await this.displayChecksMode();
} catch (error) {
console.error(colors.error('Check failed: ' + error.message));
this.currentCheckStatus = null;
} finally {
this.isRunningChecks = false;
}
}
updateStats(type, filename, context) {
// Update last active time
this.stats.lastActive = Date.now();
// Track file extensions/languages
const ext = path.extname(filename).slice(1).toLowerCase() || 'other';
if (!this.stats.languageStats[ext]) {
this.stats.languageStats[ext] = 0;
}
this.stats.languageStats[ext]++;
// Count files
if (type === 'added') {
this.stats.totalFiles++;
}
// Track modified files
if (type === 'modified' || type === 'added') {
this.stats.filesModified.add(context.filePath);
}
// Count lines added/removed from diff
if (context.diff) {
const lines = context.diff.split('\n');
const addedLines = lines.filter(line => line.startsWith('+') && !line.startsWith('+++')).length;
const removedLines = lines.filter(line => line.startsWith('-') && !line.startsWith('---')).length;
this.stats.linesAdded += addedLines;
this.stats.linesRemoved += removedLines;
}
}
getUpdateIcon(type) {
const icons = {
'added': chalk.green('ā
'),
'modified': chalk.yellow('š'),
'deleted': chalk.red('šļø')
};
return icons[type] || 'š';
}
createSimpleDiff(oldContent, newContent, filename) {
const oldLines = oldContent.split('\n');
const newLines = newContent.split('\n');
let diff = `diff --git a/${filename} b/${filename}\n`;
diff += `--- a/${filename}\n`;
diff += `+++ b/${filename}\n`;
diff += `@@ -1,${oldLines.length} +1,${newLines.length} @@\n`;
// Simple line-by-line diff
const maxLines = Math.max(oldLines.length, newLines.length);
for (let i = 0; i < maxLines; i++) {
if (i < oldLines.length && i < newLines.length) {
if (oldLines[i] !== newLines[i]) {
diff += `-${oldLines[i]}\n`;
diff += `+${newLines[i]}\n`;
} else {
diff += ` ${oldLines[i]}\n`;
}
} else if (i < oldLines.length) {
diff += `-${oldLines[i]}\n`;
} else {
diff += `+${newLines[i]}\n`;
}
}
return diff;
}
formatDiff(diff) {
const lines = diff.split('\n');
const formatted = [];
let showCount = 0;
let inHunk = false;
let addedCount = 0;
let removedCount = 0;
lines.forEach((line, index) => {
if (showCount > 40) return;
if (line.startsWith('@@')) {
// Hunk header - shows line numbers
formatted.push(chalk.cyan.bold(line));
inHunk = true;
} else if (line.startsWith('+') && !line.startsWith('+++')) {
// Added line - highlight with better formatting
formatted.push(chalk.green.bold('+ ') + chalk.green(line.substring(1)));
showCount++;
addedCount++;
} else if (line.startsWith('-') && !line.startsWith('---')) {
// Removed line - highlight with better formatting
formatted.push(chalk.red.bold('- ') + chalk.red(line.substring(1)));
showCount++;
removedCount++;
} else if (line.startsWith('diff --git')) {
// File header with better styling
formatted.push(chalk.yellow.bold('š ' + line));
} else if (line.startsWith('+++') || line.startsWith('---')) {
// File paths
formatted.push(chalk.gray(line));
} else if (inHunk && line.startsWith(' ')) {
// Context line in hunk - show some context
formatted.push(chalk.gray(' ' + line.substring(1)));
showCount++;
}
});
if (showCount > 40) {
formatted.push(chalk.gray('\n... (diff truncated, showing first 40 changes)'));
}
// Add summary
if (addedCount > 0 || removedCount > 0) {
formatted.unshift(chalk.cyan(`š Changes: ${addedCount} additions, ${removedCount} deletions\n`));
}
return formatted.join('\n');
}
clearScreen() {
console.clear();
}
async showNewUpdate(update) {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
// Just append the new update naturally - no screen clearing
console.log('\n' + colors.accent('ā'.repeat(60)));
console.log(colors.success.bold('š FILE CHANGED'));
console.log(colors.accent('ā'.repeat(60)));
await this.displaySingleUpdate(update, true);
console.log(colors.muted('ā'.repeat(60)) + '\n');
// Update the last displayed update
this.lastDisplayedUpdate = update;
}
async displaySingleUpdate(update, withAnimation = false) {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
console.log(colors.header(`š UPDATE #${update.number} | ${update.timestamp}`));
console.log(colors.muted('ā'.repeat(50)));
const icon = update.type === 'added' ? 'ā
' : update.type === 'modified' ? 'š' : 'šļø';
console.log(`${icon} ${colors.primary(update.filename)}`);
if (update.context.diff) {
console.log('\n' + colors.secondary('š Code Changes:'));
console.log(colors.muted('ā'.repeat(50)));
console.log(this.formatDiff(update.context.diff));
console.log(colors.muted('ā'.repeat(50)));
// Get insights with mentoring
const insights = insightEngine.analyzeChange(update.context);
if (insights) {
if (withAnimation) {
await this.typeWriter.typeOut(insights, 'fast');
} else {
console.log(insights);
}
}
// Show token usage stats
const stats = tokenAnalyzer.analyzeOutput(insights);
const tokenStats = tokenAnalyzer.formatStats(stats, 'Insight');
console.log(tokenStats);
// If in optimized mode, show savings compared to detailed
if (insightEngine.getMode() === 'optimized') {
const currentMode = insightEngine.getMode();
insightEngine.setMode('detailed');
const detailedAnalysis = insightEngine.analyzeChange(update.context);
insightEngine.setMode(currentMode);
const comparison = tokenAnalyzer.compareOutputs(detailedAnalysis, insights);
if (comparison.savings.percentage > 0) {
console.log(colors.success(` š° Token savings: ${comparison.savings.tokens} tokens (${comparison.savings.percentage}% reduction)\n`));
}
}
}
}
async displayUpdateHistory() {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
this.clearScreen();
console.log(colors.header('š Update History'));
console.log(colors.muted('Navigate with arrow keys ⢠Press ESC or Q to return to live mode'));
console.log(colors.muted('ā'.repeat(60)));
const pageSize = 5;
const totalPages = Math.ceil(this.updates.length / pageSize);
const currentPage = totalPages - this.updateHistoryPage - 1;
// Calculate indices for reverse pagination (newest first)
const startIdx = currentPage * pageSize;
const endIdx = Math.min(startIdx + pageSize, this.updates.length);
// Get updates for this page and sort newest first
const pageUpdates = this.updates.slice(startIdx, endIdx);
pageUpdates.sort((a, b) => b.sortTimestamp - a.sortTimestamp);
console.log(colors.info(`\nPage ${this.updateHistoryPage + 1} of ${totalPages} (Updates ${this.updates.length - endIdx + 1}-${this.updates.length - startIdx})\n`));
for (const update of pageUpdates) {
await this.displaySingleUpdate(update, false);
console.log(colors.muted('\n' + 'ā'.repeat(50) + '\n'));
}
// Navigation footer
const hasOlder = this.updateHistoryPage < totalPages - 1;
const hasNewer = this.updateHistoryPage > 0;
console.log(colors.secondary('\nš® Navigation:'));
if (hasNewer) console.log(colors.muted(' ā or ā - Newer updates'));
if (hasOlder) console.log(colors.muted(' ā or ā - Older updates'));
console.log(colors.muted(' ESC or Q - Return to live mode'));
}
async countProjectFiles() {
// Simple file count - in real implementation could be more sophisticated
try {
const { execSync } = await import('child_process');
const count = execSync(
`find ${this.projectPath} -type f -name "*.js" -o -name "*.ts" -o -name "*.jsx" -o -name "*.tsx" -o -name "*.json" -o -name "*.md" | grep -v node_modules | wc -l`,
{ encoding: 'utf8', stdio: 'pipe' }
).trim();
return parseInt(count) || 0;
} catch {
// Fallback for Windows or if find command fails
return 'multiple';
}
}
handleExit() {
// Prevent multiple exit calls
if (this.isExiting) return;
this.isExiting = true;
// Clean up all event listeners
process.stdin.removeAllListeners('keypress');
// Clean up readline interface
if (this.rl && !this.rl.closed) {
this.rl.close();
}
// Restore terminal state
if (process.stdin.isTTY) {
try {
process.stdin.setRawMode(false);
} catch (e) {
// Ignore errors when restoring terminal
}
}
// Stop file watcher
if (this.watcher) {
this.watcher.close();
}
console.log(chalk.yellow('\n\nš Thanks for using Vibe Code!'));
process.exit(0);
}
capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
}
async displaySEOMode() {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
console.log(colors.header('š Vibe Code - SEO Analysis'));
console.log(colors.muted('Search engine optimization monitoring and insights'));
console.log(colors.muted('ā'.repeat(60)));
// Run SEO analysis if not done yet or if it's been more than 30 seconds
if (!this.seoResults || !this.lastSEOCheck || (Date.now() - this.lastSEOCheck > 30000)) {
await this.runSEOAnalysis();
}
if (this.seoResults && this.seoResults.overall) {
const { score, grade } = this.seoResults.overall;
const scoreColor = score >= 80 ? colors.success : score >= 60 ? colors.warning : colors.error;
// Calculate score change from history
let scoreChange = '';
if (this.seoHistory.length > 1) {
const prevScore = this.seoHistory[this.seoHistory.length - 2].score;
const diff = score - prevScore;
if (diff > 0) scoreChange = colors.success(` ā +${diff} from last check`);
else if (diff < 0) scoreChange = colors.error(` ā ${diff} from last check`);
}
console.log(scoreColor.bold(`Overall Score: ${score}/100 (${grade})${scoreChange}\n`));
// Live metrics
console.log(colors.secondary('š Live Metrics:'));
if (this.seoResults.categories) {
['technical', 'content', 'social', 'performance'].forEach(category => {
const cat = this.seoResults.categories[category];
if (cat && cat.score !== undefined) {
const catColor = cat.score >= 80 ? colors.success : cat.score >= 60 ? colors.warning : colors.error;
const icon = cat.score >= 80 ? 'ā
' : cat.score >= 60 ? 'ā ļø' : 'ā';
const details = this.getSEOCategoryDetails(category, cat);
console.log(catColor(`⢠${this.capitalize(category)}: ${cat.score}/100 ${icon} ${details}`));
}
});
}
// Critical issues
if (this.seoResults.issues && this.seoResults.issues.length > 0) {
const criticalIssues = this.seoResults.issues.filter(i => i.severity === 'critical' || i.severity === 'high');
if (criticalIssues.length > 0) {
console.log(colors.error.bold('\nš“ Critical Issues (Fix immediately):'));
criticalIssues.slice(0, 3).forEach((issue, idx) => {
console.log(colors.error(`${idx + 1}. ${issue.message}`));
if (issue.recommendation) {
console.log(colors.info(` ā ${issue.recommendation}`));
}
});
}
}
// Real-time monitoring status
console.log(colors.secondary('\nā” Real-time Monitoring: ') + colors.success('Active'));
if (this.updates.length > 0) {
const lastUpdate = this.updates[this.updates.length - 1];
const timeSince = Math.floor((Date.now() - lastUpdate.sortTimestamp) / 1000);
console.log(colors.muted(`Last change: ${lastUpdate.filename} (${timeSince}s ago)`));
}
// SEO History trend
if (this.seoHistory.length > 1) {
console.log(colors.secondary('\nš Score Trend:'));
this.displaySEOTrend();
}
} else {
console.log(colors.info('ā³ Running SEO analysis...'));
}
this.showControlsFooter(colors, ['r - Run SEO analysis', 'e - Export report', 'h - View score history']);
}
async runSEOAnalysis() {
try {
const seoChecker = new SEOChecker(this.projectPath, { silent: true });
this.seoResults = await seoChecker.checkAll();
this.lastSEOCheck = Date.now();
// Add to history
if (this.seoResults.overall) {
this.seoHistory.push({
timestamp: Date.now(),
score: this.seoResults.overall.score,
grade: this.seoResults.overall.grade
});
// Keep only last 10 entries
if (this.seoHistory.length > 10) {
this.seoHistory.shift();
}
}
} catch (error) {
console.error('SEO analysis failed:', error);
}
}
getSEOCategoryDetails(category, data) {
const details = {
technical: (data) => {
const checks = data.checks || {};
const found = [];
if (checks.robots?.found) found.push('robots.txt ā');
if (checks.sitemap?.found) found.push('sitemap.xml ā');
if (checks.https?.enabled) found.push('HTTPS ā');
return found.length > 0 ? `(${found.join(', ')})` : '(missing core files)';
},
content: (data) => {
const stats = data.stats || {};
if (stats.pagesAnalyzed > 0) {
const missing = [];
if (stats.titlesFound < stats.pagesAnalyzed) {
missing.push(`${stats.pagesAnalyzed - stats.titlesFound} missing titles`);
}
if (stats.descriptionsFound < stats.pagesAnalyzed) {
missing.push(`${stats.pagesAnalyzed - stats.descriptionsFound} missing descriptions`);
}
return missing.length > 0 ? `(${missing.join(', ')})` : '(all meta tags present)';
}
return '';
},
social: (data) => {
const stats = data.stats || {};
if (stats.pagesChecked > 0) {
const missing = stats.pagesChecked - (stats.ogTags?.found || 0);
return missing > 0 ? `(${missing} pages missing OG tags)` : '(all social tags present)';
}
return '';
},
performance: (data) => {
const factors = data.factors || {};
const issues = [];
if (factors.largeImages?.length > 0) {
issues.push(`${factors.largeImages.length} large images`);
}
if (factors.renderBlocking?.length > 0) {
issues.push(`${factors.renderBlocking.length} render-blocking`);
}
return issues.length > 0 ? `(${issues.join(', ')})` : '(optimized)';
}
};
return details[category] ? details[category](data) : '';
}
displaySEOTrend() {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
const width = 40;
const height = 5;
const scores = this.seoHistory.map(h => h.score);
const maxScore = 100;
const minScore = 0;
// Simple ASCII chart
for (let i = height; i > 0; i--) {
const threshold = (i / height) * maxScore;
let line = '';
scores.forEach((score, idx) => {
if (score >= threshold) {
line += idx === scores.length - 1 ? colors.success('ā') : colors.muted('ā');
} else {
line += ' ';
}
});
console.log(colors.muted(`${String(Math.round(threshold)).padStart(3)} ā`) + line);
}
console.log(colors.muted(' ā' + 'ā'.repeat(scores.length)));
}
async handleSEOKeypress(key, str) {
if (!key) return;
if (key.name === 'r') {
await this.runSEOAnalysis();
await this.updateDisplay();
} else if (key.name === 'e') {
// Export SEO report
console.log(chalk.blue('\nš Exporting SEO report...'));
// TODO: Implement export functionality
} else if (key.name === 'h') {
// Show history
console.log(chalk.blue('\nš SEO Score History:'));
this.seoHistory.forEach(entry => {
const date = new Date(entry.timestamp).toLocaleString();
console.log(`${date}: ${entry.score}/100 (${entry.grade})`);
});
} else if (str && !key.ctrl && !key.meta) {
process.stdout.write('\r\x1B[K');
}
}
async displaySecurityMode() {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
console.log(colors.header('š Vibe Code - Security Analysis'));
console.log(colors.muted('Real-time vulnerability and threat detection'));
console.log(colors.muted('ā'.repeat(60)));
// Run security scan if needed
if (!this.securityThreats || Date.now() - (this.lastSecurityCheck || 0) > 10000) {
await this.runSecurityScan();
}
// Security score with color
const scoreColor = this.securityScore >= 80 ? colors.success :
this.securityScore >= 60 ? colors.warning : colors.error;
const riskLevel = this.securityScore >= 80 ? 'LOW RISK' :
this.securityScore >= 60 ? 'MEDIUM RISK' : 'HIGH RISK';
console.log(scoreColor.bold(`Security Score: ${this.securityScore}/100 (${this.getGrade(this.securityScore)}) - ${riskLevel} ā ļø\n`));
// Separate production and test threats
const prodThreats = this.securityThreats.filter(t => t.isRealThreat !== false);
const testThreats = this.securityThreats.filter(t => t.isRealThreat === false);
// Production threats (real issues)
if (prodThreats.length > 0) {
console.log(colors.error.bold('šØ Production Code Threats (Action Required):'));
prodThreats.slice(0, 5).forEach(threat => {
const icon = threat.severity === 'critical' ? '⢠CRITICAL:' : '⢠HIGH:';
console.log(colors.error(`${icon} ${threat.message}`));
if (threat.code) {
console.log(colors.muted(` ${threat.code}`));
}
if (threat.fix) {
console.log(colors.success(` ā ${threat.fix}`));
}
});
if (prodThreats.length > 5) {
console.log(colors.error(`\n... and ${prodThreats.length - 5} more production threats`));
}
} else {
console.log(colors.success.bold('ā
No Production Threats Detected'));
}
// Test/Local threats (informational)
if (testThreats.length > 0) {
console.log(colors.secondary('\nš Test/Local Code (Informational):'));
console.log(colors.muted(`Found ${testThreats.length} intentional test patterns - not deployment risks`));
testThreats.slice(0, 2).forEach(threat => {
console.log(colors.muted(` ⢠${threat.file} (${threat.environment})`));
});
}
// Security breakdown
console.log(colors.secondary('\nš Security Breakdown:'));
if (this.checkResults?.security) {
const sec = this.checkResults.security;
const godMode = sec.godMode || {};
const secrets = sec.secrets || {};
const vulns = sec.vulnerabilities || {};
console.log(`⢠God Mode Patterns: ${godMode.findings?.length || 0} found${godMode.findings?.filter(f => f.severity === 'critical').length ? ` (${godMode.findings.filter(f => f.severity === 'critical').length} critical)` : ''}`);
console.log(`⢠Exposed Secrets: ${secrets.findings?.length || 0} found${secrets.findings?.filter(f => f.severity === 'critical').length ? ` (${secrets.findings.filter(f => f.severity === 'critical').length} API keys)` : ''}`);
console.log(`⢠Injection Risks: ${vulns.sqlInjection?.length || 0} SQL, ${vulns.xss?.length || 0} XSS vulnerabilities`);
if (this.checkResults?.dependencies?.vulnerabilities) {
const depVulns = this.checkResults.dependencies.vulnerabilities.vulnerabilities || [];
const high = depVulns.filter(v => v.severity === 'high').length;
const critical = depVulns.filter(v => v.severity === 'critical').length;
console.log(`⢠Dependencies: ${depVulns.length} vulnerabilities${critical ? ` (${critical} critical)` : ''}`);
}
}
// Real-time monitoring
console.log(colors.secondary('\nš”ļø Real-time Protection: ') + colors.success('Active'));
const fileCount = await this.countProjectFiles();
console.log(colors.muted(`Monitoring: ${fileCount} files | Last scan: ${this.lastSecurityCheck ? Math.floor((Date.now() - this.lastSecurityCheck) / 1000) + 's ago' : 'running...'}`));
this.showControlsFooter(colors, ['r - Run full security scan', 's - Scan specific file', 'd - Check dependencies', 'f - Auto-fix issues']);
}
async runSecurityScan() {
this.lastSecurityCheck = Date.now();
try {
// Run security checker
const securityChecker = new SecurityChecker(this.projectPath, { silent: true });
const results = await securityChecker.checkAll();
// Calculate security score
let score = 100;
const threats = [];
// Process god mode patterns
if (results.godMode?.findings) {
results.godMode.findings.forEach(finding => {
// Only deduct points for production threats
if (finding.isRealThreat) {
if (finding.originalSeverity === 'critical') score -= 15;
else if (finding.originalSeverity === 'high') score -= 10;
else score -= 5;
}
threats.push({
severity: finding.severity,
message: `${finding.description} in ${finding.file}:${finding.line}`,
code: finding.match,
fix: this.getSecurityFix(finding.type),
environment: finding.environment,
isRealThreat: finding.isRealThreat,
file: finding.file
});
});
}
// Process exposed secrets
if (results.secrets?.findings) {
results.secrets.findings.forEach(finding => {
// Only deduct points for production threats
if (finding.isRealThreat) {
if (finding.originalSeverity === 'critical') score -= 20;
else score -= 10;
}
threats.push({
severity: finding.severity,
message: `${finding.type} exposed in ${finding.file}:${finding.line}`,
code: finding.match?.substring(0, 50) + '...',
fix: 'Move to .env file and add to .gitignore',
environment: finding.environment,
isRealThreat: finding.isRealThreat,
file: finding.file
});
});
}
this.securityScore = Math.max(0, score);
this.securityThreats = threats.sort((a, b) => {
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
return severityOrder[a.severity] - severityOrder[b.severity];
});
// Store full results for breakdown
this.checkResults = this.checkResults || {};
this.checkResults.security = results;
} catch (error) {
console.error('Security scan failed:', error);
}
}
getSecurityFix(type) {
const fixes = {
'eval': 'Never use eval with user input - use JSON.parse or safer alternatives',
'sql-injection': 'Use parameterized queries or prepared statements',
'xss': 'Sanitize user input and escape HTML entities',
'hardcoded-secret': 'Move to environment variables',
'weak-crypto': 'Use crypto.randomBytes for secure random values'
};
return fixes[type] || 'Review security best practices';
}
async handleSecurityKeypress(key, str) {
if (!key) return;
if (key.name === 'r') {
console.log(chalk.blue('\nš Running full security scan...'));
await this.runSecurityScan();
await this.updateDisplay();
} else if (key.name === 's') {
// TODO: Implement file-specific scanning
console.log(chalk.blue('\nš File scanning coming soon...'));
} else if (key.name === 'd') {
// Check dependencies
console.log(chalk.blue('\nš¦ Checking dependencies...'));
const depChecker = new DependencyChecker(this.projectPath, { silent: true });
const deps = await depChecker.checkVulnerabilities();
console.log(deps.message);
} else if (key.name === 'f') {
console.log(chalk.yellow('\nš§ Auto-fix feature coming soon...'));
} else if (str && !key.ctrl && !key.meta) {
process.stdout.write('\r\x1B[K');
}
}
async displaySpeedMode() {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
console.log(colors.header('ā” Vibe Code - Performance Analysis'));
console.log(colors.muted('Real-time speed and optimization monitoring'));
console.log(colors.muted('ā'.repeat(60)));
// Run performance analysis if needed
if (!this.performanceMetrics || Date.now() - (this.lastSpeedCheck || 0) > 15000) {
await this.runSpeedAnalysis();
}
// Performance score
const perfScore = this.performanceMetrics.score || 0;
const scoreColor = perfScore >= 80 ? colors.success : perfScore >= 60 ? colors.warning : colors.error;
const grade = this.getGrade(perfScore);
console.log(scoreColor.bold(`Performance Score: ${perfScore}/100 (${grade})\n`));
// Bundle analysis
if (this.performanceMetrics.bundleSize) {
const bundle = this.performanceMetrics.bundleSize;
console.log(colors.secondary('š¦ Bundle Analysis:'));
const sizeColor = bundle.totalSize > 2 * 1024 * 1024 ? colors.error : colors.success;
console.log(sizeColor(`⢠Total Size: ${this.formatSize(bundle.totalSize)}${bundle.totalSize > 2 * 1024 * 1024 ? ' (ā Target: < 2MB)' : ' ā
'}`));
if (bundle.breakdown) {
console.log(`⢠JS: ${this.formatSize(bundle.breakdown.js.size)} | CSS: ${this.formatSize(bundle.breakdown.css.size)} | Images: ${this.formatSize(bundle.breakdown.other.size)}`);
}
if (bundle.largeFiles && bundle.largeFiles.length > 0) {
console.log(colors.warning(`⢠Largest: ${bundle.largeFiles[0].name} (${bundle.largeFiles[0].size}) - Consider code splitting`));
}
}
// Image optimization
if (this.performanceMetrics.images) {
const images = this.performanceMetrics.images;
if (images.unoptimized > 0) {
console.log(colors.secondary('\nš¼ļø Image Optimization:'));
console.log(colors.warning(`⢠${images.unoptimized} unoptimized images (Save ${images.potentialSavings || 'significant size'})`));
if (images.recommendations && images.recommendations.length > 0) {
images.recommendations.slice(0, 2).forEach(rec => {
console.log(colors.muted(` - ${rec.file}: ${rec.size} ā ${rec.optimizedSize} (${rec.format})`));
});
}
}
}
// Core Web Vitals (simulated)
console.log(colors.secondary('\nā” Core Web Vitals (Simulated):'));
const lcp = this.performanceMetrics.lcp || 3.2;
const fid = this.performanceMetrics.fid || 95;
const cls = this.performanceMetrics.cls || 0.15;
console.log(`⢠LCP: ${lcp}s ${lcp <= 2.5 ? 'ā
' : 'ā ļø'} (Target: < 2.5s)`);
console.log(`⢠FID: ${fid}ms ${fid <= 100 ? 'ā
' : 'ā ļø'} (Target: < 100ms)`);
console.log(`⢠CLS: ${cls} ${cls <= 0.1 ? 'ā
' : 'ā ļø'} (Target: < 0.1)`);
// Hot spots
if (this.performanceMetrics.hotSpots && this.performanceMetrics.hotSpots.length > 0) {
console.log(colors.secondary('\nš„ Hot Spots:'));
this.performanceMetrics.hotSpots.slice(0, 3).forEach((spot, idx) => {
console.log(colors.warning(`${idx + 1}. ${spot.file}: ${spot.issue}`));
});
}
// Real-time metrics
console.log(colors.secondary('\nš Real-time Metrics:'));
const buildTime = this.buildMetrics.length > 0 ? this.buildMetrics[this.buildMetrics.length - 1].time : 'N/A';
console.log(colors.muted(`Build time: ${buildTime} | Memory: ${process.memoryUsage().heapUsed / 1024 / 1024 | 0}MB`));
this.showControlsFooter(colors, ['r - Run performance analysis', 'b - Bundle details', 'i - Image optimizer', 'c - Core Web Vitals']);
}
async runSpeedAnalysis() {
this.lastSpeedCheck = Date.now();
try {
const perfOptimizer = new PerformanceOptimizer(this.projectPath, { silent: true });
const results = await perfOptimizer.checkAll();
// Calculate performance score
let score = 100;
const hotSpots = [];
// Bundle size scoring
if (results.bundleSize) {
const totalSize = results.bundleSize.totalSize || 0;
if (totalSize > 5 * 1024 * 1024) score -= 30;
else if (totalSize > 2 * 1024 * 1024) score -= 15;
else if (totalSize > 1 * 1024 * 1024) score -= 5;
if (results.bundleSize.largeFiles) {
results.bundleSize.largeFiles.forEach(file => {
hotSpots.push({
file: file.name,
issue: `Large file: ${file.size}`
});
});
}
}
// Image optimization scoring
if (results.imageOptimization) {
const unoptimized = results.imageOptimization.unoptimized || 0;
score -= Math.min(20, unoptimized * 2);
}
// Performance issues
if (results.performance?.issues) {
results.performance.issues.forEach(issue => {
if (issue.severity === 'high') score -= 5;
else score -= 2;
hotSpots.push({
file: issue.file,
issue: issue.message
});
});
}
this.performanceMetrics = {
score: Math.max(0, score),
bundleSize: results.bundleSize,
images: results.imageOptimization,
performance: results.performance,
hotSpots,
// Simulated Core Web Vitals
lcp: 2.5 + (100 - score) * 0.02,
fid: 50 + (100 - score),
cls: 0.05 + (100 - score) * 0.002
};
} catch (error) {
console.error('Performance analysis failed:', error);
}
}
formatSize(bytes) {
if (!bytes) return '0 B';
if (bytes < 1024) return `${bytes} B`;
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
}
async handleSpeedKeypress(key, str) {
if (!key) return;
if (key.name === 'r') {
console.log(chalk.blue('\nā” Running performance analysis...'));
await this.runSpeedAnalysis();
await this.updateDisplay();
} else if (key.name === 'b') {
console.log(chalk.blue('\nš¦ Detailed bundle analysis coming soon...'));
} else if (key.name === 'i') {
console.log(chalk.blue('\nš¼ļø Image optimization wizard coming soon...'));
} else if (key.name === 'c') {
console.log(chalk.blue('\nš Core Web Vitals testing coming soon...'));
} else if (str && !key.ctrl && !key.meta) {
process.stdout.write('\r\x1B[K');
}
}
async displayGodMode() {
const colors = {
error: chalk.red,
warning: chalk.yellow,
success: chalk.green,
info: chalk.blue,
muted: chalk.gray,
primary: chalk.white,
secondary: chalk.cyan,
header: chalk.bold.blue,
highlight: chalk.bgBlue.white,
accent: chalk.magenta
};
console.log(colors.header('šļø Vibe Code - God Mode Monitoring'));
console.log(colors.muted('All-in-one comprehensive code oversight'));
console.log(colors.muted('ā'.repeat(60)));
// Run all checks if needed or auto-refresh is on
if (!this.godModeData.health || this.godModeData.autoRefresh || Date.now() - (this.lastGodCheck || 0) > 30000) {
await this.runGodModeAnalysis();
}
// Overall health with color and trend
const healthColor = this.godModeData.health >= 80 ? colors.success :
this.godModeData.health >= 60 ? colors.warning : colors.error;
const trend = this.godModeData.trend || 'stable';
const trendIcon = trend === 'improving' ? 'ā' : trend === 'declining' ? 'ā' : 'ā';
console.log(healthColor.bold(`Overall Health: ${this.godModeData.health}/100 (${this.getGrade(this.godModeData.health)}) ${trendIcon} ${this.capitalize(trend)}\n`));
// Critical issues
if (this.godModeData.criticalIssues.length > 0) {
console.log(colors.error.bold('šÆ Critical Issues (Fix First):'));
this.godModeData.criticalIssues.slice(0, 4).forEach((issue, idx) => {
const icon = issue.category === 'security' ? 'š' :
issue.category === 'performance' ? 'ā”' :
issue.category === 'seo' ? 'š' : 'šØ';
console.log(colors.error(`${idx + 1}. ${icon} ${issue.severity.toUpperCase()}: ${issue.message}`));
});
}
// Live monitors table
console.log(colors.secondary('\nš Live Monitors:'));
console.log(colors.muted('āāāāāāāāāāāāāāā¬āāāāāāāāā¬āāāāāāāāāāāāāāāāā'));
console.log(colors.muted('ā Category ā Score ā Trend ā'));
console.log(colors.muted('āāāāāāāāāāāāāāā¼āāāāāāāāā¼āāāāāāāāāāāāāāāāā¤'));
const categories = [
{ name: 'šØ Build', key: 'build' },
{ name: 'š¦ Deps', key: 'dependencies' },
{ name: 'š Security', key: 'security' },
{ name: 'š SEO', key: 'seo' },
{ name: 'ā” Speed', key: 'speed' }
];
categories.forEach(cat => {
const score = this.godModeData.scores?.[cat.key] || 0;
const icon = score >= 80 ? 'ā
' : score >= 60 ? 'ā ļø' : 'ā';
const trend = this.drawSparkline(this.godModeData.trends[cat.key] || []);
console.log(colors.muted(`ā ${cat.name.padEnd(11)} ā ${icon} ${String(score).padStart(3)} ā ${trend.padEnd(14)} ā`));
});
console.log(colors.muted('āāāāāāāāāāāāāāā“āāāāāāāāā“āāāāāāāāāāāāāāāāā'));
// Auto-refresh status
const refreshStatus = this.godModeData.autoRefresh ? colors.success('ON (30s)') : colors.muted('OFF');
console.log(colors.secondary(`\nš Auto-refresh: ${refreshStatus} | š¬ Recording: `) + colors.success('ON'));
// Session stats
console.log(colors.secondary('\nš Session Stats:'));
const sessionTime = Math.floor((Date.now() - this.stats.sessionStart) / 60000);
console.log(colors.muted(`⢠Session time: ${sessionTime}m | Files changed: ${this.stats.filesModified.size}`));
console.log(colors.muted(`⢠Issues fixed: ${this.godModeData.issuesFixed || 0} | Performance gain: ${this.godModeData.performanceGain || 0}%`));
this.showControlsFooter(colors, ['r - Run all checks', 'a - Toggle auto-refresh', '1-5 - Focus category', 'e - Export report', 'n - Notifications']);
}
async runGodModeAnalysis() {
this.lastGodCheck = Date.now();
const criticalIssues = [];
const scores = {};
try {
// Run all checks in parallel for speed
const [buildResult, depsResult, securityResult, seoResult, perfResult] = await Promise.all([
this.checkers.build.checkAll().catch(e => ({ error: e.message })),
this.checkers.dependencies.checkAll().catch(e => ({ error: e.message })),
this.checkers.security.checkAll().catch(e => ({ error: e.message })),
new SEOChecker(this.projectPath, { silent: true }).checkAll().catch(e => ({ error: e.message })),
this.checkers.performance.checkAll().catch(e => ({ error: e.message }))
]);
// Calculate scores
scores.build = buildResult.build?.status === 'passed' ? 100 : buildResult.build?.status === 'failed' ? 0 : 75;
scores.dependencies = 100;
if (depsResult.vulnerabilities?.vulnerabilities?.length > 0) {
scores.dependencies -= depsResult.vulnerabilities.vulnerabilities.length * 10;
}
scores.security = 100;
if (securityResult.godMode?.findings?.length > 0) {
scores.security -= securityResult.godMode.findings.length * 15;
}
if (securityResult.secrets?.findings?.length > 0) {
scores.security -= securityResult.secrets.findings.length * 20;
}
scores.seo = seoResult.overall?.score || 0;
scores.speed = 100;
if (perfResult.bundleSize?.totalSize > 2 * 1024 * 1024) {
scores.speed -= 30;
}
// Collect critical issues
if (securityResult.secrets?.findings?.length > 0) {
criticalIssues.push({
category: 'security',
severity: 'critical',
message: `API key exposed in ${securityResult.secrets.findings[0].file}`
});
}
if (perfResult.bundleSize?.totalSize > 3 * 1024 * 1024) {
criticalIssues.push({
category: 'performance',
severity: 'high',
message: `Bundle size ${this.formatSize(perfResult.bundleSize.totalSize)} (target: 2MB)`
});
}
if (seoResult.overall?.score < 60) {
const issues = seoResult.technical?.issues?.filter(i => i.severity === 'high') || [];
if (issues.length > 0) {
criticalIssues.push({
category: 'seo',
severity: 'high',
message: issues[0].message
});
}
}
// Calculate overall health
const weights = { build: 0.2, dependencies: 0.15, security: 0.25, seo: 0.2, speed: 0.2 };
let health = 0;
Object.entries(weights).forEach(([key, weight]) => {
health += (scores[key] || 0) * weight;
});
// Update trends
Object.keys(scores).forEach(key => {
if (!this.godModeData.trends[key]) {
this.godModeData.trends[key] = [];
}
this.godModeData.trends[key].push(scores[key]);
if (this.godModeData.trends[key].length > 10) {
this.godModeData.trends[key].shift();
}
});
// Determine overall trend
const oldHealth = this.godModeData.health || health;
this.godModeData.trend = health > oldHealth ? 'improving' : health < oldHealth ? 'declining' : 'stable';
this.godModeData.health = Math.round(health);
this.godModeData.scores = scores;
this.godModeData.criticalIssues = criticalIssues;
} catch (error) {
console.error('God mode analysis failed:', error);
}
}
drawSparkline(data) {
if (!data || data.length === 0) return 'āāāāāāāāāāā';
const chars = ['ā', 'ā', 'ā', 'ā', 'ā
', 'ā', 'ā', 'ā'];
const max = Math.max(...data);
const min = Math.min(...data);
const range = max - min || 1;
return data.map(value => {
const normalized = (value - min) / range;
const index = Math.floor(normalized * (chars.length - 1));
return chars[index];
}).join('');
}
getGrade(score) {
if (score >= 90) return 'A+';
if (score >= 85) return 'A';
if (score >= 80) return 'A-';
if (score >= 75) return 'B+';
if (score >= 70) return 'B';
if (score >= 65) return 'B-';
if (score >= 60) return 'C+';
if (score >= 55) return 'C';
if (score >= 50) return 'C-';
if (score >= 45) return 'D+';
if (score >= 40) return 'D';
return 'F';
}
async handleGodModeKeypress(key, str) {
if (!key && !str) return;
if (key && key.name === 'r') {
console.log(chalk.blue('\nš Running all checks...'));
await this.runGodModeAnalysis();
await this.updateDisplay();
} else if (key && key.name === 'a') {
// Toggle auto-refresh
this.godModeData.autoRefresh = !this.godModeData.autoRefresh;
if (this.godModeData.autoRefresh) {
console.log(chalk.green('\nā
Auto-refresh enabled (30s intervals)'));
this.godModeData.refreshInterval = setInterval(async () => {
await this.runGodModeAnalysis();
await this.updateDisplay();
}, 30000);
} else {
console.log(chalk.yellow('\nā Auto-refresh disabled'));
if (this.godModeData.refreshInterval) {
clearInterval(this.godModeData.refreshInterval);
}
}
} else if (str >= '1' && str <= '5' && !key.ctrl && !key.meta) {
// Jump to specific category
const categoryMap = { '1': 'checks', '2': 'checks', '3': 'security', '4': 'seo', '5': 'speed' };
this.currentMode = categoryMap[str];
this.currentModeIndex = this.modes.indexOf(this.currentMode);
await this.updateDisplay();
this.showModeNotification();
} else if (key && key.name === 'e') {
console.log(chalk.blue('\nš Export feature coming soon...'));
} else if (key && key.name === 'n') {
console.log(chalk.blue('\nš Notification settings coming soon...'));
} else if (str && !key.ctrl && !key.meta) {
process.stdout.write('\r\x1B[K');
}
}
}
export async function startUnifiedMonitor(projectPath) {
const monitor = new UnifiedMonitor(projectPath);
await monitor.start();
return monitor;
}