Initial commit: ai-code-trust CLI tool v1.0.0

This commit is contained in:
BunBun Labs
2026-06-18 00:23:00 +00:00
commit 75e045cd04
9 changed files with 1076 additions and 0 deletions
+208
View File
@@ -0,0 +1,208 @@
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<AnalysisConfig> = {}): 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;
}
}