import type { Command } from "commander"; import { collectString } from "./cli-options.js"; import { LIVE_TRANSPORT_QA_CLI_REGISTRATIONS } from "./live-transports/cli.js"; import type { QaProviderModeInput } from "./run-config.js"; import { hasQaScenarioPack } from "./scenario-catalog.js"; type QaLabCliRuntime = typeof import("./cli.runtime.js"); let qaLabCliRuntimePromise: Promise | null = null; async function loadQaLabCliRuntime(): Promise { qaLabCliRuntimePromise ??= import("./cli.runtime.js"); return await qaLabCliRuntimePromise; } async function runQaSelfCheck(opts: { repoRoot?: string; output?: string }) { const runtime = await loadQaLabCliRuntime(); await runtime.runQaLabSelfCheckCommand(opts); } async function runQaSuite(opts: { repoRoot?: string; outputDir?: string; providerMode?: QaProviderModeInput; primaryModel?: string; alternateModel?: string; fastMode?: boolean; cliAuthMode?: string; parityPack?: string; scenarioIds?: string[]; concurrency?: number; runner?: string; image?: string; cpus?: number; memory?: string; disk?: string; }) { const runtime = await loadQaLabCliRuntime(); await runtime.runQaSuiteCommand(opts); } async function runQaParityReport(opts: { repoRoot?: string; candidateSummary: string; baselineSummary: string; candidateLabel?: string; baselineLabel?: string; outputDir?: string; }) { const runtime = await loadQaLabCliRuntime(); await runtime.runQaParityReportCommand(opts); } async function runQaCharacterEval(opts: { repoRoot?: string; outputDir?: string; model?: string[]; scenario?: string; fast?: boolean; thinking?: string; modelThinking?: string[]; judgeModel?: string[]; judgeTimeoutMs?: number; blindJudgeModels?: boolean; concurrency?: number; judgeConcurrency?: number; }) { const runtime = await loadQaLabCliRuntime(); await runtime.runQaCharacterEvalCommand(opts); } async function runQaManualLane(opts: { repoRoot?: string; providerMode?: QaProviderModeInput; primaryModel?: string; alternateModel?: string; fastMode?: boolean; message: string; timeoutMs?: number; }) { const runtime = await loadQaLabCliRuntime(); await runtime.runQaManualLaneCommand(opts); } async function runQaUi(opts: { repoRoot?: string; host?: string; port?: number; advertiseHost?: string; advertisePort?: number; controlUiUrl?: string; controlUiToken?: string; controlUiProxyTarget?: string; uiDistDir?: string; autoKickoffTarget?: string; embeddedGateway?: string; sendKickoffOnStart?: boolean; }) { const runtime = await loadQaLabCliRuntime(); await runtime.runQaLabUiCommand(opts); } async function runQaDockerScaffold(opts: { repoRoot?: string; outputDir: string; gatewayPort?: number; qaLabPort?: number; providerBaseUrl?: string; image?: string; usePrebuiltImage?: boolean; bindUiDist?: boolean; }) { const runtime = await loadQaLabCliRuntime(); await runtime.runQaDockerScaffoldCommand(opts); } async function runQaDockerBuildImage(opts: { repoRoot?: string; image?: string }) { const runtime = await loadQaLabCliRuntime(); await runtime.runQaDockerBuildImageCommand(opts); } async function runQaDockerUp(opts: { repoRoot?: string; outputDir?: string; gatewayPort?: number; qaLabPort?: number; providerBaseUrl?: string; image?: string; usePrebuiltImage?: boolean; bindUiDist?: boolean; skipUiBuild?: boolean; }) { const runtime = await loadQaLabCliRuntime(); await runtime.runQaDockerUpCommand(opts); } async function runQaMockOpenAi(opts: { host?: string; port?: number }) { const runtime = await loadQaLabCliRuntime(); await runtime.runQaMockOpenAiCommand(opts); } export function isQaLabCliAvailable(): boolean { return hasQaScenarioPack(); } export function registerQaLabCli(program: Command) { const qa = program .command("qa") .description("Run private QA automation flows and launch the QA debugger"); qa.command("run") .description("Run the bundled QA self-check and write a Markdown report") .option("--repo-root ", "Repository root to target when running from a neutral cwd") .option("--output ", "Report output path") .action(async (opts: { repoRoot?: string; output?: string }) => { await runQaSelfCheck(opts); }); qa.command("suite") .description("Run repo-backed QA scenarios against the QA gateway lane") .option("--repo-root ", "Repository root to target when running from a neutral cwd") .option("--output-dir ", "Suite artifact directory") .option("--runner ", "Execution runner: host or multipass", "host") .option( "--provider-mode ", "Provider mode: mock-openai or live-frontier (legacy live-openai still works)", "mock-openai", ) .option("--model ", "Primary provider/model ref") .option("--alt-model ", "Alternate provider/model ref") .option( "--cli-auth-mode ", "CLI backend auth mode for live Claude CLI runs: auto, api-key, or subscription", ) .option("--parity-pack ", "Preset scenario pack; currently only \"agentic\" is supported") .option("--scenario ", "Run only the named QA scenario (repeatable)", collectString, []) .option("--concurrency ", "Scenario worker concurrency", (value: string) => Number(value), ) .option("--fast", "Enable provider fast mode where supported", false) .option("--image ", "Multipass image alias") .option("--cpus ", "Multipass vCPU count", (value: string) => Number(value)) .option("--memory ", "Multipass memory size") .option("--disk ", "Multipass disk size") .action( async (opts: { repoRoot?: string; outputDir?: string; runner?: string; providerMode?: QaProviderModeInput; model?: string; altModel?: string; cliAuthMode?: string; parityPack?: string; scenario?: string[]; concurrency?: number; fast?: boolean; image?: string; cpus?: number; memory?: string; disk?: string; }) => { await runQaSuite({ repoRoot: opts.repoRoot, outputDir: opts.outputDir, runner: opts.runner, providerMode: opts.providerMode, primaryModel: opts.model, alternateModel: opts.altModel, fastMode: opts.fast, cliAuthMode: opts.cliAuthMode, parityPack: opts.parityPack, scenarioIds: opts.scenario, concurrency: opts.concurrency, image: opts.image, cpus: opts.cpus, memory: opts.memory, disk: opts.disk, }); }, ); qa.command("parity-report") .description("Compare two QA suite summaries and write an agentic parity gate report") .requiredOption("--candidate-summary ", "Candidate qa-suite-summary.json path") .requiredOption("--baseline-summary ", "Baseline qa-suite-summary.json path") .option("--repo-root ", "Repository root to target when running from a neutral cwd") .option("--candidate-label