Files
rea/src/cli/nativeFunctionMatchingCommands.ts
T

105 lines
3.9 KiB
TypeScript

import { z } from "incur";
import type { Logger } from "pino";
import { matchNativeFunctionsFromBundles } from "../application/native/NativeFunctionMatchingService.js";
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import { matchNativeFunctionsInputSchema } from "../contracts/native/functionMatchingContracts.js";
import { FUNCTION_COMPARISON_PROVIDER } from "../domain/comparisonEvidenceIdentity.js";
import { CLI_COMMANDS } from "../cliCommandNames.js";
import { parseCliJsonInput } from "../cliJsonInput.js";
import { logCliCommand } from "../cliLogging.js";
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import type { Evidence } from "../domain/evidence.js";
import { withCommandCancellation } from "./commandCancellation.js";
import {
cliEvidenceDeliveryInputError,
publishCliAnalysisEvidence,
} from "./analysisEvidenceDelivery.js";
import { finishAnalysisCommand } from "./finishAnalysisCommand.js";
import type { CliInstance } from "./types.js";
/** Resolve portable input bundles, then use the same read-only matching service as MCP. */
export const registerNativeFunctionMatchingCommands = (
cli: CliInstance,
logger: Logger,
): void => {
cli.command(CLI_COMMANDS.matchNativeFunctions, {
description:
"Match two observed native function inventories without executing targets",
args: z.object({
input: z
.string()
.describe(
"JSON or JSON file: left/right inventories, optional observations and detail",
),
}),
options: z.object({
source_bundle: z
.array(z.string())
.default([])
.describe(
"Canonical source Evidence bundle for retained references; repeat for each input",
),
evidence_output: z
.string()
.min(1)
.optional()
.describe(
"Save full matching Evidence and selected sources; required for default summary; never overwrites",
),
}),
run: ({ args, options }) =>
withCommandCancellation((signal) =>
logCliCommand(logger, CLI_COMMANDS.matchNativeFunctions, async () => {
const input = await parseCliJsonInput(
args.input,
"match_native_functions",
signal,
);
if (!input.ok) return input.error;
const parsed = matchNativeFunctionsInputSchema.safeParse(input.value);
if (!parsed.success)
return projectAnalysisError(
analysisInputErrorFromIssues(
"match_native_functions",
parsed.error.issues,
input.value,
),
);
const delivery = {
providerId: FUNCTION_COMPARISON_PROVIDER.id,
operation: "match_native_functions",
signal,
detail: parsed.data.detail,
evidenceOutput: options.evidence_output,
};
const invalid = cliEvidenceDeliveryInputError(delivery);
if (invalid !== undefined) return projectAnalysisError(invalid);
const resources = new ArtifactResourceScope();
let sources: readonly Evidence[] = [];
const complete = await finishAnalysisCommand(
async () => {
const result = await matchNativeFunctionsFromBundles(
parsed.data,
options.source_bundle,
resources,
{ signal },
);
if (!result.ok) return result;
sources = result.value.sources;
return { ok: true as const, value: result.value.evidence };
},
resources,
delivery.providerId,
delivery.operation,
signal,
);
return publishCliAnalysisEvidence(complete, {
...delivery,
sourceEvidence: sources,
});
}),
),
});
};