import type { Finding, FindingCategory, ReviewerOutput, ValidatorOutput, } from "./types.ts"; const CATEGORIES = new Set([ "instructions", "bug", "regression", "permission", "security", "maintenance", "goal", "scope", ]); function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } function requiredString(value: unknown, field: string): string { if (typeof value !== "string" || !value.trim()) throw new Error(`Invalid ${field}`); return value; } function optionalString(value: unknown, field: string): string | undefined { if (value === undefined) return undefined; return requiredString(value, field); } function safePath(value: unknown, field: string): string { const path = requiredString(value, field); if (path.startsWith("/") || path.split("/").includes("..")) throw new Error(`Unsafe ${field}`); return path; } function parseFinding(value: unknown): Finding { if (!isRecord(value)) throw new Error("Invalid finding"); const category = requiredString( value.category, "finding category", ) as FindingCategory; if (!CATEGORIES.has(category)) throw new Error(`Unknown finding category: ${category}`); const base = { id: requiredString(value.id, "finding id"), category, title: requiredString(value.title, "finding title"), description: requiredString(value.description, "finding description"), evidence: requiredString(value.evidence, "finding evidence"), introducedByPr: requiredString(value.introducedByPr, "introducedByPr"), ...(optionalString(value.suggestion, "suggestion") ? { suggestion: String(value.suggestion) } : {}), }; if (value.kind === "code") { if (value.side !== "LEFT" && value.side !== "RIGHT") throw new Error("Invalid finding side"); if (!Number.isInteger(value.line) || Number(value.line) < 1) throw new Error("Invalid finding line"); if ( value.startLine !== undefined && (!Number.isInteger(value.startLine) || Number(value.startLine) < 1) ) { throw new Error("Invalid finding startLine"); } return { ...base, kind: "code", path: safePath(value.path, "finding path"), line: Number(value.line), ...(value.startLine === undefined ? {} : { startLine: Number(value.startLine) }), side: value.side, }; } if (value.kind === "task-omission") { return { ...base, kind: "task-omission", requirement: requiredString(value.requirement, "task requirement"), ...(value.path === undefined ? {} : { path: safePath(value.path, "finding path") }), }; } throw new Error("Invalid finding kind"); } function parseJson(text: string): unknown { let candidate = text.trim(); const fenced = /^```(?:json)?\s*([\s\S]*?)\s*```$/i.exec(candidate); if (fenced) candidate = fenced[1].trim(); try { return JSON.parse(candidate); } catch { throw new Error("Agent returned invalid JSON"); } } export function parseReviewerOutput(text: string): ReviewerOutput { const value = parseJson(text); if ( !isRecord(value) || value.schemaVersion !== 1 || !isRecord(value.coverage) || !Array.isArray(value.findings) ) { throw new Error("Invalid reviewer output"); } if ( typeof value.coverage.complete !== "boolean" || !Array.isArray(value.coverage.reviewedFiles) ) { throw new Error("Invalid coverage receipt"); } const reviewedFiles = value.coverage.reviewedFiles.map((item) => safePath(item, "reviewed file"), ); return { schemaVersion: 1, coverage: { complete: value.coverage.complete, reviewedFiles }, findings: value.findings.map(parseFinding), }; } export function parseValidatorOutput(text: string): ValidatorOutput { const value = parseJson(text); if (!isRecord(value) || value.schemaVersion !== 1) throw new Error("Invalid validator output"); if (typeof value.valid !== "boolean") throw new Error("Invalid validator decision"); if ( !Number.isInteger(value.confidence) || Number(value.confidence) < 0 || Number(value.confidence) > 100 ) { throw new Error("Invalid validator confidence"); } return { schemaVersion: 1, findingId: requiredString(value.findingId, "validator findingId"), valid: value.valid, confidence: Number(value.confidence), evidence: requiredString(value.evidence, "validator evidence"), ...(optionalString(value.rejectionReason, "rejection reason") ? { rejectionReason: String(value.rejectionReason) } : {}), finding: parseFinding(value.finding), }; } export function parseTriage(text: string): { review: boolean; reason: string } { const value = parseJson(text); if ( !isRecord(value) || value.schemaVersion !== 1 || typeof value.review !== "boolean" ) { throw new Error("Invalid triage output"); } return { review: value.review, reason: requiredString(value.reason, "triage reason"), }; } export function parseSummary(text: string): string { const value = parseJson(text); if (!isRecord(value) || value.schemaVersion !== 1) throw new Error("Invalid summary output"); return requiredString(value.summary, "summary"); }