// engine/security-analyzer/scoring.ts — Security scoring, drift detection, code scanning
// SPEC-029
import { readFile } from 'node:fs/promises';
import { loadSecurityPatterns } from './config-loader.js';
// === Security scoring ===
export function calculateSecurityScore(gaps, violations) {
    const maxGapScore = 60;
    const gapPenalty = gaps.reduce((acc, g) => {
        if (g.verificationStatus === 'missing') {
            return acc + 10;
        }
        if (g.verificationStatus === 'partial') {
            return acc + 5;
        }
        return acc;
    }, 0);
    const gapScore = Math.max(0, maxGapScore - gapPenalty);
    const maxScanScore = 40;
    const violationPenalty = violations.reduce((acc, v) => {
        if (v.severity === 'critical') {
            return acc + 20;
        }
        if (v.severity === 'high') {
            return acc + 10;
        }
        if (v.severity === 'medium') {
            return acc + 5;
        }
        return acc + 2;
    }, 0);
    const scanScore = Math.max(0, maxScanScore - violationPenalty);
    const score = gapScore + scanScore;
    const grade = computeGrade(score);
    return {
        score,
        grade,
        breakdown: {
            specCompleteness: gapScore,
            codePatternScan: scanScore,
            gapCount: gaps.length,
            violationCount: violations.length,
        },
    };
}
function computeGrade(score) {
    if (score >= 90) {
        return 'A';
    }
    if (score >= 75) {
        return 'B';
    }
    if (score >= 60) {
        return 'C';
    }
    if (score >= 45) {
        return 'D';
    }
    return 'F';
}
// === Security drift detection ===
export function detectSecurityDrift(specContent, approvedCriteria) {
    const removedCriteria = [];
    const normalizedContent = specContent.toLowerCase().replace(/[^a-z0-9 ]/g, '');
    for (const criterion of approvedCriteria) {
        const simplified = criterion
            .toLowerCase()
            .replace(/[^a-z0-9 ]/g, '')
            .slice(0, 40);
        if (!normalizedContent.includes(simplified)) {
            removedCriteria.push(criterion);
        }
    }
    return removedCriteria;
}
// === Code pattern scanning ===
export async function scanCodeForViolations(codeFiles) {
    const config = await loadSecurityPatterns();
    const violations = [];
    for (const filePath of codeFiles) {
        let content;
        try {
            content = await readFile(filePath, 'utf-8');
        }
        catch {
            continue;
        }
        const lines = content.split('\n');
        for (const pattern of config.insecurePatterns) {
            const regex = new RegExp(pattern.regex, 'gi');
            lines.forEach((line, idx) => {
                if (regex.test(line)) {
                    violations.push({
                        patternId: pattern.id,
                        file: filePath,
                        line: idx + 1,
                        severity: pattern.severity,
                        remediation: pattern.remediation,
                    });
                }
                regex.lastIndex = 0;
            });
        }
    }
    return violations;
}
//# sourceMappingURL=scoring.js.map