/** * generateRecommendation — recommendation generation end-to-end. * * Wires the sprint-2 judge loop into a real recommendation path: * 1. Assemble profile context from FactStore + medical profile * 2. Build four per-lens LLM clients (tier-diverse when cloud ON, all local when cloud OFF) * 3. Generate a candidate + gate through runJudgeLoop * 4. Emit a Finding: * - accepted → kind="action" (LLM-assigned urgency/severity/confidence, NO refer-out hedging) * - rejected → kind="question" flagged for review (per-lens dissent) * - short-circuit/refuse → canned escalation, NO finding * 5. Append AuditLog entry (IDs/enums only — NonGoal #3) * * engine.ts is NO-TOUCH: patterns are COPIED (not imported) from engine.ts:250-410. * runJudgeLoop is IMPORTED — do NOT re-implement (NonGoal #5). * * sc-3-5 (fail-closed model selection): when egress.isAllowed("cloud-inference") is FALSE, * EVERY client (all four lenses + the generator) resolves via buildMedicalInferenceClient. * deps.clientFactory is threaded through both paths so tests can spy without network. */ import type { BoberConfig } from "../../config/schema.js"; import type { LLMClient } from "../../providers/types.js"; import { EgressGuard } from "../egress.js"; import type { GuardrailSet } from "../types.js"; import type { ClientFactory } from "../inference.js"; import { AuditLog } from "../audit.js"; import type { FactStore } from "../../state/facts.js"; import type { ProfileCipher } from "../profile.js"; import { writeFinding } from "../analysis/finding-writer.js"; import type { LensClients } from "./types.js"; import type { UrgencyResult } from "./urgency.js"; /** Discriminated outcome of generateRecommendation. */ export type RecommendOutcomeKind = "accepted" | "question" | "escalated" | "refused"; export interface RecommendOutcome { kind: RecommendOutcomeKind; /** Absolute path to the written Finding note (accepted + question paths). */ findingPath?: string; /** Canned escalation response (escalated path). */ cannedResponse?: string; /** Refusal reason (refused path). */ reason?: string; } /** * Injectable dependencies for generateRecommendation. * Production callers pass no deps. Tests inject everything to avoid real network/fs. */ export interface RecommendDeps { /** Pre-built lens clients (tests inject fake ScriptedClients). */ lensClients?: LensClients; /** Override the candidate generator (tests inject a deterministic fn). */ generateCandidate?: (prevFeedback?: string) => Promise; /** Override the guardrail set (tests inject shortCircuitGuard / allowGuard). */ redFlag?: GuardrailSet; /** Override the urgency assigner (tests inject a fn returning fixed values). */ assignUrgency?: (llm: LLMClient, model: string, candidate: string, context: string) => Promise; /** Override writeFinding (tests inject a fn that writes to a temp vault). */ writeFindingFn?: typeof writeFinding; /** Injected FactStore (tests pass :memory:; caller owns lifecycle). */ facts?: FactStore; /** Override the EgressGuard (tests inject a fixed allow/deny guard). */ egress?: EgressGuard; /** * Injectable ClientFactory for cloud-OFF spy test (sc-3-5). * Threaded into BOTH buildMedicalInferenceClient AND the tier createClient calls * so tests can assert no cloud provider was constructed. */ clientFactory?: ClientFactory; /** Override the AuditLog (tests inject a no-op or in-memory log). */ auditLog?: AuditLog; /** Override ProfileCipher (tests inject a reversible fake). */ profileCipher?: ProfileCipher; } /** * Run the recommendation pipeline end-to-end. * * @param projectRoot Absolute project root path * @param config Loaded BoberConfig * @param opts { question, goal?, now } — clock is read ONLY at the CLI boundary * @param deps Optional injectable dependencies for tests */ export declare function generateRecommendation(projectRoot: string, config: BoberConfig, opts: { question: string; goal?: string; now: string; }, deps?: RecommendDeps): Promise; //# sourceMappingURL=recommend.d.ts.map