import { readFile, readdir } from 'node:fs/promises'; import { basename, join } from 'node:path'; import type { RawScanReport } from '../index.js'; /** * Reads the latest supreme-max JSON report from * `{projectPath}/.supreme2l/reports/supreme2l-scan-*.json`. * * The latest report is selected by the largest timestamp encoded in the * filename, not by file modification time. */ export async function scannerReader(projectPath: string): Promise { const reportsDir = join(projectPath, '.supreme2l', 'reports'); const candidates = await collectReportPaths(reportsDir); if (candidates.length === 0) { throw new Error(`No supreme-max reports found in: ${reportsDir}`); } const latestReportPath = selectLatestByFilenameTimestamp(candidates); const raw = await readFile(latestReportPath, 'utf8'); let parsed: unknown; try { parsed = JSON.parse(raw); } catch (error) { const message = error instanceof Error ? error.message : 'Unknown JSON parse error'; throw new Error(`Failed to parse report JSON at ${latestReportPath}: ${message}`); } assertIsRawScanReport(parsed, latestReportPath); return parsed; } async function collectReportPaths(reportsDir: string): Promise { try { const files = await readdir(reportsDir); return files .filter((f) => f.startsWith('supreme2l-scan-') && f.endsWith('.json')) .map((f) => join(reportsDir, f)); } catch { return []; } } function selectLatestByFilenameTimestamp(paths: string[]): string { let latestPath: string | null = null; let latestTimestampValue: bigint | null = null; for (const candidatePath of paths) { const timestampValue = extractTimestampValue(candidatePath); if (timestampValue === null) { continue; } if (latestTimestampValue === null || timestampValue > latestTimestampValue) { latestTimestampValue = timestampValue; latestPath = candidatePath; } } if (latestPath === null) { throw new Error('Found report files, but none matched supreme2l-scan-{timestamp}.json naming'); } return latestPath; } function extractTimestampValue(reportPath: string): bigint | null { const filename = basename(reportPath); const match = /^supreme2l-scan-(.+)\.json$/i.exec(filename); if (match === null) { return null; } const digits = match[1].replace(/\D/g, ''); if (digits.length === 0) { return null; } return BigInt(digits); } function assertIsRawScanReport(value: unknown, sourcePath: string): asserts value is RawScanReport { if (!isRecord(value)) { throw new Error(`Report at ${sourcePath} must be a JSON object`); } if (typeof value.timestamp !== 'string') { throw new Error(`Report at ${sourcePath} has invalid field: timestamp`); } if (typeof value.supreme2l_version !== 'string') { throw new Error(`Report at ${sourcePath} has invalid field: supreme2l_version`); } if (!isRecord(value.scan_summary)) { throw new Error(`Report at ${sourcePath} has invalid field: scan_summary`); } if (!isRecord(value.severity_breakdown)) { throw new Error(`Report at ${sourcePath} has invalid field: severity_breakdown`); } if (!Array.isArray(value.findings)) { throw new Error(`Report at ${sourcePath} has invalid field: findings`); } } function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null; }