import fs from "fs"; import path from "path"; interface AgentSemanticSpecSummary { scenario_id: string; domain: string | null; title: string | null; semantic_tags: string[]; steps_total: number; } interface AgentSemanticInvariantSummary { direct_answer_ok: boolean | null; temporal_honesty_ok: boolean | null; selected_object_continuity_ok: boolean | null; truth_gate_ok: boolean | null; human_answer_quality_ok: boolean | null; meta_context_integrity_ok: boolean | null; } export interface AgentSemanticAcceptanceSummary { scenario_id: string; output_dir: string; relative_output_dir: string; final_status: string | null; final_status_reason: string | null; review_overall_status: string | null; acceptance_gate_passed: boolean | null; critical_path_green: boolean | null; unresolved_p0_count: number | null; unresolved_p1_count: number | null; unresolved_p2_count: number | null; steps_total: number | null; steps_passed: number | null; steps_with_warning: number | null; steps_failed: number | null; updated_at: string | null; invariants: AgentSemanticInvariantSummary; } function toRecord(value: unknown): Record | null { if (!value || typeof value !== "object" || Array.isArray(value)) { return null; } return value as Record; } function toStringSafe(value: unknown): string | null { if (typeof value !== "string") { return null; } const trimmed = value.trim(); return trimmed.length > 0 ? trimmed : null; } function toBooleanSafe(value: unknown): boolean | null { if (typeof value === "boolean") { return value; } if (typeof value === "string") { const lowered = value.trim().toLowerCase(); if (["1", "true", "yes", "on"].includes(lowered)) return true; if (["0", "false", "no", "off"].includes(lowered)) return false; } return null; } function toNumberSafe(value: unknown): number | null { if (typeof value === "number" && Number.isFinite(value)) { return value; } if (typeof value === "string" && value.trim().length > 0) { const parsed = Number(value); return Number.isFinite(parsed) ? parsed : null; } return null; } function readJsonFile(filePath: string): unknown { return JSON.parse(fs.readFileSync(filePath, "utf-8")); } function normalizeSemanticTags(rawSteps: unknown): string[] { if (!Array.isArray(rawSteps)) { return []; } const tags = new Set(); for (const rawStep of rawSteps) { const step = toRecord(rawStep); if (!step || !Array.isArray(step.semantic_tags)) { continue; } for (const rawTag of step.semantic_tags) { const tag = toStringSafe(rawTag); if (tag) { tags.add(tag); } } } return Array.from(tags).sort((left, right) => left.localeCompare(right)); } export function readAgentSemanticSpecSummaryFromFile(sourceSpecFile: string | null | undefined): AgentSemanticSpecSummary | null { const normalizedPath = toStringSafe(sourceSpecFile); if (!normalizedPath || !fs.existsSync(normalizedPath)) { return null; } try { const parsed = readJsonFile(normalizedPath); const record = toRecord(parsed); if (!record) { return null; } const scenarioId = toStringSafe(record.scenario_id); if (!scenarioId) { return null; } const semanticTags = normalizeSemanticTags(record.steps); return { scenario_id: scenarioId, domain: toStringSafe(record.domain), title: toStringSafe(record.title), semantic_tags: semanticTags, steps_total: Array.isArray(record.steps) ? record.steps.length : 0 }; } catch { return null; } } function buildInvariantSummary(rawValue: unknown): AgentSemanticInvariantSummary { const record = toRecord(rawValue); return { direct_answer_ok: toBooleanSafe(record?.direct_answer_ok), temporal_honesty_ok: toBooleanSafe(record?.temporal_honesty_ok), selected_object_continuity_ok: toBooleanSafe(record?.selected_object_continuity_ok), truth_gate_ok: toBooleanSafe(record?.truth_gate_ok), human_answer_quality_ok: toBooleanSafe(record?.human_answer_quality_ok), meta_context_integrity_ok: toBooleanSafe(record?.meta_context_integrity_ok) }; } export function findLatestAgentSemanticAcceptanceSummary(input: { artifactsRootDir: string; repoRootDir: string; scenarioId: string | null | undefined; }): AgentSemanticAcceptanceSummary | null { const scenarioId = toStringSafe(input.scenarioId); if (!scenarioId) { return null; } if (!fs.existsSync(input.artifactsRootDir)) { return null; } const candidates = fs .readdirSync(input.artifactsRootDir, { withFileTypes: true }) .filter((entry) => entry.isDirectory() && entry.name.startsWith(`${scenarioId}_`)) .map((entry) => { const outputDir = path.resolve(input.artifactsRootDir, entry.name); const packStatePath = path.resolve(outputDir, "pack_state.json"); if (!fs.existsSync(packStatePath)) { return null; } const stats = fs.statSync(packStatePath); return { outputDir, packStatePath, mtimeMs: stats.mtimeMs }; }) .filter((item): item is { outputDir: string; packStatePath: string; mtimeMs: number } => item !== null) .sort((left, right) => right.mtimeMs - left.mtimeMs); const latest = candidates[0]; if (!latest) { return null; } try { const parsed = readJsonFile(latest.packStatePath); const packState = toRecord(parsed); if (!packState) { return null; } return { scenario_id: scenarioId, output_dir: latest.outputDir, relative_output_dir: path.relative(input.repoRootDir, latest.outputDir).replace(/\\/g, "/"), final_status: toStringSafe(packState.final_status), final_status_reason: toStringSafe(packState.final_status_reason), review_overall_status: toStringSafe(packState.review_overall_status), acceptance_gate_passed: toBooleanSafe(packState.acceptance_gate_passed), critical_path_green: toBooleanSafe(packState.critical_path_green), unresolved_p0_count: toNumberSafe(packState.unresolved_p0_count), unresolved_p1_count: toNumberSafe(packState.unresolved_p1_count), unresolved_p2_count: toNumberSafe(packState.unresolved_p2_count), steps_total: toNumberSafe(packState.steps_total), steps_passed: toNumberSafe(packState.steps_passed), steps_with_warning: toNumberSafe(packState.steps_with_warning), steps_failed: toNumberSafe(packState.steps_failed), updated_at: toStringSafe(packState.updated_at), invariants: buildInvariantSummary(packState.invariants) }; } catch { return null; } }