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
+5
View File
@@ -0,0 +1,5 @@
node_modules/
dist/
.venv/
*.log
.verdict
+51
View File
@@ -0,0 +1,51 @@
{
"name": "ai-code-trust",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "ai-code-trust",
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@types/node": "^25.9.3"
},
"bin": {
"ai-code-trust": "dist/index.js"
},
"devDependencies": {
"typescript": "^5.4.0"
}
},
"node_modules/@types/node": {
"version": "25.9.3",
"resolved": "https://registry.npmjs.org/@types/node/-/node-25.9.3.tgz",
"integrity": "sha512-603BddQMv3pUcr4U2dhujk83N2tTDVr/34wII2B6bJy6g+8WD6yUb11jszNs0gdi4PesVWl7ABt8nYMVpnLUcg==",
"license": "MIT",
"dependencies": {
"undici-types": ">=7.24.0 <7.24.7"
}
},
"node_modules/typescript": {
"version": "5.9.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "7.24.6",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.24.6.tgz",
"integrity": "sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==",
"license": "MIT"
}
}
}
+29
View File
@@ -0,0 +1,29 @@
{
"name": "ai-code-trust",
"version": "1.0.0",
"description": "CLI tool to audit and assess trust in AI-generated code",
"main": "dist/index.js",
"bin": {
"ai-code-trust": "dist/index.js"
},
"scripts": {
"build": "tsc",
"test": "node --test dist/**/*.test.js",
"lint": "tsc --noEmit"
},
"keywords": [
"ai",
"code",
"trust",
"audit",
"security"
],
"author": "BunBun Labs",
"license": "MIT",
"devDependencies": {
"typescript": "^5.4.0"
},
"dependencies": {
"@types/node": "^25.9.3"
}
}
+102
View File
@@ -0,0 +1,102 @@
import { test } from 'node:test';
import assert from 'node:assert';
import { detectPatterns, getSeverityCounts, PatternMatch } from './detectors.js';
import { analyzeFileContent, analyzeDirectory } from './analyzer.js';
test('detectPatterns finds AI comment patterns', () => {
const code = `
// This function handles the main logic
function main() {
return 42;
}
`;
const result = detectPatterns(code);
assert(result.patterns.length > 0, 'Should detect AI comment pattern');
assert(result.patterns.some(p => p.description.includes('AI comment')));
});
test('detectPatterns finds critical SQL injection', () => {
const code = `
const query = "SELECT * FROM users WHERE id = " + userId;
db.execute(query);
`;
const result = detectPatterns(code);
assert(result.patterns.some(p => p.severity === 'critical' && p.description.includes('SQL injection')));
});
test('detectPatterns finds hardcoded credentials', () => {
const code = `const apiKey = "sk-1234567890abcdef";
const password = "secret123";
`;
const result = detectPatterns(code);
assert(result.patterns.some(p => p.severity === 'critical' && p.description.includes('credential')));
});
test('detectPatterns finds eval usage', () => {
const code = `eval(userInput);`;
const result = detectPatterns(code);
assert(result.patterns.some(p => p.severity === 'critical' && p.description.includes('eval')));
});
test('analyzeFileContent analyzes a single file', () => {
const code = `const password = "hardcoded123";
// This function handles authentication
function auth() {
return true;
}
`;
const result = analyzeFileContent('test.js', code);
assert(result !== null);
assert(result!.trustScore < 100); // should have deductions
assert(result!.detection.patterns.length > 0);
});
test('analyzeDirectory returns summary', () => {
// This test runs against the actual src directory
const summary = analyzeDirectory('./src');
assert(summary.scannedFiles > 0);
assert(summary.totalLines > 0);
assert(summary.averageTrustScore >= 0 && summary.averageTrustScore <= 100);
assert(['low', 'medium', 'high', 'critical'].includes(summary.riskLevel));
});
test('getSeverityCounts aggregates correctly', () => {
const patterns: PatternMatch[] = [
{ severity: 'low', line: 1, description: 'x', pattern: 'x' },
{ severity: 'low', line: 2, description: 'x', pattern: 'x' },
{ severity: 'high', line: 3, description: 'x', pattern: 'x' },
];
const counts = getSeverityCounts(patterns);
assert.strictEqual(counts.low, 2);
assert.strictEqual(counts.medium, 0);
assert.strictEqual(counts.high, 1);
assert.strictEqual(counts.critical, 0);
});
test('clean code gets high trust score', () => {
const code = `
function add(a, b) {
return a + b;
}
`;
const result = detectPatterns(code);
assert(result.confidence < 0.5); // low confidence of AI generation
const file = analyzeFileContent('clean.js', code);
assert(file !== null);
assert(file!.trustScore >= 90, `Expected >= 90, got ${file!.trustScore}`);
});
test('trust score penalizes multiple issues', () => {
const code = `
// This function handles user authentication
function login(userId, password) {
const query = "SELECT * FROM users WHERE id = " + userId;
eval(password);
return query;
}
`;
const file = analyzeFileContent('bad.js', code);
assert(file !== null);
assert(file!.trustScore < 50, `Expected < 50, got ${file!.trustScore}`);
assert(file!.detection.patterns.some(p => p.severity === 'critical'));
});
+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;
}
}
+261
View File
@@ -0,0 +1,261 @@
// AI-generated code pattern detectors
// Heuristic-based detection of common AI coding tool patterns
export interface PatternMatch {
pattern: string;
severity: 'low' | 'medium' | 'high' | 'critical';
description: string;
line: number;
}
export interface DetectionResult {
confidence: number; // 0-1
patterns: PatternMatch[];
}
// Known AI-generated code signatures
const AI_PATTERNS = [
// Overly verbose comments explaining obvious code
{
regex: /\/\/\s*(This function|This method|This class|This variable|This code|This loop|This block)/i,
severity: 'low' as const,
description: 'Boilerplate AI comment pattern'
},
// Excessive inline documentation
{
regex: /\/\/.\s*(defines|creates|initializes|sets up|handles|manages|processes|implements)/i,
severity: 'low' as const,
description: 'Explanatory comment pattern typical of AI output'
},
// Generic error handling without context
{
regex: /catch\s*\([^)]*\)\s*\{\s*\n?\s*(console\.error|console\.log|print)\s*\(/i,
severity: 'medium' as const,
description: 'Generic catch-all error logging without meaningful handling'
},
// TODO/FIXME comments from incomplete generation
{
regex: /(TODO|FIXME|HACK|XXX|NOTE|REVIEW)\s*:/i,
severity: 'medium' as const,
description: 'Incomplete implementation marker'
},
// Suspicious variable names
{
regex: /\b(data|result|output|input|value|item|temp|tmp|obj|arr|str|num|val|func|cb)\d*\b/g,
severity: 'low' as const,
description: 'Generic variable naming pattern'
},
// Overly defensive null checks in sequence
{
regex: /if\s*\(\s*[^)]+\s*!==?\s*(null|undefined|None)\s*\)\s*\{[^}]*\}\s*(?:else\s*\{[^}]*\})?/g,
severity: 'low' as const,
description: 'Defensive null checking pattern'
},
// Copy-paste license headers without context
{
regex: /MIT License|Apache License|BSD License|GNU General Public License/i,
severity: 'low' as const,
description: 'License header (may be auto-inserted)'
},
// JSDoc with AI-style verbose descriptions
{
regex: /\/\*\*\s*\n\s*\*\s*(This is|This function|A utility|Helper function|Main function)/i,
severity: 'low' as const,
description: 'Verbose JSDoc pattern'
},
// Suspicious date handling
{
regex: /new\s+Date\s*\(\s*\)\s*\.[a-zA-Z]+\s*\(/i,
severity: 'low' as const,
description: 'Date manipulation without timezone consideration'
},
// JSON.parse without try-catch
{
regex: /JSON\.parse\s*\([^)]+\)\s*(?!\s*catch)/i,
severity: 'high' as const,
description: 'JSON.parse without error handling — common AI-generated security risk'
},
// eval or Function constructor usage
{
regex: /\beval\s*\(|new\s+Function\s*\(/i,
severity: 'critical' as const,
description: 'Dangerous eval/Function constructor usage'
},
// SQL string concatenation
{
regex: /(?:SELECT|INSERT|UPDATE|DELETE).*(?:\+|\$\{|`[^`]*\$\{)/i,
severity: 'critical' as const,
description: 'Potential SQL injection via string concatenation'
},
// Hardcoded credentials
{
regex: /(?:password|secret|token|api_key|apikey|access_token)\s*[:=]\s*["'][^"']{4,}["']/i,
severity: 'critical' as const,
description: 'Hardcoded credential detected'
},
// Insecure randomness
{
regex: /Math\.random\s*\(\s*\)/i,
severity: 'medium' as const,
description: 'Math.random() used for security-sensitive operation'
},
// Prototype pollution
{
regex: /Object\.prototype\.\w+\s*=/i,
severity: 'high' as const,
description: 'Prototype modification — potential prototype pollution'
},
// Disabled security (unsafe-inline, etc.)
{
regex: /unsafe-inline|unsafe-eval|'unsafe-inline'|'unsafe-eval'/i,
severity: 'high' as const,
description: 'Disabled security policy in configuration'
},
// Clipboard access without permission check
{
regex: /navigator\.clipboard\.writeText|document\.execCommand\s*\(\s*['"]copy['"]/i,
severity: 'medium' as const,
description: 'Clipboard access without explicit permission handling'
},
// File path traversal
{
regex: /(?:readFile|writeFile|createReadStream|createWriteStream)\s*\(\s*[^)]*(?:\+|\$\{|req\.|request\.)/i,
severity: 'high' as const,
description: 'Potential path traversal via user input'
},
// Missing CSRF protection
{
regex: /fetch\s*\(\s*[^)]*\)\s*\{[^}]*method\s*:\s*['"]POST['"]/i,
severity: 'medium' as const,
description: 'POST request without visible CSRF token'
},
// InnerHTML with user input
{
regex: /\.innerHTML\s*=\s*[^;]*(?:\+|\$\{|req\.|request\.)/i,
severity: 'high' as const,
description: 'XSS risk: innerHTML with user-controlled input'
},
// Regex DoS
{
regex: /\([^)]*\+\+?[^)]*\)[\*\+]\+?/,
severity: 'medium' as const,
description: 'Potentially catastrophic regex (ReDoS risk)'
},
// Weak crypto
{
regex: /md5\s*\(|sha1\s*\(|createHash\s*\(\s*['"]md5['"]/i,
severity: 'high' as const,
description: 'Weak cryptographic hash function'
},
// Process exit in error handler
{
regex: /process\.exit\s*\(/i,
severity: 'medium' as const,
description: 'Process termination in error path'
},
// Unhandled promise rejection pattern
{
regex: /new\s+Promise\s*\([^)]*\)\s*\{[^}]*\}(?!\s*catch)/i,
severity: 'medium' as const,
description: 'Promise without catch or error handler'
},
// Arbitrary command execution
{
regex: /exec\s*\(|execSync\s*\(|spawn\s*\(\s*[^)]*(?:\+|\$\{|req\.|request\.)/i,
severity: 'critical' as const,
description: 'Command execution with user input — RCE risk'
},
// Disabled SSL verification
{
regex: /rejectUnauthorized\s*:\s*false|NODE_TLS_REJECT_UNAUTHORIZED\s*=\s*['"]0['"]/i,
severity: 'critical' as const,
description: 'SSL certificate verification disabled'
},
// Open redirect
{
regex: /res\.redirect\s*\(\s*[^)]*(?:\+|req\.|request\.|\$\{)/i,
severity: 'high' as const,
description: 'Open redirect via user input'
},
// Information disclosure
{
regex: /res\.json\s*\(\s*\{[^}]*error[^}]*\}\s*\)/i,
severity: 'medium' as const,
description: 'Verbose error response may leak sensitive information'
},
// Race condition in file operations
{
regex: /access\s*\([^)]+\)\s*\{[^}]*readFile|writeFile/i,
severity: 'medium' as const,
description: 'Time-of-check time-of-use (TOCTOU) race condition'
},
// Missing authorization check
{
regex: /app\.(get|post|put|delete|patch)\s*\([^)]+\)\s*,\s*\(?\s*req\s*,\s*res\s*\)?\s*=>/i,
severity: 'medium' as const,
description: 'Route handler without visible authorization middleware'
},
// Mass assignment
{
regex: /Object\.assign\s*\(\s*[^,]+,\s*req\.(body|query|params)/i,
severity: 'high' as const,
description: 'Mass assignment from user input'
},
// Timing attack vulnerability
{
regex: /===\s*['"][^'"]+['"]/i,
severity: 'low' as const,
description: 'String comparison may be vulnerable to timing attacks'
}
];
export function detectPatterns(content: string): DetectionResult {
const patterns: PatternMatch[] = [];
const lines = content.split('\n');
let aiScore = 0;
let securityScore = 0;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
const lineNum = i + 1;
for (const pattern of AI_PATTERNS) {
const regex = new RegExp(pattern.regex.source, 'i');
if (regex.test(line)) {
patterns.push({
pattern: pattern.regex.source,
severity: pattern.severity,
description: pattern.description,
line: lineNum
});
if (['low', 'medium'].includes(pattern.severity)) {
aiScore += 0.05;
} else {
securityScore += 0.15;
}
}
}
}
// Normalize scores
aiScore = Math.min(aiScore, 1.0);
securityScore = Math.min(securityScore, 1.0);
// Combined confidence: weighted toward security issues
const confidence = (aiScore * 0.3) + (securityScore * 0.7);
return {
confidence: Math.min(confidence, 1.0),
patterns
};
}
export function getSeverityCounts(patterns: PatternMatch[]): Record<string, number> {
const counts: Record<string, number> = { low: 0, medium: 0, high: 0, critical: 0 };
for (const p of patterns) {
counts[p.severity] = (counts[p.severity] || 0) + 1;
}
return counts;
}
+168
View File
@@ -0,0 +1,168 @@
#!/usr/bin/env node
import * as fs from 'fs';
import * as path from 'path';
import { analyzeDirectory, analyzeFileContent, AnalysisSummary } from './analyzer.js';
import { generateConsoleReport, generateJSONReport, generateMarkdownReport } from './report.js';
interface CliOptions {
target: string;
format: 'console' | 'json' | 'markdown';
output?: string;
include?: string[];
exclude?: string[];
threshold?: number;
help: boolean;
version: boolean;
}
function parseArgs(): CliOptions {
const args = process.argv.slice(2);
const options: CliOptions = {
target: '.',
format: 'console',
help: false,
version: false
};
for (let i = 0; i < args.length; i++) {
const arg = args[i];
if (arg === '--help' || arg === '-h') {
options.help = true;
} else if (arg === '--version' || arg === '-v') {
options.version = true;
} else if (arg === '--format' || arg === '-f') {
const format = args[++i];
if (format === 'json' || format === 'markdown' || format === 'console') {
options.format = format;
}
} else if (arg === '--output' || arg === '-o') {
options.output = args[++i];
} else if (arg === '--include') {
options.include = args[++i].split(',').map((s: string) => s.trim().startsWith('.') ? s.trim() : '.' + s.trim());
} else if (arg === '--exclude') {
options.exclude = args[++i].split(',').map((s: string) => s.trim());
} else if (arg === '--threshold') {
options.threshold = parseInt(args[++i], 10);
} else if (!arg.startsWith('-')) {
options.target = arg;
}
}
return options;
}
function showHelp(): void {
console.log(`
ai-code-trust — Audit and assess trust in AI-generated code
USAGE:
ai-code-trust [target] [options]
OPTIONS:
-h, --help Show this help message
-v, --version Show version
-f, --format <type> Output format: console (default), json, markdown
-o, --output <file> Write output to file instead of stdout
--include <exts> Comma-separated extensions to scan (default: common code types)
--exclude <dirs> Comma-separated directories to exclude
--threshold <score> Minimum trust score threshold (0-100)
EXAMPLES:
ai-code-trust ./src
ai-code-trust . --format json --output report.json
ai-code-trust ./src --include .ts,.js --exclude node_modules,dist
ai-code-trust ./src --threshold 60
DESCRIPTION:
Scans code for AI-generation patterns and security vulnerabilities,
assigns trust scores (0-100), and generates actionable reports.
`);
}
function showVersion(): void {
console.log('ai-code-trust v1.0.0');
}
function main(): void {
const options = parseArgs();
if (options.help) {
showHelp();
process.exit(0);
}
if (options.version) {
showVersion();
process.exit(0);
}
const targetPath = path.resolve(options.target);
if (!fs.existsSync(targetPath)) {
console.error(`Error: Target path does not exist: ${targetPath}`);
process.exit(1);
}
let summary: AnalysisSummary;
if (fs.statSync(targetPath).isFile()) {
const fileResult = analyzeFileContent(targetPath);
if (!fileResult) {
console.error('Error: Could not analyze file');
process.exit(1);
}
summary = {
totalFiles: 1,
scannedFiles: 1,
totalLines: fileResult.lines,
issuesFound: fileResult.detection.patterns.length,
criticalIssues: fileResult.detection.patterns.filter(p => p.severity === 'critical').length,
highIssues: fileResult.detection.patterns.filter(p => p.severity === 'high').length,
mediumIssues: fileResult.detection.patterns.filter(p => p.severity === 'medium').length,
lowIssues: fileResult.detection.patterns.filter(p => p.severity === 'low').length,
averageTrustScore: fileResult.trustScore,
filesWithIssues: fileResult.detection.patterns.length > 0 ? 1 : 0,
cleanFiles: fileResult.detection.patterns.length > 0 ? 0 : 1,
riskLevel: fileResult.trustScore < 40 ? 'critical' : fileResult.trustScore < 60 ? 'high' : fileResult.trustScore < 80 ? 'medium' : 'low',
fileResults: [fileResult]
};
} else {
const config: { include?: string[]; exclude?: string[]; minScore?: number } = {};
if (options.include) config.include = options.include;
if (options.exclude) config.exclude = options.exclude;
if (options.threshold !== undefined) config.minScore = options.threshold;
summary = analyzeDirectory(targetPath, config);
}
let output: string;
switch (options.format) {
case 'json':
output = generateJSONReport(summary);
break;
case 'markdown':
output = generateMarkdownReport(summary);
break;
default:
output = generateConsoleReport(summary);
}
if (options.output) {
fs.writeFileSync(path.resolve(options.output), output, 'utf-8');
console.log(`Report written to: ${options.output}`);
} else {
console.log(output);
}
// Exit with non-zero if critical issues found
if (summary.criticalIssues > 0) {
process.exit(2);
}
process.exit(0);
}
main();
+233
View File
@@ -0,0 +1,233 @@
import { AnalysisSummary, FileResult } from './analyzer.js';
import { PatternMatch } from './detectors.js';
export function generateConsoleReport(summary: AnalysisSummary): string {
const lines: string[] = [];
lines.push('═'.repeat(60));
lines.push(' AI CODE TRUST AUDIT REPORT');
lines.push('═'.repeat(60));
lines.push('');
// Risk level with color indicator
const riskEmoji = {
low: '✅',
medium: '⚠️ ',
high: '🔴',
critical: '☠️ '
};
lines.push(`OVERALL RISK LEVEL: ${riskEmoji[summary.riskLevel]} ${summary.riskLevel.toUpperCase()}`);
lines.push(`Average Trust Score: ${summary.averageTrustScore}/100`);
lines.push('');
lines.push('─'.repeat(60));
lines.push('SUMMARY');
lines.push('─'.repeat(60));
lines.push(` Files scanned: ${summary.scannedFiles} / ${summary.totalFiles}`);
lines.push(` Total lines: ${summary.totalLines.toLocaleString()}`);
lines.push(` Files with issues: ${summary.filesWithIssues}`);
lines.push(` Clean files: ${summary.cleanFiles}`);
lines.push(` Issues found: ${summary.issuesFound}`);
lines.push('');
lines.push('─'.repeat(60));
lines.push('ISSUE BREAKDOWN');
lines.push('─'.repeat(60));
lines.push(` 🔴 Critical: ${summary.criticalIssues}`);
lines.push(` 🔴 High: ${summary.highIssues}`);
lines.push(` ⚠️ Medium: ${summary.mediumIssues}`);
lines.push(` ️ Low: ${summary.lowIssues}`);
lines.push('');
// Top files by risk
if (summary.fileResults.length > 0) {
lines.push('─'.repeat(60));
lines.push('FILES BY TRUST SCORE (lowest first)');
lines.push('─'.repeat(60));
const topFiles = summary.fileResults.slice(0, 20); // show top 20
for (const file of topFiles) {
const scoreEmoji = file.trustScore >= 80 ? '✅' : file.trustScore >= 60 ? '⚠️ ' : file.trustScore >= 40 ? '🔴' : '☠️ ';
lines.push(` ${scoreEmoji} ${file.trustScore.toString().padStart(3)} ${file.file} (${file.lines} lines, ${file.detection.patterns.length} issues)`);
}
if (summary.fileResults.length > 20) {
lines.push(` ... and ${summary.fileResults.length - 20} more files`);
}
lines.push('');
}
// Detailed issue report for critical and high
const criticalHighFiles = summary.fileResults.filter(
f => f.detection.patterns.some(p => p.severity === 'critical' || p.severity === 'high')
);
if (criticalHighFiles.length > 0) {
lines.push('─'.repeat(60));
lines.push('CRITICAL & HIGH SEVERITY DETAILS');
lines.push('─'.repeat(60));
for (const file of criticalHighFiles) {
const criticalIssues = file.detection.patterns.filter(p => p.severity === 'critical' || p.severity === 'high');
if (criticalIssues.length > 0) {
lines.push(`\n ${file.file} (trust score: ${file.trustScore})`);
for (const issue of criticalIssues) {
const severityEmoji = issue.severity === 'critical' ? '☠️ ' : '🔴';
lines.push(` ${severityEmoji} [${issue.severity.toUpperCase()}] Line ${issue.line}: ${issue.description}`);
}
}
}
lines.push('');
}
// Recommendations
lines.push('─'.repeat(60));
lines.push('RECOMMENDATIONS');
lines.push('─'.repeat(60));
if (summary.criticalIssues > 0) {
lines.push(' ☠️ CRITICAL: Fix all critical issues immediately before deploying.');
}
if (summary.highIssues > 0) {
lines.push(' 🔴 HIGH: Review and fix high-severity issues before code review.');
}
if (summary.mediumIssues > 0) {
lines.push(' ⚠️ MEDIUM: Address medium issues during your next refactor cycle.');
}
if (summary.averageTrustScore < 50) {
lines.push(' 📋 Consider a full manual code review of this codebase.');
} else if (summary.averageTrustScore < 70) {
lines.push(' 📋 This codebase shows moderate AI-generation signals. Review recommended.');
} else {
lines.push(' ✅ This codebase scores well. Continue monitoring for new issues.');
}
lines.push('');
lines.push('═'.repeat(60));
lines.push('Generated by ai-code-trust v1.0.0');
lines.push('═'.repeat(60));
return lines.join('\n');
}
export function generateJSONReport(summary: AnalysisSummary): string {
const report = {
version: '1.0.0',
generatedAt: new Date().toISOString(),
summary: {
riskLevel: summary.riskLevel,
averageTrustScore: summary.averageTrustScore,
totalFiles: summary.totalFiles,
scannedFiles: summary.scannedFiles,
totalLines: summary.totalLines,
issuesFound: summary.issuesFound,
issueBreakdown: {
critical: summary.criticalIssues,
high: summary.highIssues,
medium: summary.mediumIssues,
low: summary.lowIssues
},
filesWithIssues: summary.filesWithIssues,
cleanFiles: summary.cleanFiles
},
files: summary.fileResults.map(f => ({
file: f.file,
lines: f.lines,
trustScore: f.trustScore,
issueCount: f.detection.patterns.length,
issues: f.detection.patterns.map(p => ({
line: p.line,
severity: p.severity,
description: p.description
}))
}))
};
return JSON.stringify(report, null, 2);
}
export function generateMarkdownReport(summary: AnalysisSummary): string {
const lines: string[] = [];
lines.push('# AI Code Trust Audit Report');
lines.push('');
lines.push(`**Generated:** ${new Date().toISOString()}`);
lines.push(`**Risk Level:** ${summary.riskLevel.toUpperCase()}`);
lines.push(`**Average Trust Score:** ${summary.averageTrustScore}/100`);
lines.push('');
lines.push('## Summary');
lines.push('');
lines.push(`| Metric | Value |`);
lines.push(`|--------|-------|`);
lines.push(`| Files Scanned | ${summary.scannedFiles} / ${summary.totalFiles} |`);
lines.push(`| Total Lines | ${summary.totalLines.toLocaleString()} |`);
lines.push(`| Issues Found | ${summary.issuesFound} |`);
lines.push(`| Critical | ${summary.criticalIssues} |`);
lines.push(`| High | ${summary.highIssues} |`);
lines.push(`| Medium | ${summary.mediumIssues} |`);
lines.push(`| Low | ${summary.lowIssues} |`);
lines.push(`| Clean Files | ${summary.cleanFiles} |`);
lines.push('');
if (summary.fileResults.length > 0) {
lines.push('## Files by Trust Score');
lines.push('');
lines.push(`| Score | File | Lines | Issues |`);
lines.push(`|-------|------|-------|--------|`);
for (const file of summary.fileResults) {
lines.push(`| ${file.trustScore} | ${file.file} | ${file.lines} | ${file.detection.patterns.length} |`);
}
lines.push('');
}
const criticalHighFiles = summary.fileResults.filter(
f => f.detection.patterns.some(p => p.severity === 'critical' || p.severity === 'high')
);
if (criticalHighFiles.length > 0) {
lines.push('## Critical & High Severity Issues');
lines.push('');
for (const file of criticalHighFiles) {
const issues = file.detection.patterns.filter(p => p.severity === 'critical' || p.severity === 'high');
if (issues.length > 0) {
lines.push(`### ${file.file}`);
lines.push('');
for (const issue of issues) {
lines.push(`- **${issue.severity.toUpperCase()}** (Line ${issue.line}): ${issue.description}`);
}
lines.push('');
}
}
}
lines.push('## Recommendations');
lines.push('');
if (summary.criticalIssues > 0) {
lines.push('- **CRITICAL**: Fix all critical issues immediately before deploying.');
}
if (summary.highIssues > 0) {
lines.push('- **HIGH**: Review and fix high-severity issues before code review.');
}
if (summary.mediumIssues > 0) {
lines.push('- **MEDIUM**: Address medium issues during your next refactor cycle.');
}
if (summary.averageTrustScore < 50) {
lines.push('- Consider a full manual code review of this codebase.');
} else if (summary.averageTrustScore < 70) {
lines.push('- This codebase shows moderate AI-generation signals. Review recommended.');
} else {
lines.push('- This codebase scores well. Continue monitoring for new issues.');
}
lines.push('');
lines.push('---');
lines.push('*Generated by ai-code-trust v1.0.0*');
return lines.join('\n');
}
+19
View File
@@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"resolveJsonModule": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}