import { safeParseJson } from "./domain/safeJson.js"; import { AnalysisInputError } from "./domain/analysisErrorCore.js"; import { analysisInputErrorFromIssues } from "./domain/inputIssueProjection.js"; import { bundleComparisonInputSchema } from "./domain/bundleComparison.js"; import { Cli, z } from "incur"; import { compareEvidenceBundlesCommand, exportEvidenceBundleCommand, importEvidenceBundleCommand, } from "./application/EvidenceBundleCommands.js"; import { logCliCommand } from "./cliLogging.js"; import type { Logger } from "pino"; import { CLI_COMMANDS } from "./cliCommandNames.js"; import type { JsonValue } from "./domain/jsonValue.js"; import { projectAnalysisError } from "./domain/analysisErrorProjection.js"; import type { AnalysisError } from "./domain/analysisErrorBase.js"; /** Register caller-path Evidence commands. */ export const registerEvidenceCommands = ( cli: ReturnType, logger: Logger, ): void => { cli.command(CLI_COMMANDS.evidenceImport, { description: "Validate and import a bounded local Evidence bundle", args: z.object({ path: z.string().describe("Evidence bundle JSON path"), }), run: ({ args }) => logCliCommand(logger, "evidence-import", async () => { const imported = await importEvidenceBundleCommand(args.path); return imported.ok ? imported.value : cliError(imported.error); }), }); cli.command(CLI_COMMANDS.evidenceExport, { description: "Validate and atomically export canonical Evidence JSON", args: z.object({ source: z.string().describe("Existing evidence bundle JSON path"), output: z.string().describe("Canonical output JSON path"), }), options: z.object({ overwrite: z.boolean().default(false).describe("Replace output file"), }), run: ({ args, options }) => logCliCommand(logger, "evidence-export", async () => { const exported = await exportEvidenceBundleCommand( args.source, args.output, options.overwrite, ); return exported.ok ? exported.value : cliError(exported.error); }), }); cli.command(CLI_COMMANDS.compare, { aliases: ["compare-bundles"], description: "Compare two canonical Evidence bundles", args: z.object({ left: z.string().describe("Left Evidence bundle JSON path"), right: z.string().describe("Right Evidence bundle JSON path"), }), options: z.object({ record_pairs: z .string() .optional() .describe( "JSON array of explicit left_evidence_id/right_evidence_id observation pairs", ), }), run: ({ args, options }) => logCliCommand(logger, "compare", async () => { const decoded = options.record_pairs === undefined ? { ok: true as const, value: [] } : safeParseJson(options.record_pairs); if (!decoded.ok) return cliError( new AnalysisInputError( "compare_bundles", { cause: decoded.cause }, [ { path: ["record_pairs"], reason: "invalid_format", expected: "JSON array", }, ], ), ); const input = { record_pairs: decoded.value }; const selected = bundleComparisonInputSchema .pick({ record_pairs: true }) .safeParse(input); if (!selected.success) return cliError( analysisInputErrorFromIssues( "compare_bundles", selected.error.issues, input, ), ); const compared = await compareEvidenceBundlesCommand({ leftPath: args.left, rightPath: args.right, recordPairs: selected.data.record_pairs, }); return compared.ok ? compared.value : cliError(compared.error); }), }); }; const cliError = (error: AnalysisError): JsonValue => ({ error: "Analysis failed", ...projectAnalysisError(error), });