import { db } from "@workspace/db"; import { rulesTable, promptsTable, aiProvidersTable, type Prompt, } from "@workspace/db"; import { eq } from "drizzle-orm"; import { STATIC_RULES, runStaticRule, type ParsedFile, type RawFinding, type Severity, } from "./ruleCatalog"; import type { FindingCounts as DbFindingCounts } from "@workspace/db"; import { runAiAnalysis } from "./aiAnalysis"; const SEVERITY_WEIGHT: Record = { critical: 50, high: 18, medium: 7, low: 2, info: 0, }; export type EngineResult = { findings: RawFinding[]; counts: DbFindingCounts; riskScore: number; verdict: "pass" | "review" | "block"; aiUsed: boolean; aiError: string | null; }; export function computeCounts(findings: RawFinding[]): DbFindingCounts { const counts: DbFindingCounts = { critical: 0, high: 0, medium: 0, low: 0, info: 0, security: 0, privacy: 0, total: findings.length, }; for (const f of findings) { counts[f.severity] += 1; counts[f.axis] += 1; } return counts; } export function computeScore(findings: RawFinding[]): number { let score = 0; for (const f of findings) score += SEVERITY_WEIGHT[f.severity]; return Math.min(100, score); } export function computeVerdict( findings: RawFinding[], score: number, ): "pass" | "review" | "block" { const hasCritical = findings.some((f) => f.severity === "critical"); const hasHigh = findings.some((f) => f.severity === "high"); if (hasCritical || score >= 70) return "block"; if (hasHigh || score >= 20) return "review"; return "pass"; } export async function analyzeSkill( files: ParsedFile[], useAi: boolean, ): Promise { const dbRules = await db.select().from(rulesTable); const ruleConfig = new Map( dbRules.map((r) => [r.ruleId, { enabled: r.enabled, severity: r.severity as Severity }]), ); const findings: RawFinding[] = []; for (const rule of STATIC_RULES) { const cfg = ruleConfig.get(rule.ruleId); if (cfg && !cfg.enabled) continue; const severity = cfg?.severity ?? rule.defaultSeverity; for (const file of files) { findings.push(...runStaticRule(rule, file, severity)); } } let aiUsed = false; let aiError: string | null = null; if (useAi) { const aiRulesEnabled = dbRules .filter((r) => r.detectionType === "ai") .some((r) => r.enabled); const [provider] = await db .select() .from(aiProvidersTable) .where(eq(aiProvidersTable.enabled, true)) .limit(1); if (!aiRulesEnabled) { aiError = "KI-Regeln sind im Regelwerk deaktiviert."; } else if (!provider) { aiError = "Kein aktiver KI-Provider konfiguriert. Bitte im Admin-Bereich einrichten."; } else if (!provider.apiToken) { aiError = `Für den Provider "${provider.name}" ist kein API-Token hinterlegt.`; } else { const prompts: Prompt[] = await db.select().from(promptsTable); const result = await runAiAnalysis(provider, prompts, files); aiError = result.error; if (!result.error) { aiUsed = true; findings.push(...result.findings); } } } const riskScore = computeScore(findings); const counts = computeCounts(findings); const verdict = computeVerdict(findings, riskScore); return { findings, counts, riskScore, verdict, aiUsed, aiError }; }