// --------------------------------------------------------------------------- // caduceus — persona lens router // // Maps the active persona to a required-lens subset for the review // state machine. Only 4 of the 10 built-in personas trigger lens // requirements (security, reviewer, architect, debugger); the rest // allocate no lens runs. // // See design.md §3.6 for the API contract and §6.3 for the full // routing table. // --------------------------------------------------------------------------- import type { LensId, LensRegistry } from "./review-lens-framework.ts"; import type { PersonaSnapshot, LensRunDetail } from "./review-types.ts"; // --------------------------------------------------------------------------- // Public types // --------------------------------------------------------------------------- // PersonaSnapshot and LensRunSummary are defined in ./review-types.ts // and re-exported here for backward compatibility with callers that // imported them from this module. export type { PersonaSnapshot, LensRunDetail }; export type LensSelectionRule = { persona: string; required: ReadonlyArray; }; // --------------------------------------------------------------------------- // Routing table (per design.md §6.3) // --------------------------------------------------------------------------- /** * Static routing table. Each entry maps a persona name to its * required lens subset. Personas not in the table allocate no lens * runs (they are non-binding by design). * * Phase A only: 4 binding personas. Future phases may add more. */ export const PERSONA_LENS_ROUTING: ReadonlyArray = Object.freeze([ { persona: "security", required: Object.freeze(["security", "risk"]) }, { persona: "reviewer", required: Object.freeze(["readability", "spec-compliance"]), }, { persona: "architect", required: Object.freeze(["spec-compliance", "risk"]), }, { persona: "debugger", required: Object.freeze(["correctness"]) }, ]); // --------------------------------------------------------------------------- // Public API // --------------------------------------------------------------------------- /** * Return the required lens IDs for a given persona name. Defensive: * returns an empty array for unknown personas (does not throw). * Order is preserved from the routing table. */ export function requiredLensesForPersona(persona: string): ReadonlyArray { const entry = PERSONA_LENS_ROUTING.find((r) => r.persona === persona); return entry ? entry.required : []; } /** * Allocate a LensRunSummary for each lens required by the persona * snapshot. All runs start in `queued` status with `personaRequired: * true`. The state machine advances them: `queued → running → * completed` (or `→ skipped` if the lens has no `run` implementation, * per design.md §3.5). * * The `registry` parameter is accepted for forward compatibility; * the router always emits `queued` (the state machine owns the * `skipped` decision). */ export function allocateLensRuns( _registry: LensRegistry, snapshot: PersonaSnapshot, ): ReadonlyArray { const required = requiredLensesForPersona(snapshot.activePersona); return Object.freeze( required.map( (lensId): LensRunDetail => Object.freeze({ lensId, status: "queued", personaRequired: true, findingsCount: 0, startedAt: null, completedAt: null, durationMs: 0, findings: Object.freeze([]), }), ), ); }