import * as fs from 'fs'; import * as path from 'path'; import { detectPatterns, getSeverityCounts, DetectionResult } from './detectors.js'; export interface FileResult { file: string; lines: number; detection: DetectionResult; trustScore: number; // 0-100, higher = more trustworthy } export interface AnalysisConfig { include: string[]; exclude: string[]; maxSize: number; // max file size in bytes minScore: number; // minimum trust score threshold } export interface AnalysisSummary { totalFiles: number; scannedFiles: number; totalLines: number; issuesFound: number; criticalIssues: number; highIssues: number; mediumIssues: number; lowIssues: number; averageTrustScore: number; filesWithIssues: number; cleanFiles: number; riskLevel: 'low' | 'medium' | 'high' | 'critical'; fileResults: FileResult[]; } const DEFAULT_CONFIG: AnalysisConfig = { include: ['.js', '.ts', '.jsx', '.tsx', '.py', '.java', '.go', '.rb', '.php', '.cs', '.cpp', '.c', '.swift', '.kt', '.rs'], exclude: ['node_modules', 'dist', '.git', 'vendor', 'build', 'coverage', '.next', 'out', '__pycache__', '.venv', 'venv'], maxSize: 1024 * 1024, // 1MB minScore: 50 }; function shouldScan(filePath: string, config: AnalysisConfig): boolean { const ext = path.extname(filePath).toLowerCase(); if (!config.include.includes(ext)) return false; const parts = filePath.split(path.sep); for (const ex of config.exclude) { if (parts.some(part => part === ex)) return false; } return true; } function calculateTrustScore(detection: DetectionResult, lines: number): number { const counts = getSeverityCounts(detection.patterns); // Base score: 100 let score = 100; // Deduct for each severity level score -= counts.critical * 25; score -= counts.high * 15; score -= counts.medium * 8; score -= counts.low * 2; // Deduct for AI pattern density (patterns per 100 lines) const density = (detection.patterns.length / Math.max(lines, 1)) * 100; score -= Math.min(density * 3, 30); // cap at 30 points // Boost for very clean files if (detection.patterns.length === 0) { score += 5; // bonus for clean file } // Clamp to 0-100 return Math.max(0, Math.min(100, Math.round(score))); } function analyzeFile(filePath: string, basePath: string): FileResult | null { try { const stat = fs.statSync(filePath); if (stat.size > DEFAULT_CONFIG.maxSize) { return null; // skip large files } const content = fs.readFileSync(filePath, 'utf-8'); const lines = content.split('\n').length; const detection = detectPatterns(content); const trustScore = calculateTrustScore(detection, lines); return { file: path.relative(basePath, filePath), lines, detection, trustScore }; } catch (err) { return null; // skip unreadable files } } export function analyzeDirectory(dirPath: string, config: Partial = {}): AnalysisSummary { const fullConfig = { ...DEFAULT_CONFIG, ...config }; const results: FileResult[] = []; let totalFiles = 0; let totalLines = 0; function scanDir(dir: string) { const entries = fs.readdirSync(dir, { withFileTypes: true }); for (const entry of entries) { const fullPath = path.join(dir, entry.name); if (entry.isDirectory()) { let skip = false; for (const ex of fullConfig.exclude) { if (entry.name === ex) { skip = true; break; } } if (!skip) { scanDir(fullPath); } } else if (entry.isFile()) { totalFiles++; if (shouldScan(fullPath, fullConfig)) { const result = analyzeFile(fullPath, dirPath); if (result) { results.push(result); totalLines += result.lines; } } } } } scanDir(dirPath); // Calculate summary let issuesFound = 0; let criticalIssues = 0; let highIssues = 0; let mediumIssues = 0; let lowIssues = 0; let filesWithIssues = 0; let totalTrustScore = 0; for (const result of results) { const counts = getSeverityCounts(result.detection.patterns); issuesFound += result.detection.patterns.length; criticalIssues += counts.critical || 0; highIssues += counts.high || 0; mediumIssues += counts.medium || 0; lowIssues += counts.low || 0; if (result.detection.patterns.length > 0) { filesWithIssues++; } totalTrustScore += result.trustScore; } const averageTrustScore = results.length > 0 ? Math.round(totalTrustScore / results.length) : 100; // Determine risk level let riskLevel: 'low' | 'medium' | 'high' | 'critical' = 'low'; if (criticalIssues > 0) { riskLevel = 'critical'; } else if (highIssues > 0 || averageTrustScore < 50) { riskLevel = 'high'; } else if (mediumIssues > 0 || averageTrustScore < 70) { riskLevel = 'medium'; } return { totalFiles, scannedFiles: results.length, totalLines, issuesFound, criticalIssues, highIssues, mediumIssues, lowIssues, averageTrustScore, filesWithIssues, cleanFiles: results.length - filesWithIssues, riskLevel, fileResults: results.sort((a, b) => a.trustScore - b.trustScore) // sort by trust score ascending }; } export function analyzeFileContent(filePath: string, content?: string): FileResult | null { try { const actualContent = content || fs.readFileSync(filePath, 'utf-8'); const lines = actualContent.split('\n').length; const detection = detectPatterns(actualContent); const trustScore = calculateTrustScore(detection, lines); return { file: path.basename(filePath), lines, detection, trustScore }; } catch (err) { return null; } }