import type { AnalysisSnapshot, AnalyzerFailure } from "./policy.ts"; export interface AnalysisRequest { readonly analyzerKey: string; readonly paths: readonly string[] } export interface AnalyzerAdapter { analyze(paths: readonly string[], workspaceRevision: number, signal?: AbortSignal): Promise } export class AnalysisCoordinator { constructor(private readonly adapters: ReadonlyMap) {} async run(requests: readonly AnalysisRequest[], revision: number, signal?: AbortSignal): Promise> { const grouped = new Map>(); for (const request of requests) { const paths = grouped.get(request.analyzerKey) ?? new Set(); for (const path of request.paths) paths.add(path); grouped.set(request.analyzerKey, paths); } const entries = await Promise.all([...grouped].sort(([a], [b]) => a.localeCompare(b)).map(async ([key, paths]) => { const adapter = this.adapters.get(key); const sorted = [...paths].sort(); if (!adapter) return [key, failed(key, revision, sorted, { kind: "missing", message: `No analyzer registered for ${key}` })] as const; try { return [key, deepFreeze(await adapter.analyze(sorted, revision, signal))] as const; } catch (error) { return [key, failed(key, revision, sorted, { kind: "internal", message: error instanceof Error ? error.message : String(error) })] as const; } })); return readonlyMap(entries); } } function failed(key: string, workspaceRevision: number, paths: readonly string[], failure: AnalyzerFailure): AnalysisSnapshot { return Object.freeze({ analyzerKey: key, workspaceRevision, paths: Object.freeze([...paths]), findings: Object.freeze([]), durationMs: 0, failure: Object.freeze(failure) }); } export function deepFreeze(value: T): T { if (value && typeof value === "object" && !Object.isFrozen(value)) { Object.freeze(value); for (const item of Object.values(value as Record)) deepFreeze(item); } return value; } export function readonlyMap(entries: readonly (readonly [K, V])[]): ReadonlyMap { const map = new Map(entries); Object.freeze(map); return new Proxy(map, { get(target, prop) { if (prop === "set" || prop === "delete" || prop === "clear") return undefined; const value = Reflect.get(target, prop, target) as unknown; return typeof value === "function" ? (value as Function).bind(target) : value; } }); }