jay-code
Version:
Streamlined AI CLI orchestration engine with mathematical rigor and enterprise-grade reliability
514 lines (429 loc) • 15.8 kB
text/typescript
/**
* Jay-Code QA Agent: Statistical Code Analysis with Mathematical Scoring
* Implements information theory metrics and mathematical validation
*/
import { EventEmitter } from 'events';
export interface CodeAnalysis {
complexity: number;
maintainability: number;
testability: number;
performance: number;
security: number;
documentation: number;
overallScore: number;
issues: CodeIssue[];
metrics: CodeMetrics;
}
export interface CodeIssue {
type: 'error' | 'warning' | 'suggestion';
severity: number; // 0-1 scale
line?: number;
column?: number;
message: string;
rule: string;
fixSuggestion?: string;
}
export interface CodeMetrics {
linesOfCode: number;
cyclomaticComplexity: number;
cognitiveComplexity: number;
halsteadVolume: number;
maintainabilityIndex: number;
testCoverage?: number;
duplicateLines: number;
technicalDebt: number; // in hours
}
export interface QAReport {
analysisId: string;
timestamp: Date;
codeHash: string;
analysis: CodeAnalysis;
validationResults: ValidationResult[];
recommendations: Improvement[];
passThreshold: number;
passed: boolean;
}
export interface ValidationResult {
rule: string;
status: 'passed' | 'failed' | 'warning';
score: number;
details: string;
expectedValue?: any;
actualValue?: any;
}
export interface Improvement {
priority: 'high' | 'medium' | 'low';
category: string;
description: string;
impact: number; // Expected improvement in overall score
effort: number; // Estimated effort in hours
codeExample?: string;
}
export class QAAgent extends EventEmitter {
private passThreshold: number = 0.7;
private strictMode: boolean = false;
private autoFix: boolean = true;
constructor(options: {
passThreshold?: number;
strictMode?: boolean;
autoFix?: boolean;
} = {}) {
super();
this.passThreshold = options.passThreshold || 0.7;
this.strictMode = options.strictMode || false;
this.autoFix = options.autoFix || true;
}
/**
* Review code with statistical analysis and mathematical scoring
*/
async reviewCode(code: string, requirements: any): Promise<QAReport> {
const startTime = Date.now();
const codeHash = this.calculateCodeHash(code);
// Comprehensive code analysis with mathematical rigor
const analysis = await this.analyzeCode(code);
const validationResults = await this.validateAgainstRequirements(code, analysis, requirements);
const recommendations = this.generateRecommendations(analysis);
const passed = analysis.overallScore >= this.passThreshold;
const report: QAReport = {
analysisId: `qa_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`,
timestamp: new Date(),
codeHash,
analysis,
validationResults,
recommendations,
passThreshold: this.passThreshold,
passed
};
this.emit('review-completed', {
analysisId: report.analysisId,
passed,
score: analysis.overallScore,
duration: Date.now() - startTime
});
return report;
}
private calculateCodeHash(code: string): string {
// Simple hash for code versioning - could use crypto.createHash in production
let hash = 0;
for (let i = 0; i < code.length; i++) {
const char = code.charCodeAt(i);
hash = ((hash << 5) - hash) + char;
hash = hash & hash; // Convert to 32-bit integer
}
return Math.abs(hash).toString(16);
}
private async analyzeCode(code: string): Promise<CodeAnalysis> {
const metrics = this.calculateMetrics(code);
const issues = this.detectIssues(code);
// Mathematical scoring with weighted factors
const complexity = this.scoreComplexity(metrics);
const maintainability = this.scoreMaintainability(metrics, issues);
const testability = this.scoreTestability(code, metrics);
const performance = this.scorePerformance(code, issues);
const security = this.scoreSecurity(code, issues);
const documentation = this.scoreDocumentation(code);
// Weighted overall score using mathematical optimization
const weights = {
complexity: 0.20,
maintainability: 0.25,
testability: 0.15,
performance: 0.15,
security: 0.15,
documentation: 0.10
};
const overallScore =
complexity * weights.complexity +
maintainability * weights.maintainability +
testability * weights.testability +
performance * weights.performance +
security * weights.security +
documentation * weights.documentation;
return {
complexity,
maintainability,
testability,
performance,
security,
documentation,
overallScore,
issues,
metrics
};
}
private calculateMetrics(code: string): CodeMetrics {
const lines = code.split('\n');
const linesOfCode = lines.filter(line => line.trim().length > 0).length;
// Cyclomatic complexity calculation
const cyclomaticComplexity = this.calculateCyclomaticComplexity(code);
// Cognitive complexity (more human-focused than cyclomatic)
const cognitiveComplexity = this.calculateCognitiveComplexity(code);
// Halstead metrics for program volume
const halsteadVolume = this.calculateHalsteadVolume(code);
// Maintainability index using mathematical formula
const maintainabilityIndex = Math.max(0,
171 - 5.2 * Math.log(halsteadVolume) - 0.23 * cyclomaticComplexity - 16.2 * Math.log(linesOfCode)
) / 171;
const duplicateLines = this.countDuplicateLines(lines);
const technicalDebt = this.estimateTechnicalDebt(code, cyclomaticComplexity);
return {
linesOfCode,
cyclomaticComplexity,
cognitiveComplexity,
halsteadVolume,
maintainabilityIndex,
duplicateLines,
technicalDebt
};
}
private calculateCyclomaticComplexity(code: string): number {
// Count decision points: if, while, for, case, catch, &&, ||, ?
const patterns = [
/\bif\b/g, /\bwhile\b/g, /\bfor\b/g, /\bcase\b/g, /\bcatch\b/g,
/&&/g, /\|\|/g, /\?/g, /\belif\b/g, /\belse\s+if\b/g
];
let complexity = 1; // Base complexity
for (const pattern of patterns) {
const matches = code.match(pattern);
if (matches) complexity += matches.length;
}
return complexity;
}
private calculateCognitiveComplexity(code: string): number {
// Simplified cognitive complexity - considers nesting and logical operators
let complexity = 0;
let nestingLevel = 0;
const lines = code.split('\n');
for (const line of lines) {
// Track nesting level
const openBraces = (line.match(/{/g) || []).length;
const closeBraces = (line.match(/}/g) || []).length;
nestingLevel += openBraces - closeBraces;
// Add complexity for control structures with nesting penalty
if (/\b(if|while|for|switch)\b/.test(line)) {
complexity += 1 + nestingLevel;
}
// Logical operators add complexity
const logicalOps = (line.match(/&&|\|\|/g) || []).length;
complexity += logicalOps;
}
return complexity;
}
private calculateHalsteadVolume(code: string): number {
// Simplified Halstead volume calculation
const operators = code.match(/[+\-*/%=<>!&|^~(){}[\];,.:?]/g) || [];
const operands = code.match(/\b[a-zA-Z_$][a-zA-Z0-9_$]*\b/g) || [];
const uniqueOperators = new Set(operators).size;
const uniqueOperands = new Set(operands).size;
const totalOperators = operators.length;
const totalOperands = operands.length;
const vocabulary = uniqueOperators + uniqueOperands;
const length = totalOperators + totalOperands;
return vocabulary > 0 && length > 0 ? length * Math.log2(vocabulary) : 0;
}
private countDuplicateLines(lines: string[]): number {
const lineMap = new Map<string, number>();
let duplicates = 0;
for (const line of lines) {
const trimmed = line.trim();
if (trimmed.length > 5) { // Only consider meaningful lines
const count = lineMap.get(trimmed) || 0;
lineMap.set(trimmed, count + 1);
if (count === 1) duplicates++;
}
}
return duplicates;
}
private estimateTechnicalDebt(code: string, complexity: number): number {
// Estimate technical debt in hours based on various factors
let debt = 0;
// Complexity debt
debt += Math.max(0, (complexity - 10) * 0.5);
// Documentation debt
const comments = (code.match(/\/\*[\s\S]*?\*\/|\/\/.*$/gm) || []).length;
const codeLines = code.split('\n').filter(l => l.trim()).length;
const commentRatio = comments / Math.max(codeLines, 1);
if (commentRatio < 0.1) debt += 2; // Poor documentation
// TODO and FIXME debt
const todos = (code.match(/TODO|FIXME|HACK/gi) || []).length;
debt += todos * 0.5;
return debt;
}
private detectIssues(code: string): CodeIssue[] {
const issues: CodeIssue[] = [];
const lines = code.split('\n');
// Static analysis patterns
const patterns = [
{
pattern: /console\.log|debugger/gi,
message: 'Debug statement found in production code',
type: 'warning' as const,
severity: 0.3,
rule: 'no-debug'
},
{
pattern: /eval\(/gi,
message: 'Use of eval() is dangerous and should be avoided',
type: 'error' as const,
severity: 0.9,
rule: 'no-eval'
},
{
pattern: /var\s+/gi,
message: 'Use let/const instead of var',
type: 'suggestion' as const,
severity: 0.2,
rule: 'prefer-const'
}
];
lines.forEach((line, index) => {
for (const { pattern, message, type, severity, rule } of patterns) {
if (pattern.test(line)) {
issues.push({
type,
severity,
line: index + 1,
message,
rule,
fixSuggestion: this.generateFixSuggestion(rule, line)
});
}
}
});
return issues;
}
private generateFixSuggestion(rule: string, line: string): string {
switch (rule) {
case 'no-debug':
return 'Remove debug statement or replace with proper logging';
case 'no-eval':
return 'Replace eval() with safer alternatives like JSON.parse()';
case 'prefer-const':
return line.replace(/var\s+/, 'const ');
default:
return 'Follow best practices for this rule';
}
}
private scoreComplexity(metrics: CodeMetrics): number {
// Lower complexity is better, normalize to 0-1 scale
const cyclomaticScore = Math.max(0, 1 - (metrics.cyclomaticComplexity - 1) / 20);
const cognitiveScore = Math.max(0, 1 - metrics.cognitiveComplexity / 30);
return (cyclomaticScore + cognitiveScore) / 2;
}
private scoreMaintainability(metrics: CodeMetrics, issues: CodeIssue[]): number {
// Start with maintainability index
let score = metrics.maintainabilityIndex;
// Penalize for high-severity issues
const severityPenalty = issues
.filter(i => i.severity > 0.7)
.reduce((sum, i) => sum + i.severity, 0) * 0.1;
score -= severityPenalty;
// Penalize for duplicates
const duplicatePenalty = Math.min(metrics.duplicateLines / metrics.linesOfCode, 0.3);
score -= duplicatePenalty;
return Math.max(0, Math.min(1, score));
}
private scoreTestability(code: string, metrics: CodeMetrics): number {
// Higher testability for lower complexity and better structure
let score = 1 - (metrics.cyclomaticComplexity - 1) / 20;
// Bonus for dependency injection patterns
if (/constructor.*inject|@Injectable/i.test(code)) score += 0.1;
// Penalty for static methods and global variables
const staticMethods = (code.match(/static\s+\w+/g) || []).length;
const globalVars = (code.match(/window\.|global\./g) || []).length;
score -= (staticMethods + globalVars) * 0.05;
return Math.max(0, Math.min(1, score));
}
private scorePerformance(code: string, issues: CodeIssue[]): number {
let score = 1.0;
// Check for performance anti-patterns
const performanceIssues = [
{ pattern: /for.*in.*Object/, penalty: 0.1 },
{ pattern: /\.innerHTML\s*=/, penalty: 0.05 },
{ pattern: /document\.getElementById/, penalty: 0.02 }
];
for (const { pattern, penalty } of performanceIssues) {
const matches = (code.match(pattern) || []).length;
score -= matches * penalty;
}
return Math.max(0, score);
}
private scoreSecurity(code: string, issues: CodeIssue[]): number {
let score = 1.0;
// Security issue penalties
const securityScore = issues
.filter(i => ['no-eval', 'no-innerHTML'].includes(i.rule))
.reduce((sum, i) => sum + i.severity, 0);
score -= securityScore * 0.2;
return Math.max(0, score);
}
private scoreDocumentation(code: string): number {
const lines = code.split('\n').filter(l => l.trim());
const comments = (code.match(/\/\*[\s\S]*?\*\/|\/\/.*$/gm) || []).length;
const functions = (code.match(/function\s+\w+|=>\s*{|\w+\s*\(/g) || []).length;
// Documentation ratio
const commentRatio = comments / Math.max(lines.length, 1);
const functionDocRatio = comments / Math.max(functions, 1);
const score = Math.min(1, commentRatio * 5 + functionDocRatio * 0.5);
return Math.max(0, score);
}
private async validateAgainstRequirements(
code: string,
analysis: CodeAnalysis,
requirements: any
): Promise<ValidationResult[]> {
const results: ValidationResult[] = [];
// Standard validation rules
results.push({
rule: 'complexity-threshold',
status: analysis.complexity >= 0.6 ? 'passed' : 'failed',
score: analysis.complexity,
details: `Code complexity score: ${analysis.complexity.toFixed(2)}`
});
results.push({
rule: 'maintainability-threshold',
status: analysis.maintainability >= 0.7 ? 'passed' : 'failed',
score: analysis.maintainability,
details: `Maintainability score: ${analysis.maintainability.toFixed(2)}`
});
return results;
}
private generateRecommendations(analysis: CodeAnalysis): Improvement[] {
const recommendations: Improvement[] = [];
if (analysis.complexity < 0.6) {
recommendations.push({
priority: 'high',
category: 'complexity',
description: 'Reduce cyclomatic complexity by extracting methods',
impact: 0.2,
effort: 4,
codeExample: 'Extract complex conditional logic into separate methods'
});
}
if (analysis.documentation < 0.3) {
recommendations.push({
priority: 'medium',
category: 'documentation',
description: 'Add comprehensive documentation and comments',
impact: 0.15,
effort: 2,
codeExample: 'Add JSDoc comments to all public methods'
});
}
return recommendations;
}
async validateOutput(result: any, expected: any): Promise<ValidationResult> {
// Simple output validation
const passed = JSON.stringify(result) === JSON.stringify(expected);
return {
rule: 'output-validation',
status: passed ? 'passed' : 'failed',
score: passed ? 1 : 0,
details: passed ? 'Output matches expected result' : 'Output differs from expected result',
expectedValue: expected,
actualValue: result
};
}
async suggestImprovements(analysis: CodeAnalysis): Promise<Improvement[]> {
return this.generateRecommendations(analysis);
}
}